Commit Graph

356 Commits

Author SHA1 Message Date
Jordan Ritter 39027610bf fix(showcase/shell-docs): Card renders icon, className, children instead of dropping them
Card's type accepted icon, className, and children but the component
body never read them — silent prop drop. Render icon above the title,
merge className onto the wrapper, and render children below the
description so MDX authors get the behavior the type signature
promises.
2026-04-20 17:16:40 -07:00
Jordan Ritter 72ca49d4b2 fix(showcase/shell-docs): layout reserved-slug collision logs in production too 2026-04-20 17:16:11 -07:00
Jordan Ritter 0bab312efe fix(showcase/shell-docs): middleware cookie secure only in prod; surface posthog http errors 2026-04-20 17:16:02 -07:00
Jordan Ritter 1964255193 fix(showcase/shell-docs): single Callout implementation; remove mdx-components duplicate
Two Callout components coexisted: a restricted variant in
mdx-components.tsx (info | warn | error only) and the broader-surface
variant in docs-callout.tsx (info | tip | warn | warning | error |
danger | note). reference/[...slug]/page.tsx imported from
mdx-components, silently limiting authors to three types. Collapse to
a single implementation by re-exporting docs-callout's Callout from
mdx-components so existing import paths keep working.
2026-04-20 17:15:55 -07:00
Jordan Ritter 5cdcda816f fix(showcase/shell-docs): MDX Link renders next/link for client-side nav
The registry's <Link> shim rendered a plain <a>, forcing a full page
reload on every internal MDX link. next/link was already imported in
this file; now the shim routes through it when href is present, falling
back to a bare <a> only when no href is provided.
2026-04-20 17:15:49 -07:00
Jordan Ritter 2f665fa533 fix(showcase/shell-docs): ag-ui page strips leading H1 to prevent duplicate title render
The page wrapper renders the extracted title inside its own <h1>.
When no frontmatter title is present the title comes from the MDX
body's first H1 — which MDXRemote then also renders, producing two
stacked h1s at the top of every ag-ui doc. Strip the first leading
`# …` line (after any blank lines) from the MDX content before
rendering so the wrapper provides the single h1 and the body flows
from the first paragraph.
2026-04-20 17:15:46 -07:00
Jordan Ritter b6734accb2 fix(showcase/shell-docs): middleware matcher excludes static assets + handles Sec-Purpose prefetch 2026-04-20 17:15:32 -07:00
Jordan Ritter c06c18de68 refactor(showcase/shell-docs): consumers import FRAMEWORK_CATEGORY_ORDER from docs-render
Both sidebar-framework-selector.tsx and the catch-all docs page.tsx
kept local copies of FRAMEWORK_CATEGORY_ORDER that mirrored the
canonical one exported from docs-render. Drift between the copies
would have shown up as divergent category ordering across the UI.
Remove the local duplicates and import the single source of truth.
2026-04-20 17:15:16 -07:00
Jordan Ritter a6dc0a720f fix(showcase/shell-docs): convertTablesInJSX skips nested same-tag containers
The non-greedy regex matches through the first same-family close tag,
so nested containers like <Card>outer <Card>inner</Card> rest</Card>
closed at the inner </Card> and left the remaining 'rest</Card>' as
literal text. Capture the opening tag name and bail when the inner
body contains another occurrence of it — renders correctly through
MDX's own JSX handling instead of producing broken markup.
2026-04-20 17:14:50 -07:00
Jordan Ritter 473ba4d2bf fix(showcase/shell-docs): CopyButton clears pending reset timer on unmount 2026-04-20 17:13:17 -07:00
Jordan Ritter 621b00b972 fix(showcase/shell-docs): search-modal guards Enter against IME composition; supports mailto/tel schemes 2026-04-20 17:13:13 -07:00
Jordan Ritter 6edd8836ad fix(showcase/shell-docs): docs-render CRLF-safe frontmatter + fence marker stored fully
Accept \r?\n in extractFrontmatter so Windows-authored MDX files
don't silently skip frontmatter extraction. In stripLeadingImports,
store the full fence marker (``` or ~~~) rather than its first
character so a stray single backtick inside a fenced block doesn't
prematurely close it.
2026-04-20 17:13:09 -07:00
Jordan Ritter cfc47fb896 fix(showcase/shell-docs): loadDoc uses gray-matter; guarded IO in docs-render readers
Replace the hand-rolled frontmatter regex in loadDoc with gray-matter
(already a dep) so quoted values, folded YAML, multiline descriptions,
and malformed frontmatter no longer fall through silently. Wrap every
fs read in readTitle, loadDoc, inlineSnippets, and buildNavTreeFromFilesystem
in try/catch — a single unreadable file / permission error used to
crash the entire page render.
2026-04-20 17:12:50 -07:00
Jordan Ritter 7b5fb016ef refactor(showcase/shell-docs): hoist table helpers to satisfy oxlint
Move parseTableRow, isTableRow, isTableSeparator to module scope
so they aren't recreated on every convertTablesInJSX call. Fixes
eslint-plugin-unicorn(consistent-function-scoping) warnings. No
behavior change.
2026-04-20 16:55:35 -07:00
Jordan Ritter ac81e10deb chore(showcase/shell-docs): audit SNIPPET_MAP aliases; export FRAMEWORK_CATEGORY_ORDER
- Document every SNIPPET_MAP alias inline (AgUI/AGUI,
  FrontendTools/FrontEndToolsImpl) so future maintainers can see why
  the duplicates exist and don't collapse them blindly. Confirmed
  FrontEndToolsImpl is still referenced from live MDX under
  integrations/langgraph/*.
- Verified every SNIPPET_MAP target exists on disk with matching
  casing (including migrate-to-1.10.X.mdx and migrate-to-1.8.2.mdx).
- Export FRAMEWORK_CATEGORY_ORDER (+ FrameworkCategory type) from
  docs-render as the single source of truth. Consumer files
  (sidebar-framework-selector.tsx, [[...slug]]/page.tsx) each define
  the same constant independently today; a follow-up agent can
  retire those duplicates once they pick up the import.
2026-04-20 16:53:34 -07:00
Jordan Ritter 9500518203 fix(showcase/shell-docs): error boundaries log full error + generic copy + reportable id
- Log the full Error instance (not just .message/.digest) so the stack
  trace actually reaches the server log / browser devtools.
- Include the pathname (via usePathname) in the log message so reports
  can be tied back to the page that crashed.
- Replace misleading migration-specific copy ("This page will be fixed
  shortly. Some ... components are still being migrated.") with a
  generic user-facing message — the error boundary catches errors from
  many causes, not just migration gaps.
- Show error.digest explicitly as a reportable "Error ID" so users can
  include it when reporting the issue; drop the fallback to
  error.message because it can leak internal details in a user-facing
  surface.
2026-04-20 16:53:32 -07:00
Jordan Ritter a21efbdcc6 refactor(showcase/shell-docs): reference pages share loadItems helper + recursive static params + guarded reads
- Extract duplicated loadItems/getAllItems into @/lib/reference-items so the
  /reference index page and the /reference/[...slug] page read the same
  tree the same way (same subdirs, same walker, same gray-matter path,
  same caching).
- Walker is now recursive: subfolder files like components/inputs/textarea.mdx
  are indexed and statically generated. Previously only top-level .mdx
  files under components/ and hooks/ were picked up.
- Wrap gray-matter and fs reads in try/catch per file: a single malformed
  frontmatter block no longer crashes the whole static-generation pass —
  we log the offending path and skip that file.
- Fence-aware import stripper in the slug page so code samples containing
  import ... lines inside fences are not corrupted. (Duplicated inline
  here and in ag-ui page with a TODO(dedup) marker pointing at a future
  shared helper in @/lib/docs-render.)
- loadReferenceItems memoizes in production (module-scope cache keyed by
  subdir). Dev bypasses the cache so edits show up without a server
  restart.
2026-04-20 16:53:03 -07:00
Jordan Ritter 6bd81286bf perf(showcase/shell-docs): memoize readTitle and readMeta
buildNavTree walks the entire content tree on every page render and
calls readTitle / readMeta O(pages) times per request. Each call
previously reopened and re-parsed the file from disk.

- Process-scoped Map caches keyed by resolved absolute path.
- Null results are cached too — a missing or malformed file still
  short-circuits on the second visit.
- Memory footprint is trivial: titles are short strings, meta is a
  small JSON object.
- Stale-during-process semantics are acceptable for Next.js build
  and server runtime lifetimes; authors re-run the process to pick
  up MDX/meta.json edits (same as before in production builds).
2026-04-20 16:52:01 -07:00
Jordan Ritter cdb35b17ba fix(showcase/shell-docs): ag-ui page reuses docsComponents + gray-matter + guarded fs reads
- Replace local MDX components map with spread of shared docsComponents
  from @/lib/mdx-registry, so AG-UI pages get the full component set
  (Tabs, FrameworkTabs, Snippet, Steps from @/components/docs-steps,
  etc.) instead of silently rendering raw JSX when MDX uses anything
  outside the tiny local subset.
- Use the shared InlineDemo from mdx-registry, whose Open full demo
  link uses an absolute NEXT_PUBLIC_SHELL_URL/integrations/... URL.
  The prior local copy used a relative /integrations/... URL that 404d
  on the docs host (no /integrations route on docs.showcase.copilotkit.ai).
- Parse frontmatter with gray-matter instead of a whole-file title:
  regex that could match any title: line buried inside an MDX body.
- Wrap fs.readFileSync in try/catch with meaningful error logs naming
  the offending path; fall back gracefully instead of crashing render.
- Fence-aware leading-import stripper so MDX code samples that show
  import ... lines are not corrupted (duplicated here; TODO(dedup) in
  comment to hoist into @/lib/docs-render next time another route needs
  it).
2026-04-20 16:51:46 -07:00
Jordan Ritter 50a6488363 refactor(showcase/shell-docs): broaden JSX table containers; accept outer-pipe-optional GFM; log readMeta parse errors
- JSX_CONTAINER_TAGS now covers Callout, Card, Cards, Step, Steps,
  Tabs alongside the original Accordion/Tab. Previously a Markdown
  table inside e.g. <Callout> silently failed to promote to HTML
  and rendered as raw pipes.
- convertMarkdownTableToHtml's parseRow and the row/separator
  detection in convertTablesInJSX now accept GFM tables written
  WITHOUT outer leading/trailing pipes (valid GFM — previously
  rejected because we sliced (1, -1) unconditionally).
- readMeta now logs the offending path + parse error when JSON.parse
  fails, replacing the silent catch. Malformed meta.json no longer
  collapses to 'no nav ordering' with zero diagnostic signal.
2026-04-20 16:51:30 -07:00
Jordan Ritter dd72890693 fix(showcase/shell-docs): framework-selector handles /docs/* clear + router.replace + strict strip
- Clear selection button was hidden on /docs/* because it branched on
  framework (URL-derived, always null there). A user landing on /docs
  with a stale storedFramework from a previous session had no way to
  clear the preference from the selector UI. Show the button whenever
  framework OR storedFramework is set; skip the navigation when we're
  not on a framework-scoped route (it's a pure preference change).
- router.push -> router.replace on framework change. Picking a backend
  is a pivot on the same logical page, not forward navigation. Using
  push clutters the back stack with every framework the user clicked
  through, making the browser Back button useless.
- Type-guard e.target with instanceof Node before passing to contains,
  rather than hard-casting. EventTarget can be non-DOM (e.g. events
  against window); the cast silently degrades to contains(null) which
  returns false in most engines but is undefined behaviour in spec.
- Document that stripFrameworkPrefix intentionally only inspects
  parts[0] and does NOT recurse — /<fw>/<fw>/x must keep the inner
  <fw>/x as the feature tail.
2026-04-20 16:51:02 -07:00
Jordan Ritter 0e29c78a0d refactor(showcase/shell-docs): dedupe FRAMEWORK_CATEGORY_ORDER + stable sort by slug
- Rename INTEGRATION_CATEGORY_IDS to FRAMEWORK_CATEGORY_ORDER to match
  the identically-defined constant in /[[...slug]]/page.tsx (the two
  will be consolidated into @/lib/registry in a follow-up owned by the
  registry refactor; left a TODO pointing at the canonical home).
- sort_order tiebreak: Array#sort is not guaranteed stable for ties
  across engines. When multiple integrations default to 999 the
  rendered order shuffles between V8 revisions. Add an explicit
  alphabetical-by-slug tiebreak so the rendered panel is
  deterministic.
2026-04-20 16:49:36 -07:00
Jordan Ritter 4940ffb9ef chore(showcase/shell-docs): sidebar-link drop dead fallbackHref path
fallbackHref was assigned to href and then immediately overwritten in
every branch of the scope switch — the value was never read. Since
useFramework() returns the URL-derived framework identically during
SSR and post-hydration, the resolved href matches what fallbackHref
was pre-computing server-side, so the 'client takes over on mount'
comment was describing a no-op.

- Remove the dead assignment; resolve href directly from framework.
- Keep fallbackHref as an optional prop (ignored) to avoid churning
  call sites that still pass it.
2026-04-20 16:49:28 -07:00
Jordan Ritter 7713d6b531 fix(showcase/shell-docs): sidebar-nav scrolls only on route change; try/catch + spec-value behavior
The effect had no dependency array, so it re-ran on every render and
hijacked the user's own sidebar scroll whenever any unrelated re-render
fired (state/context flips, parent rerenders). Key the effect on
pathname so scroll-to-active only runs on route change.

Also:
- behavior: 'instant' is a Chromium non-standard extension; other
  engines silently ignore it. Use 'auto' which is the spec-compliant
  'no smooth animation' value.
- Wrap scrollIntoView in try/catch — it can throw on detached nodes
  and in obscure iframe/security contexts; a sidebar scroll
  restoration never justifies taking down the route.
2026-04-20 16:49:21 -07:00
Jordan Ritter 484556e41d Revert "chore(showcase/shell-docs): mdx-registry stubs log discarded props in dev"
This reverts commit 0f46be9c37.
2026-04-20 16:49:11 -07:00
Jordan Ritter c54c37c3e9 fix(showcase/shell-docs): fence-aware import stripping + cycle protection + missing-snippet logging
- Replace the global /^import\s+.+$/gm pass with a fence-aware walker
  (stripLeadingImports). The old regex silently mangled docs code
  samples like 'import os' inside Python fences; the new pass only
  drops top-of-file MDX import lines and leaves fence bodies alone.
- Add Set<string> cycle tracking through inlineSnippets' recursion so
  a self- or mutually-referencing snippet can't loop until the stack
  overflows. On a repeat, emit a warning and inline a comment marker
  so the author sees something diagnosable.
- Log warn when a <Component /> reference can't be mapped to a
  snippet file (either missing from SNIPPET_MAP or missing on disk);
  previously the reference silently rendered nothing.
2026-04-20 16:48:38 -07:00
Jordan Ritter 3338f0f82f fix(showcase/shell-docs): search trigger avoids hydration mismatch + respects input focus
- isMac now starts as null and is only resolved in useEffect, so SSR output matches the first client render; reserve horizontal space on the shortcut pill with a non-breaking-space placeholder (+ suppressHydrationWarning) so ⌘K/Ctrl+K swap-in doesn't reflow the button
- Cmd/Ctrl+K no longer hijacks the browser shortcut when focus is inside an unrelated <input>, <textarea>, <select>, or contenteditable; still toggles when focus is within the search modal itself (data-search-modal boundary)
- Trigger buttons use setOpen((prev) => !prev) so Cmd+K-on-top-of-existing-modal toggles correctly and the hint reflects the true action
2026-04-20 16:48:11 -07:00
Jordan Ritter 3cd0aa53e4 fix(showcase/shell-docs): search modal types, load error, cleanup, dedupe, split loading states
- Replace useState<any> for registryData with typed Registry | null
- Surface registry-load failures: .catch logs + inline "Search index failed to load" banner
- Track the setTimeout focus id and clear it in effect cleanup
- Let static search-index matches render immediately; show a '[loading...]' hint until registry.json resolves; show 'no results' only after loading completes
- Use a ref for selectedIndex so Enter never reads a stale value after reset-on-input
- Dedupe results by type+href before slicing to 12 (stable key becomes type-href)
- Route navigation through a shared helper that detects external URLs (http(s)://, //) and uses window.location.assign for them, router.push otherwise
- Warn once in dev when an integration has no description so it gets fixed upstream
2026-04-20 16:48:02 -07:00
Jordan Ritter 0f46be9c37 chore(showcase/shell-docs): mdx-registry stubs log discarded props in dev
Add three dev-mode visibility affordances so MDX authors learn when
their embeds render nothing or when stub components silently discard
props:

- stub(name) helper wraps children-only shims and warns once per
  (component, propKeys) pair when non-children props are passed.
  Applied to Inspector and MCPApps as representative cases.
- warnSilentNull() + visible dev-mode fallback for FeatureIntegrations
  when no deployed integrations support the requested feature.
- InlineDemo now warns (missing props, unknown slug, or not-deployed)
  so authors see why their live demo didn't appear.
- Snippet base stub now emits a visible '[Snippet] runtime override
  required' placeholder in dev so missing DocsPageView override is
  obvious.

All warnings are gated on NODE_ENV !== 'production' and deduped so
HMR re-renders don't spam the console.
2026-04-20 16:48:01 -07:00
Jordan Ritter 5492fab195 fix(showcase/shell-docs): guard MDX filesystem reads against path traversal
Slug paths reaching loadDoc and buildBreadcrumbs come straight from
URL segments. Before this change, path.join(CONTENT_DIR, slugPath)
would silently escape CONTENT_DIR when slugPath contained '..'
segments (e.g. /docs/..%2F..%2Fsecrets), letting an attacker read
arbitrary files the server process has access to.

- Add a tiny safe-fs helper (resolveWithinDir / safeExistsSync /
  safeReadFileSync) that resolves a relative path under a declared
  base and returns null when the resolved path escapes.
- Route loadDoc and buildBreadcrumbs through resolveWithinDir so any
  traversal attempt collapses to a clean 'not found' fall-through.
- Apply the same guard to inlineSnippets' SNIPPETS_DIR reads for
  defense-in-depth, even though its inputs are currently internal.
2026-04-20 16:47:53 -07:00
Jordan Ritter 7d0b77f8a9 fix(showcase/shell-docs): copy-button surfaces clipboard failure
- Replace the silent empty `catch {}` with a proper error state:
  clipboard write failures now set state to "error", surface a
  "Copy blocked" label for 2s, and log the original error so devs
  can debug (permissions, insecure context, etc.) instead of the
  button failing invisibly.
- Collapse the two booleans into a single `CopyState` so idle/copied/
  error styling and aria-label stay in sync.
2026-04-20 16:47:37 -07:00
Jordan Ritter f119bbede1 fix(showcase/shell-docs): snippet validates reg.code shape + logs unknown langs
- Guard `reg.code` as a string before rendering; malformed
  `demo-content.json` (missing `code`, stale bundle) now produces a
  visible WarningBox instead of crashing React on `undefined`.
- `resolveHljsLanguage` warns once per unknown language via a
  module-scope Set, so authors learn the right names to use and stop
  relying on hljs.highlightAuto guessing wrong.
2026-04-20 16:47:23 -07:00
Jordan Ritter bcbc8276f8 fix(showcase/shell-docs): snippet highlight error logging + fallback class + range clamping
- hljs try/catch now logs key/file/language + original error instead of
  swallowing silently, so authors can spot bad snippets.
- When highlighting fails, drop the `hljs language-*` class so the
  escaped plain-text fallback isn't styled as though it were highlighted.
- Defense-in-depth non-string hljs return guard.
- `regionFromFile` normalizes by stripping a single trailing newline
  once, then splits — trailing-newline shape is consistent whether or
  not `lines=` is supplied, so CopyButton text is predictable.
- Clamp `end` via `effectiveEnd = Math.min(end, allLines.length)` and
  warn when an explicit range drifts past the file.
- Move the `WarningMessage` interface up next to the other type decls.
2026-04-20 16:46:23 -07:00
Jordan Ritter 6a292151b5 fix(showcase/shell-docs): escape table cell HTML + restrict frontmatter regexes to fm block
- Escape every interpolated value in convertMarkdownTableToHtml to
  close an XSS vector: MDX table cells with HTML-significant characters
  (e.g. <script>, &) previously landed raw in the generated markup.
- Scope readTitle and loadDoc title/description lookups to the
  frontmatter block so a 'title:' / 'description:' that happens to
  appear inside the MDX body (code samples, example config) can't
  override the real frontmatter.
- Export a small escapeHtml helper for reuse within this module;
  duplicated rather than imported from snippet.tsx to keep
  docs-render server-only.
2026-04-20 16:46:06 -07:00
Jordan Ritter 48329bb8e0 fix(showcase/shell-docs): mdx img/video accept user className; Button default type=button
The img and video MDX shims spread props then overrode className to
undefined, silently dropping any className from authors' MDX. Remove the
bogus override so user-supplied className flows through.

Button had no type attribute, defaulting to type="submit" inside a
form (unintentional submits). Default to type="button", accept and
forward onClick/disabled/aria-label, and emit a dev-mode warning when
rendered without onClick so MDX authors learn their Button is
non-interactive.
2026-04-20 16:45:42 -07:00
Jordan Ritter 87b2bb0250 fix(showcase/shell-docs): add iframe sandbox + a11y title on IframeSwitcher & YouTubeVideo
IframeSwitcher had no sandbox attribute, letting user-controlled MDX embed
malicious-origin iframes with full browser capabilities. Mirror the
InlineDemo sandbox policy (scripts/same-origin/forms/popups) and add an
a11y title.

YouTubeVideo was missing both a title (screen readers announced only the
URL) and a sandbox policy. Add sandbox tuned for YouTube embeds
(scripts/same-origin/presentation/popups) and title fallback.
2026-04-20 16:45:21 -07:00
Jordan Ritter 40aa676e8c chore(showcase/shell-docs): bump tsconfig target to ES2022
React 19 / Next 15 expects modern runtimes; ES2017 is stale. ES2022 gives
us top-level await, class fields, and error cause without needing additional
helpers. `next build` still passes.
2026-04-20 16:42:08 -07:00
Jordan Ritter 1db5e4047e chore(showcase/shell-docs): engines, start port, drop unused deps
- Add `engines.node >=18.18` to match Next.js 15 runtime requirements.
- Align `npm run start` with the dev port (3003) for local parity; the
  Docker runner sets PORT=10000 via ENV and is unaffected.
- Drop unused dependencies that are not imported anywhere in shell-docs:
  `react-markdown`, `react-syntax-highlighter`, `rehype-raw`, and
  `@types/react-syntax-highlighter`. These are used in showcase/shell/
  but not here; removing them keeps the dependency surface honest.
- Regenerate package-lock.json to match.
2026-04-20 16:42:03 -07:00
Jordan Ritter 0da65ecbce chore(showcase/shell-docs): .gitignore parity + env secret ignores
Add `.env*.local` and `*.log` entries so accidental secrets and debug
logs never land in the tree. Matches the pattern used by showcase/shell.
Leave the generated /src/data/*.json files tracked, as they are today.
2026-04-20 16:41:56 -07:00
Jordan Ritter e2d5742dfd fix(showcase/shell-docs): Dockerfile hygiene (ARG scope, npm ci, .dockerignore, direct next binary)
- Re-declare COMMIT_SHA and BRANCH ARGs in the runner stage so ENV
  interpolation resolves (ARGs do not cross stages in multi-stage builds).
- Declare NEXT_PUBLIC_BASE_URL as a build-time ARG/ENV so CI can pass it
  via --build-arg for the production `next build` (next.config.ts requires it).
- Copy lockfile for shell-docs and switch to `npm ci` for reproducible installs.
  scripts/ has no committed lockfile yet, so keep `npm install` there.
- Add .dockerignore so local .next/, node_modules/, .git, logs, and .env*
  are not slurped into the build context.
- Invoke next via node_modules/.bin/next (both build and CMD) instead of
  npx to avoid runtime network-fallback risk.
- Document the required build context at the top of the Dockerfile.
- Note the libc6-compat discussion: the slim base is Debian (glibc), so
  no compat shim is required; note how to enable it if we ever move to
  node:20-alpine.
2026-04-20 16:41:51 -07:00
Jordan Ritter 9ed2f9fb37 fix(showcase/shell-docs): StoredFrameworkHighlight reads storedFramework (feature was broken on /docs/*)
The component's name, comments, and stated purpose are to highlight the
user's remembered framework pick on the docs root pivot. The code was
keying off 'framework' (strictly URL-derived), which is always null on
/docs/* where this component is actually mounted — the 'Your choice'
badge and accent ring NEVER rendered.

Now reads 'storedFramework' (the remembered advisory preference) and
compares it against the card's slug, which is what the rest of the file
already documents it doing.
2026-04-20 16:38:20 -07:00
Jordan Ritter 4c73052c1d fix(showcase/shell-docs): RouterPivot redirects using storedFramework; aligned supported/unsupported deployed filter
Functional regressions in the docs router pivot:

1. The redirect effect was reading 'framework' (URL-derived) only. On
   /docs/<feature> the URL prefix 'docs' is never in knownFrameworks, so
   'framework' is always null and the effect never fired. The whole
   'picked-once, sticks' feature was silently broken — users with a
   stored preference were never redirected. Now prefers URL framework,
   falling back to storedFramework, as a single 'target' signal.

2. The 'Loading <framework> view...' placeholder was unreachable for the
   same reason. Now renders when 'target' is set.

3. The supported/unsupported filters disagreed on whether 'deployed'
   applied. supported = withCell (ignored deployed); unsupported =
   !withCell && deployed. Now both require deployed; undeployed
   integrations are omitted entirely rather than surfacing dead links.

4. FrameworkGuardedContent previously returned null whenever 'framework'
   was null, which on /docs/* is always. The MDX body was permanently
   hidden. Now it hides only while a redirect is imminent (URL framework
   set, or storedFramework set — both cases are about to navigate away);
   users with no stored preference see the pivot alongside the MDX body.
2026-04-20 16:38:14 -07:00
Jordan Ritter a2565ec87a fix(showcase/shell-docs): dark-mode-aware highlight.js theme import + layer ordering
- Move highlight.js @import ahead of @import "tailwindcss" so Tailwind
  utility layers can still override specific hljs selectors when needed
  rather than being silently shadowed in Tailwind v4's layer ordering.
- Scope the dark-dimmed theme via prefers-color-scheme so it only
  paints when the user's system is in dark mode. shell-docs does not
  yet ship a dark theme, but this makes syntax blocks correct the
  moment it does.
2026-04-20 16:38:05 -07:00
Jordan Ritter de5301be50 fix(showcase/shell-docs): throw on useFramework outside provider; log localStorage errors; validate slugs
- useFramework() now throws when called outside FrameworkProvider instead
  of returning a silent no-op context. Silent fallback was masking wiring
  bugs where components rendered outside the provider would report no
  framework forever.
- readStoredFramework / writeStoredFramework now log once per session on
  failure (guarded by module-scope flags) instead of swallowing errors
  entirely. localStorage may still be unavailable (SSR, private mode),
  but devs get a warning the first time.
- Dropped 'stored' from the URL-persist effect's deps. It was causing
  redundant re-check cycles every time we called setStored from within.
  The internal !== guard still short-circuits the no-op case.
- setStoredFramework now validates against knownFrameworks. Previously
  any string could poison the stored preference (e.g. a route segment
  like 'docs'); invalid slugs now warn and no-op.
2026-04-20 16:38:02 -07:00
Jordan Ritter 286b214fbf fix(showcase/shell-docs): middleware skips non-GET requests and router prefetches
`docs_pageview` was firing on every matched request — HEAD probes,
POSTs, and Next.js router prefetches all counted as pageviews, which
inflated counts and polluted funnels. Next.js App Router prefetches
links via low-priority fetches that still hit middleware, so they're
especially noisy on pages with long link lists.

We now bail out of tracking when:
  - the method is not GET
  - `next-router-prefetch: 1` is set (Next.js App Router)
  - `purpose: prefetch` is set (generic prefetch hint)

The response is still served normally; only the PostHog capture is
skipped.
2026-04-20 16:37:40 -07:00
Jordan Ritter 16d940779a chore(showcase/shell-docs): a11y-hide commit-SHA overlay; short-sha 7 chars; distinguish dev vs empty
- Add aria-hidden=true so the SHA is not announced by screen readers.
- Shorten to the conventional 7-char git short-sha (was 9).
- Distinguish 'dev' (NEXT_PUBLIC_COMMIT_SHA undefined) from 'unknown'
  (env present but empty string) so Docker ARG-scope bugs surface as
  something other than a misleading 'dev' label in production.
2026-04-20 16:37:25 -07:00
Jordan Ritter b69846c353 fix(showcase/shell-docs): tighten middleware matcher + log posthog errors + module-load warning
Three smaller cleanups to the middleware:

1. Matcher regex: `(?!api|...)` matched `/apidocs` as well as `/api`,
   dropping analytics for any docs path whose first segment starts with
   'api'. The pattern now uses `api/` with a terminator, and also
   excludes `robots.txt`, `sitemap.xml`, `manifest.webmanifest`, and
   the `.well-known/` prefix, which some static-path tooling expects
   to reach without middleware interception.

2. Capture errors are now logged via `console.warn` rather than
   silently swallowed. PostHog 401/403/5xx, DNS failures, etc. were
   invisible to operators.

3. The missing-`POSTHOG_PROJECT_KEY` warning now fires once at module
   load rather than via a module-scoped mutable flag. Edge runtime
   isolates are short-lived and per-request, so the in-memory
   warn-once flag was unreliable. Module-load is the cleanest
   available single-fire hook.
2026-04-20 16:37:07 -07:00
Jordan Ritter bb1c337255 fix(showcase/shell-docs): guard knownFrameworks against reserved-route-slug collisions
If a registry integration ever ships a slug matching a top-level route
segment (docs, ag-ui, reference, api, matrix, integrations), the
FrameworkProvider.urlFramework resolver would treat the route as a
framework scope and hijack navigation. Filter those slugs out in the
root layout and log a dev-only console.error so the collision is
visible to the maintainer.
2026-04-20 16:37:06 -07:00
Jordan Ritter 6a355693ea fix(showcase/shell-docs): drop redundant env block in next.config.ts; validate NEXT_PUBLIC_BASE_URL at build time 2026-04-20 16:37:01 -07:00
Jordan Ritter 6faf3a7bb7 fix(showcase/shell-docs): middleware uses per-visitor distinct_id via first-party cookie
The previous hard-coded distinct_id ("docs-pageview-tracker") collapsed
every visitor into a single synthetic PostHog person, which destroyed
unique-visitor analytics.

Each visitor now gets a stable UUID minted on first visit, persisted
in a first-party `ph_distinct_id` cookie (Lax, Secure, ~2 year TTL)
and read back on subsequent requests. This avoids IP+UA hashing and
the PII concerns that come with it.

Also wraps the PostHog capture POST in `event.waitUntil()` — the
middleware signature now takes `NextFetchEvent` — so the Edge runtime
keeps the request alive until the POST resolves. A bare fire-and-forget
fetch can be torn down as soon as `NextResponse.next()` returns,
dropping events.
2026-04-20 16:36:40 -07:00