From ce5cce50302a3be65115140ad154bf15cdcac758 Mon Sep 17 00:00:00 2001
From: Illia Basalaiev <44750366+Ellba@users.noreply.github.com>
Date: Mon, 23 Feb 2026 13:54:40 +0100
Subject: [PATCH] replace github discussions with local guides in the docs
search (#42335)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 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
**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
## 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.
---------
Co-authored-by: Illia Basalaiev
Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
Co-authored-by: Chris Chinchilla
---
.../globalSearch/globalSearchModel.ts | 2 +-
.../scripts/search/generate-embeddings.ts | 3 -
apps/docs/scripts/search/sources/index.ts | 32 ++----
.../scripts/search/sources/troubleshooting.ts | 101 ++++++++++++++++++
packages/common/hooks/useDocsSearch.ts | 1 +
.../prepackaged/DocsSearch/DocsSearchPage.tsx | 12 ++-
6 files changed, 122 insertions(+), 29 deletions(-)
create mode 100644 apps/docs/scripts/search/sources/troubleshooting.ts
diff --git a/apps/docs/resources/globalSearch/globalSearchModel.ts b/apps/docs/resources/globalSearch/globalSearchModel.ts
index 731d0271e2c..0d6b4881f61 100644
--- a/apps/docs/resources/globalSearch/globalSearchModel.ts
+++ b/apps/docs/resources/globalSearch/globalSearchModel.ts
@@ -128,7 +128,7 @@ function createModelFromMatch({
} else {
return null
}
- case 'github-discussions':
+ case 'troubleshooting':
return new TroubleshootingModel({
title: page_title,
href,
diff --git a/apps/docs/scripts/search/generate-embeddings.ts b/apps/docs/scripts/search/generate-embeddings.ts
index 81263f113a5..d7519d2b156 100644
--- a/apps/docs/scripts/search/generate-embeddings.ts
+++ b/apps/docs/scripts/search/generate-embeddings.ts
@@ -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',
diff --git a/apps/docs/scripts/search/sources/index.ts b/apps/docs/scripts/search/sources/index.ts
index 88a1de69c2f..e62f320f371 100644
--- a/apps/docs/scripts/search/sources/index.ts
+++ b/apps/docs/scripts/search/sources/index.ts
@@ -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
+ // 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
}
diff --git a/apps/docs/scripts/search/sources/troubleshooting.ts b/apps/docs/scripts/search/sources/troubleshooting.ts
new file mode 100644
index 00000000000..424ba8aea59
--- /dev/null
+++ b/apps/docs/scripts/search/sources/troubleshooting.ts
@@ -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 {
+ 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 {
+ const entries = (await getAllTroubleshootingEntriesInternal()) as ITroubleshootingEntry[]
+ return entries.map((entry) => new TroubleshootingLoader('troubleshooting', entry))
+}
diff --git a/packages/common/hooks/useDocsSearch.ts b/packages/common/hooks/useDocsSearch.ts
index 1b02bb44779..b64f14b6f90 100644
--- a/packages/common/hooks/useDocsSearch.ts
+++ b/packages/common/hooks/useDocsSearch.ts
@@ -16,6 +16,7 @@ enum PageType {
Reference = 'reference',
Integration = 'partner-integration',
GithubDiscussion = 'github-discussions',
+ Troubleshooting = 'troubleshooting',
}
interface PageSection {
diff --git a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx
index db6b2a8457e..afba52261c1 100644
--- a/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx
+++ b/packages/ui-patterns/src/CommandMenu/prepackaged/DocsSearch/DocsSearchPage.tsx
@@ -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
case PageType.GithubDiscussion:
return
@@ -325,6 +328,7 @@ export function getPageSectionIcon(page: Page) {
case PageType.Markdown:
case PageType.Reference:
case PageType.Integration:
+ case PageType.Troubleshooting:
return
case PageType.GithubDiscussion:
return