replace github discussions with local guides in the docs search (#42335)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

feature

## What is the current behavior?

Currently, old GitHub discussions appear in the docs search instead of
troubleshooting guides in docs/guides/troubleshooting

## What is the new behavior?

Local troubleshooting guides appear in the search

## Additional context

<img width="958" height="846" alt="CleanShot 2026-01-31 at 23 37 33@2x"
src="https://github.com/user-attachments/assets/445fab5d-764a-4b4d-b4ef-c29ab675a9ae"
/>


**troubleshooting.ts** - New source loader that reads local MDX files
from content/troubleshooting/ directly instead of fetching from GitHub
Discussions API
- Generates correct docs paths: /guides/troubleshooting/{slug}
- Uses type = 'troubleshooting' for proper search result mapping
- Sets slug: undefined to avoid trailing # in URLs
- Checksum includes title/topics/keywords so metadata-only changes
trigger re-indexing
- Left comments for review 

**index.ts** - Replaced GitHub discussion sources with local
troubleshooting sources
- Removed GitHubDiscussionLoader, fetchDiscussions,
buildGithubUrlToSlugMap imports
- Added fetchTroubleshootingSources and TroubleshootingSource
- Updated SearchSource type union

**globalSearchModel.ts** - Changed type mapping from
'github-discussions' to 'troubleshooting'

**generate-embeddings.ts** - Removed GitHub App env vars from required
list (DOCS_GITHUB_APP_ID, DOCS_GITHUB_APP_INSTALLATION_ID,
DOCS_GITHUB_APP_PRIVATE_KEY) since they're no longer needed


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Local troubleshooting articles are now indexed and appear directly in
search results for easier access to step‑by‑step guidance.
* Search UI now recognizes a Troubleshooting page type and shows
appropriate icons/sections.

* **Refactor**
* Search sourcing switched from external discussion feeds to local
troubleshooting sources to improve relevance and indexing consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Illia Basalaiev <illiab@IMB3.local>
Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
This commit is contained in:
authored and GitHub committed 2026-02-23 13:54:40 +01:00
1 parent e707b71833
commit ce5cce5030
6 files changed
+122 -29

No files matched your search

@@ -128,7 +128,7 @@ function createModelFromMatch({
} else {
return null
}
case 'github-discussions':
case 'troubleshooting':
return new TroubleshootingModel({
title: page_title,
href,
@@ -446,9 +446,6 @@ async function generateEmbeddings() {
}
requireEnvOrThrow([
'DOCS_GITHUB_APP_ID',
'DOCS_GITHUB_APP_INSTALLATION_ID',
'DOCS_GITHUB_APP_PRIVATE_KEY',
'NEXT_PUBLIC_MISC_ANON_KEY',
'NEXT_PUBLIC_MISC_URL',
'NEXT_PUBLIC_SUPABASE_URL',
+11 -21
View File
@@ -1,10 +1,5 @@
import { type GuideModel } from '../../../resources/guide/guideModel.js'
import { GuideModelLoader } from '../../../resources/guide/guideModelLoader.js'
import {
GitHubDiscussionLoader,
type GitHubDiscussionSource,
fetchDiscussions,
} from './github-discussion.js'
import { LintWarningsGuideLoader, type LintWarningsGuideSource } from './lint-warnings-guide.js'
import { MarkdownLoader, type MarkdownSource } from './markdown.js'
import { IntegrationLoader, type IntegrationSource, fetchPartners } from './partner-integrations.js'
@@ -16,13 +11,14 @@ import {
OpenApiReferenceLoader,
type OpenApiReferenceSource,
} from './reference-doc.js'
import { fetchTroubleshootingSources, type TroubleshootingSource } from './troubleshooting.js'
export type SearchSource =
| MarkdownSource
| OpenApiReferenceSource
| ClientLibReferenceSource
| CliReferenceSource
| GitHubDiscussionSource
| TroubleshootingSource
| IntegrationSource
| LintWarningsGuideSource
@@ -150,21 +146,15 @@ export async function fetchAllSources(fullIndex: boolean) {
.then((data) => data.flat())
: []
const githubDiscussionSources = fetchDiscussions(
'supabase',
'supabase',
'DIC_kwDODMpXOc4CUvEr' // 'Troubleshooting' category
)
.then((discussions) =>
Promise.all(
discussions.map((discussion) =>
new GitHubDiscussionLoader('supabase/supabase', discussion).load()
)
)
)
// Load troubleshooting articles from local MDX files
const troubleshootingSources = fetchTroubleshootingSources()
.then((loaders) => Promise.all(loaders.map((loader) => loader.load())))
.then((data) => data.flat())
const sources: SearchSource[] = (
// Type assertion required because ReferenceLoader.load() returns Promise<BaseSource[]>
// which widens the inferred union type. All concrete sources in this array are valid
// SearchSource types (MarkdownSource, OpenApiReferenceSource, etc.).
const sources = (
await Promise.all([
guideSources,
lintWarningsGuideSources,
@@ -177,9 +167,9 @@ export async function fetchAllSources(fullIndex: boolean) {
ktLibReferenceSource,
cliReferenceSource,
partnerIntegrationSources,
githubDiscussionSources,
troubleshootingSources,
])
).flat()
).flat() as SearchSource[]
return sources
}
@@ -0,0 +1,101 @@
import { createHash } from 'node:crypto'
import { BaseLoader, BaseSource } from './base.js'
import {
getAllTroubleshootingEntriesInternal,
getArticleSlug,
} from '../../../features/docs/Troubleshooting.utils.common.mjs'
import type { ITroubleshootingEntry } from '../../../features/docs/Troubleshooting.utils.js'
/**
* Loader for troubleshooting articles from local MDX files.
*
* The path format is `/guides/troubleshooting/{slug}` where slug is derived
* from the filename (e.g., `auth-error-handling.mdx` → `auth-error-handling`).
*/
export class TroubleshootingLoader extends BaseLoader {
type = 'troubleshooting' as const
constructor(
source: string,
public entry: ITroubleshootingEntry
) {
const slug = getArticleSlug(entry)
super(source, `/guides/troubleshooting/${slug}`)
}
async load(): Promise<TroubleshootingSource[]> {
return [new TroubleshootingSource(this.source, this.path, this.entry)]
}
}
/**
* Search source for a single troubleshooting article.
*
* Each article becomes one indexed page with a single section containing
* the full content. This differs from guide pages which may have multiple
* sections based on headings.
*/
export class TroubleshootingSource extends BaseSource {
type = 'troubleshooting' as const
constructor(
source: string,
path: string,
public entry: ITroubleshootingEntry
) {
super(source, path)
}
async process() {
const { title, topics, keywords } = this.entry.data
const content = this.entry.contentWithoutJsx
// Include title and metadata in checksum so any changes trigger re-indexing.
// This ensures updates to title, topics, or keywords are picked up even if
// the main content hasn't changed.
const checksum = createHash('sha256')
.update(JSON.stringify({ title, topics, keywords, content }))
.digest('base64')
const meta = { title, topics, keywords }
// Troubleshooting articles are single-section pages (no sub-headings indexed).
// We explicitly set slug to undefined so the database's `get_full_content_url`
// function returns the page URL without a fragment (e.g., no trailing `#slug`).
// This is handled in SQL: `CASE WHEN slug IS NULL THEN '' ELSE concat('#', slug) END`
const sections = [
{
heading: title,
slug: undefined as string | undefined,
content: `# ${title}\n${content}`,
},
]
this.checksum = checksum
this.meta = meta
this.sections = sections
return { checksum, meta, sections }
}
/**
* Returns the full article content formatted for full-text search indexing.
* The title is included as a heading to boost its relevance in search results.
*/
extractIndexedContent(): string {
return `# ${this.entry.data.title}\n\n${this.entry.contentWithoutJsx}`
}
}
/**
* Loads all troubleshooting articles from `content/troubleshooting/` directory.
*
* Each MDX file is parsed, validated, and converted to a TroubleshootingLoader.
* Hidden files (prefixed with `_`) are excluded.
*
* @returns Array of loaders, one per troubleshooting article
*/
export async function fetchTroubleshootingSources(): Promise<TroubleshootingLoader[]> {
const entries = (await getAllTroubleshootingEntriesInternal()) as ITroubleshootingEntry[]
return entries.map((entry) => new TroubleshootingLoader('troubleshooting', entry))
}
+1
View File
@@ -16,6 +16,7 @@ enum PageType {
Reference = 'reference',
Integration = 'partner-integration',
GithubDiscussion = 'github-discussions',
Troubleshooting = 'troubleshooting',
}
interface PageSection {
@@ -1,14 +1,14 @@
'use client'
import {
DocsSearchResultType as PageType,
useDocsSearch,
type DocsSearchResult as Page,
type DocsSearchResultSection as PageSection,
DocsSearchResultType as PageType,
useDocsSearch,
} from 'common'
import { Book, ChevronRight, Github, Hash, Loader2, MessageSquare, Search } from 'lucide-react'
import { useEffect, useRef } from 'react'
import { Button, cn, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_ } from 'ui'
import { Button, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, cn } from 'ui'
import { StatusIcon } from 'ui/src/components/StatusIcon'
import {
@@ -83,6 +83,7 @@ const DocsSearchPage = () => {
switch (pageType) {
case PageType.Markdown:
case PageType.Reference:
case PageType.Troubleshooting:
if (BASE_PATH === '/docs') {
router.push(link)
setIsOpen(false)
@@ -299,8 +300,9 @@ export function formatSectionUrl(page: Page, section: PageSection) {
return `${page.path}#${section.slug ?? ''}`
case PageType.Reference:
return `${page.path}/${section.slug ?? ''}`
case PageType.Troubleshooting:
// [Charis] Markdown headings on integrations pages don't have slugs yet
case PageType.Integration:
// [Charis] Markdown headings on integrations pages don't have slugs yet
return page.path
default:
throw new Error(`Unknown page type '${page.type}'`)
@@ -312,6 +314,7 @@ export function getPageIcon(page: Page) {
case PageType.Markdown:
case PageType.Reference:
case PageType.Integration:
case PageType.Troubleshooting:
return <Book strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
case PageType.GithubDiscussion:
return <Github strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
@@ -325,6 +328,7 @@ export function getPageSectionIcon(page: Page) {
case PageType.Markdown:
case PageType.Reference:
case PageType.Integration:
case PageType.Troubleshooting:
return <Hash strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
case PageType.GithubDiscussion:
return <MessageSquare strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />