jamdesk 1.1.38 → 1.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/__tests__/integration/init.integration.test.js +14 -11
  2. package/dist/__tests__/integration/init.integration.test.js.map +1 -1
  3. package/dist/__tests__/unit/dev-cache-cleanup.test.d.ts +2 -0
  4. package/dist/__tests__/unit/dev-cache-cleanup.test.d.ts.map +1 -0
  5. package/dist/__tests__/unit/dev-cache-cleanup.test.js +74 -0
  6. package/dist/__tests__/unit/dev-cache-cleanup.test.js.map +1 -0
  7. package/dist/commands/dev.d.ts +8 -0
  8. package/dist/commands/dev.d.ts.map +1 -1
  9. package/dist/commands/dev.js +56 -5
  10. package/dist/commands/dev.js.map +1 -1
  11. package/package.json +1 -1
  12. package/templates/components/callouts.mdx +56 -0
  13. package/templates/components/cards.mdx +80 -0
  14. package/templates/components/steps.mdx +39 -0
  15. package/templates/components/tabs-and-accordions.mdx +65 -0
  16. package/templates/docs.json +21 -0
  17. package/templates/introduction.mdx +40 -10
  18. package/templates/quickstart.mdx +98 -9
  19. package/templates/writing/code-blocks.mdx +80 -0
  20. package/templates/writing/components.mdx +78 -0
  21. package/templates/writing/pages.mdx +59 -0
  22. package/vendored/app/api/chat/[project]/route.ts +2 -2
  23. package/vendored/app/api/docs-search/[project]/search/route.ts +15 -50
  24. package/vendored/app/layout.tsx +4 -4
  25. package/vendored/components/navigation/Sidebar.tsx +9 -4
  26. package/vendored/components/search/SearchModal.tsx +6 -6
  27. package/vendored/lib/language-utils.ts +6 -0
  28. package/vendored/lib/search-client.ts +72 -24
  29. package/vendored/lib/static-file-route.ts +13 -0
  30. package/vendored/scripts/build-search-index.cjs +34 -26
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: Steps
3
+ description: "Break multi-step processes into numbered instructions with Steps and Step components. Each step gets a title and supports rich content."
4
+ ---
5
+
6
+ Use Steps for ordered instructions:
7
+
8
+ <Steps>
9
+ <Step title="Install the CLI">
10
+ ```bash
11
+ npm install -g jamdesk
12
+ ```
13
+ </Step>
14
+ <Step title="Initialize your project">
15
+ ```bash
16
+ jamdesk init
17
+ ```
18
+ </Step>
19
+ <Step title="Start writing">
20
+ Open your project and create `.mdx` files. Each file becomes a page in your docs.
21
+ </Step>
22
+ </Steps>
23
+
24
+ ## Usage
25
+
26
+ ```mdx
27
+ <Steps>
28
+ <Step title="First step">
29
+ Description of what to do.
30
+ </Step>
31
+ <Step title="Second step">
32
+ Next instruction.
33
+ </Step>
34
+ </Steps>
35
+ ```
36
+
37
+ <Tip>
38
+ Steps work well for setup guides, tutorials, and getting-started flows.
39
+ </Tip>
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Tabs & Accordions
3
+ description: "Organize content with Tabs for language or platform variants and AccordionGroup for collapsible FAQ-style sections that save vertical space."
4
+ ---
5
+
6
+ ## Tabs
7
+
8
+ Show content in switchable panels:
9
+
10
+ <Tabs>
11
+ <Tab title="React">
12
+ ```jsx
13
+ function App() {
14
+ return <h1>Hello React</h1>;
15
+ }
16
+ ```
17
+ </Tab>
18
+ <Tab title="Vue">
19
+ ```html
20
+ <template>
21
+ <h1>Hello Vue</h1>
22
+ </template>
23
+ ```
24
+ </Tab>
25
+ <Tab title="Svelte">
26
+ ```html
27
+ <h1>Hello Svelte</h1>
28
+ ```
29
+ </Tab>
30
+ </Tabs>
31
+
32
+ ```mdx
33
+ <Tabs>
34
+ <Tab title="React">
35
+ Content for the React tab.
36
+ </Tab>
37
+ <Tab title="Vue">
38
+ Content for the Vue tab.
39
+ </Tab>
40
+ </Tabs>
41
+ ```
42
+
43
+ ## Accordions
44
+
45
+ Collapsible sections for optional details:
46
+
47
+ <AccordionGroup>
48
+ <Accordion title="What is Jamdesk?">
49
+ Jamdesk is a documentation platform that builds and hosts your docs from MDX files in a GitHub repository.
50
+ </Accordion>
51
+ <Accordion title="How do I deploy?">
52
+ Push to GitHub. Jamdesk builds and deploys automatically on every push.
53
+ </Accordion>
54
+ <Accordion title="Can I use a custom domain?">
55
+ Yes. Configure your custom domain in the Jamdesk dashboard under project settings.
56
+ </Accordion>
57
+ </AccordionGroup>
58
+
59
+ ```mdx
60
+ <AccordionGroup>
61
+ <Accordion title="Question here">
62
+ Answer here.
63
+ </Accordion>
64
+ </AccordionGroup>
65
+ ```
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "{{PROJECT_NAME}}",
3
3
  "theme": "jam",
