Skip to content

Commit 0186ae9

Browse files
committed
fix(shell-docs): unbreak preview build + harden related regressions
Three regressions from the earlier CR Round 1 fix batch + a related miss the same round didn't catch. 1. `components/ai/page-actions.tsx` is `"use client"`; importing `getBaseUrl` from `@/lib/sitemap-helpers` pulled `fs` / `path` / `gray-matter` into the client bundle and broke the build entirely ("Module not found: Can't resolve 'fs'"). The whole point of `getBaseUrl` is the 2-line env-var read + trailing-slash strip — no filesystem work — so inline a `getClientBaseUrl()` helper here with a pointer to the canonical server-side version. `sitemap-helpers.ts` stays untouched so other server-side callers keep their convenience. 2. The same file re-threw caught errors from `fetchMarkdown` / `clipboard.writeText` on the assumption that Fumadocs's `useCopyButton` would treat the rejection as "don't flip the `checked` state". It doesn't — there's no `.catch()` on the internal promise (verified in `fumadocs-ui/dist/utils/use-copy-button.js`), so the throw produced an unhandled rejection (browser console noise + Sentry spam) AND gave the user no visible failure indicator either way. Log and swallow at this layer; a follow-up PR can introduce an explicit error UI if we want "Copy failed" to surface. 3. `.claude/launch.json` routed `shell` to port 3004 by passing `-- --port 3004` to `npm --prefix showcase/shell run dev`. But shell's `dev` script ends with `npx -y concurrently -k -n bundle,next "tsx ... --watch" "next dev"` — the trailing `--port 3004` was parsed by `concurrently`, not `next dev`, so `next dev` still bound 3000 and the original collision with `docs` persisted. Switch to `bash -c "PORT=3004 npm --prefix showcase/shell run dev"` so the env var passes through `concurrently` into `next dev` (which natively reads PORT). Call-site enumeration: - `getClientBaseUrl` (new) — only used inside the same file. No external callers to update. - `getBaseUrl` (untouched in `@/lib/sitemap-helpers`) — server-side callers (sitemap routes, `llms-full.txt` route, `llms.txt` route) unchanged; verified via grep that no `"use client"` file imports it. - `MarkdownCopyButton` — error now logged once via `console.error` and swallowed; the button stays in its idle state. - `.claude/launch.json` `shell` entry — `runtimeExecutable` flipped from `npm` to `bash`; harness reads these as opaque strings.
1 parent 04c6383 commit 0186ae9

2 files changed

Lines changed: 28 additions & 18 deletions

File tree

.claude/launch.json

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,8 @@
1515
},
1616
{
1717
"name": "shell",
18-
"runtimeExecutable": "npm",
19-
"runtimeArgs": [
20-
"--prefix",
21-
"showcase/shell",
22-
"run",
23-
"dev",
24-
"--",
25-
"--port",
26-
"3004"
27-
],
18+
"runtimeExecutable": "bash",
19+
"runtimeArgs": ["-c", "PORT=3004 npm --prefix showcase/shell run dev"],
2820
"port": 3004
2921
},
3022
{

showcase/shell-docs/src/components/ai/page-actions.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
} from "@/components/ui/popover";
1818
import { buttonVariants } from "@/components/ui/button";
1919
import { usePathname } from "fumadocs-core/framework";
20-
import { getBaseUrl } from "@/lib/sitemap-helpers";
2120
import ClaudeIcon from "@/components/icons/claude";
2221
import ClaudeCodeIcon from "@/components/icons/claude-code";
2322
import CodexIcon from "@/components/icons/codex";
@@ -28,6 +27,21 @@ import WindsurfIcon from "@/components/icons/windsurf";
2827
// awaited STRING (not a Promise) to avoid the failed-fetch poisoning
2928
// pattern where a rejected promise gets cached and replayed on every
3029
// subsequent click — see `fetchMarkdown` below.
30+
/**
31+
* Resolve the canonical base URL on the client. Inlined here (instead of
32+
* imported from `@/lib/sitemap-helpers`) because that module also pulls
33+
* in `fs` / `path` / `gray-matter` for sitemap generation — Node-only
34+
* deps that fail the Next.js client bundle when this `"use client"`
35+
* component reaches for them. The env var contract is identical:
36+
* `NEXT_PUBLIC_BASE_URL` (set in production), prod fallback otherwise,
37+
* with trailing slash stripped so callers can concatenate
38+
* `${BASE}${path}` safely.
39+
*/
40+
function getClientBaseUrl(): string {
41+
const raw = process.env.NEXT_PUBLIC_BASE_URL || "https://docs.copilotkit.ai";
42+
return raw.replace(/\/+$/, "");
43+
}
44+
3145
const cache = new Map<string, string>();
3246

3347
/** Fetch the markdown body for a docs URL, caching successful responses
@@ -73,11 +87,15 @@ export function MarkdownCopyButton({
7387
const body = await fetchMarkdown(markdownUrl);
7488
await navigator.clipboard.writeText(body);
7589
} catch (err) {
76-
// Surface for support / on-page diagnostics, but don't flip the
77-
// copied indicator (useCopyButton respects throws by not setting
78-
// `checked`, so the button visually fails-shut).
90+
// Log for support / on-page diagnostics. Do NOT re-throw: Fumadocs's
91+
// `useCopyButton` does not attach a `.catch()` to its internal
92+
// promise, so a throw here becomes an unhandled rejection (browser
93+
// console noise + Sentry spam) without any user-visible feedback
94+
// — the button stays in its idle state either way. Swallowing
95+
// here keeps the failure mode loud-but-bounded; a follow-up PR
96+
// can introduce an explicit error UI state if we want the user
97+
// to see "Copy failed".
7998
console.error("[page-actions] Copy Markdown failed", markdownUrl, err);
80-
throw err;
8199
} finally {
82100
setLoading(false);
83101
}
@@ -128,15 +146,15 @@ export function ViewOptionsPopover({
128146
}) {
129147
const pathname = usePathname();
130148
const items = useMemo(() => {
131-
// Build the absolute URL deterministically from `getBaseUrl()` so
132-
// SSR and the first client render agree. The previous
149+
// Build the absolute URL deterministically from `getClientBaseUrl()`
150+
// so SSR and the first client render agree. The previous
133151
// `typeof window === "undefined" ? pathname : new URL(pathname, ...)`
134152
// branch produced a relative path on the server and an absolute URL
135153
// on the client, causing a React hydration mismatch on every
136154
// popover anchor AND embedding a path-only URL ("Read /quickstart,
137155
// I want to ask...") into the LLM-app deep-link prompt — which the
138156
// target LLM can't resolve.
139-
const pageUrl = `${getBaseUrl()}${pathname}`;
157+
const pageUrl = `${getClientBaseUrl()}${pathname}`;
140158
const q = `Read ${pageUrl}, I want to ask questions about it.`;
141159

142160
return [

0 commit comments

Comments
 (0)