4
+ "colors": {
5
+ "primary": "#635BFF",
6
+ "light": "#7C75FF",
7
+ "dark": "#4F46E5"
8
+ },
4
9
  "api": {
5
10
  "openapi": [
6
11
  "/openapi/example-api.yaml"
@@ -23,8 +28,24 @@
23
28
  "groups": [
24
29
  {
25
30
  "group": "Getting Started",
31
+ "icon": "rocket",
26
32
  "pages": ["introduction", "quickstart"]
27
33
  },
34
+ {
35
+ "group": "Writing Content",
36
+ "icon": "pen",
37
+ "pages": ["writing/pages", "writing/components", "writing/code-blocks"]
38
+ },
39
+ {
40
+ "group": "Built-In Components",
41
+ "icon": "puzzle-piece",
42
+ "pages": [
43
+ "components/cards",
44
+ "components/callouts",
45
+ "components/tabs-and-accordions",
46
+ "components/steps"
47
+ ]
48
+ },
28
49
  {
29
50
  "group": "API Pages",
30
51
  "icon": "plug",
@@ -1,19 +1,49 @@
1
1
  ---
2
2
  title: Introduction
3
- description: Welcome to your documentation
3
+ description: "Starter project with example pages, components, and API reference. Edit MDX, customize your theme, and ship docs in minutes — preview locally or auto-deploy from GitHub."
4
4
  ---
5
5
 
6
- # Welcome to {{PROJECT_NAME}}
6
+ Welcome to your Jamdesk starter project. Everything you need to ship a polished documentation site is in this repo — edit the MDX files, tweak `docs.json`, and you're live.
7
7
 
8
- This is your new documentation site powered by Jamdesk.
8
+ <Tip>
9
+ **Built-in AI search.** Click the sparkles button in the top-right corner of the header — chat is grounded on this site's pages and works out of the box.
10
+ </Tip>
9
11
 
10
- ## Getting Started
12
+ ## What's included
11
13
 
12
- Edit this file to customize your documentation. Add new MDX files and update `docs.json` to add them to the navigation.
14
+ <Columns cols={2}>
15
+ <Card title="Ready-made pages" icon="file-lines">
16
+ Example pages for getting started, writing content, and API references.
17
+ </Card>
18
+ <Card title="50+ components" icon="puzzle-piece" href="/components/cards">
19
+ Cards, callouts, tabs, steps, code groups, accordions — no imports needed.
20
+ </Card>
21
+ <Card title="OpenAPI rendering" icon="plug" href="/api-reference/openapi-example">
22
+ Auto-generate endpoint pages from a YAML or JSON spec.
23
+ </Card>
24
+ <Card title="Auto-deploy" icon="rocket">
25
+ Push to GitHub and your docs build and deploy in seconds.
26
+ </Card>
27
+ </Columns>
13
28
 
14
- ## Features
29
+ ## A minimal Jamdesk project
15
30
 
16
- - **MDX Support** - Write documentation with React components
17
- - **Hot Reload** - See changes instantly during development
18
- - **Search** - Full-text search built in
19
- - **Themes** - Choose from multiple themes
31
+ ```text
32
+ my-docs/
33
+ ├── docs.json # Site config: theme, colors, navigation
34
+ ├── introduction.mdx # This page
35
+ └── quickstart.mdx # Your getting-started guide
36
+ ```
37
+
38
+ That's all you need. Add more `.mdx` files to add more pages — the file path becomes the URL.
39
+
40
+ ## Next steps
41
+
42
+ <Columns cols={2}>
43
+ <Card title="Quickstart" icon="play" href="/quickstart">
44
+ Edit pages locally, customize, and deploy.
45
+ </Card>
46
+ <Card title="Components" icon="puzzle-piece" href="/components/cards">
47
+ Browse every built-in component with live examples.
48
+ </Card>
49
+ </Columns>
@@ -1,20 +1,109 @@
1
1
  ---
2
2
  title: Quickstart
3
- description: Get up and running in minutes
3
+ description: "Edit MDX files, preview locally with the Jamdesk CLI, then connect a GitHub repo for automatic deploys. Customize colors, branding, and navigation in docs.json."
4
4
  ---
5
5
 
6
- # Quickstart
6
+ Your docs are built from MDX files in this repository. Edit them locally with the CLI, then connect to Jamdesk for automatic builds on every push.
7
7
 
8
- Follow these steps to get started with your documentation.
8
+ ## 1. Preview locally
9
9
 
10
- ## Step 1: Edit docs.json
10
+ Install the Jamdesk CLI:
11
11
 
12
- Configure your documentation settings in `docs.json`.
12
+ <CodeGroup>
13
+ ```bash npm
14
+ npm install -g jamdesk
15
+ ```
13
16
 
14
- ## Step 2: Add Pages
17
+ ```bash brew
18
+ brew install jamdesk/tap/jamdesk
19
+ ```
20
+ </CodeGroup>
15
21
 
16
- Create new `.mdx` files and add them to the navigation in `docs.json`.
22
+ Start the dev server with hot reload:
17
23
 
18
- ## Step 3: Deploy
24
+ ```bash
25
+ jamdesk dev
26
+ ```
19
27
 
20
- Push to your Git repository and your docs will be built automatically.
28
+ Open [http://localhost:3000](http://localhost:3000). Edits to MDX files appear instantly.
29
+
30
+ ## 2. Edit a page
31
+
32
+ Open any `.mdx` file and start writing. MDX supports standard Markdown plus Jamdesk components.
33
+
34
+ ```mdx
35
+ ---
36
+ title: My Page
37
+ description: A brief description for SEO
38
+ ---
39
+
40
+ # Heading
41
+
42
+ Regular markdown works — **bold**, *italic*, `code`, [links](https://example.com).
43
+
44
+ <Tip>
45
+ Jamdesk components like this Tip drop in without imports.
46
+ </Tip>
47
+ ```
48
+
49
+ ## 3. Add a new page
50
+
51
+ <Steps>
52
+ <Step title="Create an MDX file">
53
+ Add a new `.mdx` file anywhere in your project, for example `guides/deployment.mdx`.
54
+ </Step>
55
+ <Step title="Add it to navigation">
56
+ Open `docs.json` and add the page path to the `navigation` section:
57
+
58
+ ```json
59
+ {
60
+ "group": "Guides",
61
+ "pages": ["guides/deployment"]
62
+ }
63
+ ```
64
+ </Step>
65
+ </Steps>
66
+
67
+ ## 4. Customize your site
68
+
69
+ Everything is configured in `docs.json`:
70
+
71
+ | Setting | What it does |
72
+ |---------|-------------|
73
+ | `name` | Site name shown in the header |
74
+ | `colors` | Primary, light, and dark accent colors |
75
+ | `logo` | Light and dark mode logo images |
76
+ | `theme` | Visual theme (`jam`, `nebula`, or `pulsar`) |
77
+ | `navigation` | Sidebar tabs, groups, and page order |
78
+ | `navbar` | Top navigation links and buttons |
79
+
80
+ <Tip>
81
+ See the full configuration reference at [jamdesk.com/docs/config/docs-json-reference](https://jamdesk.com/docs/config/docs-json-reference).
82
+ </Tip>
83
+
84
+ ## 5. Connect GitHub for auto-deploy
85
+
86
+ Once your docs look right locally, hand off building and hosting to Jamdesk:
87
+
88
+ <Steps>
89
+ <Step title="Push your code to GitHub">
90
+ Create a repository and push your project.
91
+ </Step>
92
+ <Step title="Connect on the dashboard">
93
+ Sign in at [dashboard.jamdesk.com](https://dashboard.jamdesk.com), create a project, and connect your repository.
94
+ </Step>
95
+ <Step title="Push changes">
96
+ Every push triggers an automatic build. Your site is live in seconds at `<slug>.jamdesk.app` or your custom domain.
97
+ </Step>
98
+ </Steps>
99
+
100
+ ## What's next
101
+
102
+ <Columns cols={2}>
103
+ <Card title="Components" icon="puzzle-piece" href="/components/cards">
104
+ Cards, callouts, tabs, steps, and more — all ready to use.
105
+ </Card>
106
+ <Card title="API Pages" icon="plug" href="/api-reference/openapi-example">
107
+ Render endpoint pages from an OpenAPI spec, or hand-author with components.
108
+ </Card>
109
+ </Columns>
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: Code Blocks
3
+ description: "Fenced code blocks with automatic syntax highlighting, optional file titles, and CodeGroup for multi-language examples side by side."
4
+ ---
5
+
6
+ Fenced code blocks are automatically syntax-highlighted.
7
+
8
+ ## Basic code block
9
+
10
+ ```javascript
11
+ function greet(name) {
12
+ return `Hello, ${name}!`;
13
+ }
14
+ ```
15
+
16
+ Add a language identifier after the opening fence for syntax highlighting:
17
+
18
+ ````mdx
19
+ ```javascript
20
+ function greet(name) {
21
+ return `Hello, ${name}!`;
22
+ }
23
+ ```
24
+ ````
25
+
26
+ ## With title
27
+
28
+ Add a title after the language:
29
+
30
+ ```javascript server.js
31
+ const express = require('express');
32
+ const app = express();
33
+
34
+ app.get('/', (req, res) => {
35
+ res.send('Hello World');
36
+ });
37
+
38
+ app.listen(3000);
39
+ ```
40
+
41
+ ````mdx
42
+ ```javascript server.js
43
+ const express = require('express');
44
+ const app = express();
45
+ app.listen(3000);
46
+ ```
47
+ ````
48
+
49
+ ## Code groups
50
+
51
+ Show the same example in multiple languages:
52
+
53
+ <CodeGroup>
54
+ ```javascript Node.js
55
+ const response = await fetch('https://api.example.com/data');
56
+ const data = await response.json();
57
+ ```
58
+
59
+ ```python Python
60
+ import requests
61
+ response = requests.get('https://api.example.com/data')
62
+ data = response.json()
63
+ ```
64
+
65
+ ```bash cURL
66
+ curl -X GET https://api.example.com/data
67
+ ```
68
+ </CodeGroup>
69
+
70
+ ````mdx
71
+ <CodeGroup>
72
+ ```javascript Node.js
73
+ const response = await fetch('https://api.example.com/data');
74
+ ```
75
+
76
+ ```python Python
77
+ response = requests.get('https://api.example.com/data')
78
+ ```
79
+ </CodeGroup>
80
+ ````
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: Components
3
+ description: "Choose the right component for the job — when to use Cards, Callouts, Tabs, Steps, and more. Live examples for every component live in the Components tab."
4
+ ---
5
+
6
+ Jamdesk's MDX components are available in every page — no imports needed. This guide helps you pick the right one. For live examples of every component, see the **Components** tab in the sidebar.
7
+
8
+ ## Highlighting information
9
+
10
+ <Note>
11
+ **Note** — Helpful context that enhances understanding.
12
+ </Note>
13
+
14
+ <Info>
15
+ **Info** — Neutral facts or supplementary details.
16
+ </Info>
17
+
18
+ <Tip>
19
+ **Tip** — Best practices and "pro tips."
20
+ </Tip>
21
+
22
+ <Warning>
23
+ **Warning** — Important caveats or requirements.
24
+ </Warning>
25
+
26
+ <Danger>
27
+ **Danger** — Critical warnings (data loss, security).
28
+ </Danger>
29
+
30
+ <Check>
31
+ **Check** — Success confirmations.
32
+ </Check>
33
+
34
+ Use the lightest one that conveys the urgency. Reach for `Warning` and `Danger` sparingly so they keep their weight.
35
+
36
+ ## Linking to other pages
37
+
38
+ | Component | When to use |
39
+ |-----------|-------------|
40
+ | `<Card>` | A single feature or destination — title, icon, optional href. |
41
+ | `<Columns>` | A grid of cards (2, 3, or 4 columns). Wraps multiple `<Card>` children. |
42
+
43
+ ```mdx
44
+ <Columns cols={2}>
45
+ <Card title="Quickstart" icon="rocket" href="/quickstart">
46
+ Get up and running in minutes.
47
+ </Card>
48
+ <Card title="Components" icon="puzzle-piece" href="/components/cards">
49
+ See every built-in component.
50
+ </Card>
51
+ </Columns>
52
+ ```
53
+
54
+ ## Showing alternatives or steps
55
+
56
+ | Component | When to use |
57
+ |-----------|-------------|
58
+ | `<Tabs>` + `<Tab>` | Equal alternatives the reader chooses between (languages, platforms, OSes). |
59
+ | `<AccordionGroup>` + `<Accordion>` | Collapsed-by-default details (FAQs, advanced options). |
60
+ | `<Steps>` + `<Step>` | A linear sequence the reader walks through in order. |
61
+ | `<CodeGroup>` | Multi-language code blocks side by side with shared tab strip. |
62
+
63
+ If readers do all the work, use `<Steps>`. If they pick one path, use `<Tabs>`. If most readers skip it, use `<AccordionGroup>`.
64
+
65
+ ## Documenting APIs
66
+
67
+ | Component | When to use |
68
+ |-----------|-------------|
69
+ | `<ParamField>` | A request parameter (path, query, header, body). |
70
+ | `<ResponseField>` | A response field. |
71
+ | `<RequestExample>` | The request code samples panel. |
72
+ | `<ResponseExample>` | The response payload(s) panel. |
73
+
74
+ See the **API Pages** group for working examples.
75
+
76
+ <Tip>
77
+ For the full component reference (every prop, every variant) see [jamdesk.com/docs/components/overview](https://jamdesk.com/docs/components/overview).
78
+ </Tip>
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Pages
3
+ description: "How MDX files become pages, how frontmatter sets titles and descriptions, and how file paths map to URLs in your documentation site."
4
+ ---
5
+
6
+ Every `.mdx` file in your project becomes a page. The file path determines the URL.
7
+
8
+ ## Frontmatter
9
+
10
+ Each page starts with frontmatter — metadata between `---` fences:
11
+
12
+ ```mdx
13
+ ---
14
+ title: My Page Title
15
+ description: A short description for search engines
16
+ ---
17
+
18
+ Your content starts here.
19
+ ```
20
+
21
+ | Field | Required | Description |
22
+ |-------|----------|-------------|
23
+ | `title` | Yes | Page title shown in the browser tab and sidebar |
24
+ | `description` | No | Meta description for SEO |
25
+
26
+ ## File organization
27
+
28
+ Your file structure maps directly to URLs:
29
+
30
+ ```
31
+ introduction.mdx → /introduction
32
+ quickstart.mdx → /quickstart
33
+ guides/deployment.mdx → /guides/deployment
34
+ api/users/list.mdx → /api/users/list
35
+ ```
36
+
37
+ ## Markdown features
38
+
39
+ MDX supports all standard Markdown:
40
+
41
+ - **Bold**, *italic*, `inline code`, and [links](https://jamdesk.com).
42
+ - Ordered and unordered lists (you're reading one).
43
+ - Fenced code blocks with syntax highlighting.
44
+ - Tables, blockquotes, and headings (`##`, `###`, `####`).
45
+
46
+ Lists, tables, and blockquotes render natively:
47
+
48
+ | Format | Markdown | Renders as |
49
+ |--------|----------|-----------|
50
+ | Bold | `**bold**` | **bold** |
51
+ | Italic | `*italic*` | *italic* |
52
+ | Code | `` `code` `` | `code` |
53
+ | Link | `[text](url)` | [text](https://jamdesk.com) |
54
+
55
+ > Blockquotes pull a passage out of the flow. Use them sparingly.
56
+
57
+ <Note>
58
+ Headings on the page automatically appear in the right sidebar as a table of contents.
59
+ </Note>
@@ -28,6 +28,7 @@ import { getDocsPath, getBaseUrl, trackChatAnalytics } from '@/lib/route-helpers
28
28
  import { getAnthropicClient } from '@/lib/anthropic-client';
29
29
  import { rewriteQueryForSearch } from '@/lib/query-rewriter';
30
30
  import { fetchDocsConfig } from '@/lib/r2-content';
31
+ import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
31
32
  import { redis } from '@/lib/redis';
32
33
  import { CHAT_TOOLS } from '@/lib/chat-tools';
33
34
  import { rewriteSlugLinks } from '@/lib/link-rewriter';
@@ -44,7 +45,6 @@ const MAX_HISTORY = 10;
44
45
  * Used both for searchQuery enrichment and to skip the rewriter. */
45
46
  const SHORT_FOLLOWUP_LEN = 60;
46
47
  const VALID_ROLES = new Set<string>(['user', 'assistant']);
47
- const VALID_LOCALE_RE = /^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/;
48
48
 
49
49
  export async function POST(
50
50
  request: NextRequest,
@@ -92,7 +92,7 @@ export async function POST(
92
92
 
93
93
  let clientLocale: string | undefined;
94
94
  if (body.locale !== undefined) {
95
- if (typeof body.locale !== 'string' || !VALID_LOCALE_RE.test(body.locale)) {
95
+ if (typeof body.locale !== 'string' || !BCP47_LANGUAGE_RE.test(body.locale)) {
96
96
  return Response.json({ error: 'Invalid locale' }, { status: 400 });
97
97
  }
98
98
  clientLocale = body.locale;
@@ -22,6 +22,7 @@ import { getBaseUrl, trackServerAnalytics } from '@/lib/route-helpers';
22
22
  import { redis } from '@/lib/redis';
23
23
  import { fetchDocsConfig } from '@/lib/r2-content';
24
24
  import { isMultiLanguageConfig } from '@/lib/navigation-utils';
25
+ import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
25
26
  import { log } from '@/lib/logger';
26
27
 
27
28
  export const runtime = 'nodejs';
@@ -37,10 +38,6 @@ const MAX_LIMIT = 20;
37
38
  const DEFAULT_LIMIT = 5;
38
39
  const MAX_QUERY_LENGTH = 500;
39
40
  const RATE_LIMIT_PER_MIN = 60;
40
- /** BCP-47-ish: 2-3 letter primary tag, optional 2-4 letter region/script.
41
- * Matches the chat endpoint's VALID_LOCALE_RE — keep both contracts in sync.
42
- * Limitation: 3-segment tags like `zh-Hant-HK` are rejected. Documented. */
43
- const VALID_LANGUAGE_RE = /^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/;
44
41
  const DEFAULT_LANGUAGE = 'en';
45
42
 
46
43
  export async function OPTIONS(_request: NextRequest) {
@@ -109,21 +106,17 @@ export async function POST(
109
106
 
110
107
  const { query, limit: rawLimit } = body;
111
108
 
112
- // Validate language. `null` and `undefined` both default to 'en'
113
- // (real-world clients send `language: null` from `?? null` fallbacks
114
- // rejecting them as 400 would be gratuitous). A non-string value or a
115
- // string that doesn't match the BCP-47 pattern is a 400.
109
+ // `null` is treated as omitted: real-world clients emit `language: null`
110
+ // from `?? null` fallbacks, and rejecting them would be gratuitous.
116
111
  let language: string = DEFAULT_LANGUAGE;
117
- let languageWasExplicit = false;
118
112
  if (body.language != null) {
119
- if (typeof body.language !== 'string' || !VALID_LANGUAGE_RE.test(body.language)) {
113
+ if (typeof body.language !== 'string' || !BCP47_LANGUAGE_RE.test(body.language)) {
120
114
  return NextResponse.json(
121
115
  { error: 'Invalid language code' },
122
116
  { status: 400, headers: CORS_HEADERS },
123
117
  );
124
118
  }
125
119
  language = body.language;
126
- languageWasExplicit = true;
127
120
  }
128
121
 
129
122
  if (!query || typeof query !== 'string' || query.trim().length === 0) {
@@ -154,47 +147,19 @@ export async function POST(
154
147
  // Apply the locale filter only when the project is configured for
155
148
  // multiple languages AND the per-project kill switch is not set.
156
149
  // Single-language projects' chunks have no locale metadata, so the
157
- // strict filter would return zero. Run the config fetch and kill-switch
158
- // read in parallel fetchDocsConfig has a 5-min in-memory cache, and
159
- // Redis is the bottleneck only on a cold cache. .catch() on the kill
160
- // switch keeps a Redis blip from breaking searches.
161
- const killSwitchPromise: Promise<boolean> = redis
162
- ? redis.get(`searchLocaleFilter:${project}`)
163
- .then((v) => v === 'disabled')
164
- .catch(() => false)
165
- : Promise.resolve(false);
166
-
167
- let config: Awaited<ReturnType<typeof fetchDocsConfig>>;
168
- let killSwitch: boolean;
169
- try {
170
- [config, killSwitch] = await Promise.all([
171
- fetchDocsConfig(project),
172
- killSwitchPromise,
173
- ]);
174
- } catch (err) {
175
- // R2 outage — config unavailable. Asymmetric fail-mode:
176
- // - Caller sent an explicit language: 503 (don't silently leak
177
- // cross-language results to a caller who asked for filtering).
178
- // - Caller defaulted to 'en': fall back to today's no-filter behavior
179
- // so docs sites keep working through R2 incidents.
180
- log('error', 'docs-search: fetchDocsConfig failed', {
181
- project,
182
- error: String(err),
183
- languageWasExplicit,
184
- });
185
- if (languageWasExplicit) {
186
- return NextResponse.json(
187
- { error: 'Search temporarily unavailable' },
188
- { status: 503, headers: CORS_HEADERS },
189
- );
190
- }
191
- config = null;
192
- killSwitch = await killSwitchPromise.catch(() => false);
193
- }
150
+ // strict filter would return zero. Both reads tolerate failure (R2
151
+ // outage or Redis blip) by falling back to "filter disabled" — same
152
+ // pattern the chat endpoint uses.
153
+ const [config, killSwitch] = await Promise.all([
154
+ fetchDocsConfig(project).catch(() => null),
155
+ redis
156
+ ? redis.get(`searchLocaleFilter:${project}`)
157
+ .then((v) => v === 'disabled')
158
+ .catch(() => false)
159
+ : Promise.resolve(false),
160
+ ]);
194
161
 
195
162
  if (killSwitch) {
196
- // Operators have flipped the kill switch for this project — log so we
197
- // can spot escapes. Not an error, but worth surfacing.
198
163
  log('warn', 'docs-search: locale filter kill switch is enabled', {
199
164
  project,
200
165
  });