Commit Graph

538 Commits

Author SHA1 Message Date
Jordan Ritter f1be2bc0d3 fix(shell-docs/docs-render): backref and longest-first alternation in convertTablesInJSX
JS regex alternation is leftmost-first, not leftmost-longest, so for
source like `<Tabs>` the pattern `Tab|Tabs` matches "Tab", `[^>]*`
consumes the "s", and the close-side alternation would then pair
with any container's `</...>` regardless of which tag opened. Two
separate bugs fell out of this:

- Prefix collisions (Tab vs Tabs) silently skipped table conversion.
  Sort JSX_CONTAINER_TAGS longest-first so the opener always matches
  the actual outer tag name.
- Mismatched pairs like `<Tabs><Tab>x</Tab></Tabs>` paired the outer
  `<Tabs>` with the inner `</Tab>`, stranding `</Tabs>`. Use a
  numbered backref (`\\2`) on the closing tag so only the same tag
  name can close the match.

Also tighten the inline comment on the nested-same-tag bailout to
reflect that the non-greedy match now closes on the same tag, not
"any container in the set".
2026-04-20 22:18:00 -07:00
Jordan Ritter 235f6a67a6 Merge jpr5/shell-docs-td-routes-infra into tech-debt branch 2026-04-20 19:44:57 -07:00
Jordan Ritter 4b2ef609bb Merge jpr5/shell-docs-td-lib into tech-debt branch 2026-04-20 19:44:57 -07:00
Jordan Ritter c02a0176ce Merge jpr5/shell-docs-td-framework into tech-debt branch 2026-04-20 19:44:56 -07:00
Jordan Ritter 9773491455 refactor(showcase/shell-docs): shared ErrorBoundaryCard; thin route-level error wrappers
Extract the near-identical card markup from both route-level
error.tsx files into src/components/error-boundary-card.tsx. The
route-level handlers remain required (Next.js discovers them per
route segment) but are now one-line wrappers that forward props
with a scope label ("docs" / "ag-ui") so console.error entries
stay distinguishable per route segment (finding #14).
2026-04-20 19:41:57 -07:00
Jordan Ritter 4ca108ada0 fix(showcase/shell-docs): reference breadcrumb Title-Cases; category list derived from items
- Replace the className="capitalize" breadcrumb span (which only
  cased the first character, rendering my-component as
  My-component) with an explicit titleCase helper that splits on
  hyphens and capitalizes each segment (finding #12).
- Derive the sidebar category list from loadAllReferenceItems
  instead of hardcoding ["Components", "Hooks"]. A new
  REFERENCE_SUBDIRS entry in reference-items.ts now shows up
  automatically without a second edit here (finding #13).
2026-04-20 19:41:50 -07:00
Jordan Ritter f2af758f0c fix(showcase/shell-docs): ag-ui titleFromSlug title-cases + caches nav titles
- titleFromSlug now Title-Cases the result (multimodal-inputs →
  Multimodal Inputs) instead of returning a lowercased fallback.
  The lowercase labels previously clashed with the docs-render
  and reference-breadcrumb conventions elsewhere (finding #10).
- Wrap getTitleForSlug in a process-scoped cache with dev-mode
  invalidation, mirroring reference-items.ts. Previously every
  entry in NAV_DEFINITION re-opened and re-parsed its MDX file on
  every request — multiply across ~20 nav entries per render and
  it added noticeable fs churn (finding #11).
2026-04-20 19:41:42 -07:00
Jordan Ritter e6f2476fe3 fix(showcase/shell-docs): harden [framework] route — RESERVED slugs, null-alt filter, registry-gated landing cards
- Reject RESERVED_ROUTE_SLUGS at the handler as defense-in-depth.
  Next.js already prefers exact-match top-level routes over this
  catch-all, so /docs, /ag-ui, etc. never reach here under normal
  routing — but if the registry ever ships an integration whose slug
  collides with a reserved segment, the handler now short-circuits
  with notFound() rather than rendering garbage (finding #6).
- Import the shared findFrameworksWithCell helper instead of a
  duplicated local copy (finding #3 follow-through).
- Filter out unresolved alternative-framework slugs BEFORE mapping
  to React fragments so the ", " separator aligns with the final
  rendered count. The pre-filter index previously emitted stray
  commas when an entry dropped to null mid-sequence (finding #7).
- FrameworkLandingPage's four hardcoded LandingCards are now gated by
  registry integration.features presence: strands (no HITL support)
  no longer shows a dead "Human-in-the-Loop" card. The card
  definitions are hoisted into a module-level constant with a TODO
  pointing at the eventual feature-id → doc-slug mapping
  (finding #8).
- Declare export const dynamicParams = true explicitly so a future
  migration to output: "export" fails loudly here (under static
  export dynamicParams must be false) rather than silently 404ing
  every /<framework>/<slug> URL (finding #9).
2026-04-20 19:41:34 -07:00
Jordan Ritter bf8da2c216 refactor(showcase/shell-docs): extract findFrameworksWithCell; validate integration slug; sort-ordered preview selection
- Extract findFrameworksWithCell into @/lib/docs-render so the docs
  catch-all and framework-scoped catch-all share one implementation
  instead of carrying a near-identical local copy in each (finding #3).
- Validate /docs/integrations/<slug> against the registry with
  notFound() when the slug is unknown. Previously a crafted URL like
  /docs/integrations/fake-framework silently fell through to an empty
  nav tree, indistinguishable from a valid integration with no scoped
  content (finding #4).
- Sort integrations by sort_order then slug before picking the
  animated preview URL. Registry iteration order alone isn't
  deterministic w.r.t. the visual priority the docs UI shows
  everywhere else, so the preview now matches (finding #5).
2026-04-20 19:41:21 -07:00
Jordan Ritter a12be881ad feat(showcase/shell-docs): snippet supports comma-separated line ranges + maps tsx/jsx/sh/bash to hljs langs
Two additions in snippet.tsx:

1. parseLineRange now accepts comma-separated segments like
   `lines="1-5,10-15"`. Each segment is validated and sliced
   independently; discontinuous sections are stitched with a visible
   `// ...` gap marker so readers see the jump. Invalid segments fail
   the whole prop (clear error beats partial rendering). Single-range
   and open-ended ("A-") forms continue to work unchanged.

2. resolveHljsLanguage now has explicit entries for tsx (→ typescript),
   jsx (→ javascript), and sh / bash / shell (→ bash). These are the
   common bundler-emitted hints that previously returned null and
   fell through to highlightAuto — producing noisy one-shot warnings
   and non-deterministic highlighting.
2026-04-20 19:36:57 -07:00
Jordan Ritter 38b9858920 chore(showcase/shell-docs): reference-items documents cache-lifecycle
The __itemsCache has no invalidation hook and lives for the life of
the Node process. That's correct for the current `next start`
deployment but silently fragile if we ever add ISR to /reference —
the cache would serve stale items forever.

Replace the two-line inline comment with a JSDoc block that spells
out the prod vs dev behaviour and the ISR caveat, so future changes
know where the assumption lives.
2026-04-20 19:36:45 -07:00
Jordan Ritter 69d497412b fix(showcase/shell-docs): mdx-registry Tooltip warns on dropped props; validate iframe src; Link detects external href
Three loosely-coupled shim hardenings in one commit (all in mdx-registry.tsx):

- Tooltip / TooltipProvider were children-only shims that silently
  dropped Radix-style `content` / `label` props. Route through
  stub() so dev gets a one-shot warning with the dropped prop names.

- YouTubeVideo + IframeSwitcher now validate author-controlled URLs
  via a shared validateIframeSrc() helper: only https:// is accepted,
  malformed URLs fall back to rendering nothing, and dev gets a
  targeted warning. YouTubeVideo also enforces the 11-char video-id
  shape so a stray value can't traversal-inject query params into the
  embed URL.

- Link shim now detects external hrefs (https://..., mailto:..., etc.)
  and routes them through a plain <a> with sensible defaults
  (target=_blank, rel=noopener noreferrer). Internal hrefs continue to
  use next/link for client-side navigation. next/link should never be
  used for external URLs — it spuriously prefetches them.
2026-04-20 19:36:37 -07:00
Jordan Ritter 1a354f5b5b refactor(showcase/shell-docs): document inlineSnippets regex selectivity
The inlineSnippets regex deliberately only matches strict self-closing
JSX with an optional `components={...}` attr, which confused readers
into thinking tags like `<Snippet region="x" />` fall through by
accident. Document the two-path design (SNIPPET_MAP inlining vs MDX
component map) so future edits don't try to "fix" the selectivity.

No behaviour change. SNIPPET_MAP aliases were already documented
in-place (finding noted earlier); this commit covers the regex comment
only.
2026-04-20 19:36:25 -07:00
Jordan Ritter 04b1353f9d perf(showcase/shell-docs): disable readTitle/readMeta caches in dev
readTitle/readMeta are backed by process-scoped Maps. Without a dev
bypass, editing an MDX title or meta.json required a server restart to
see the change — the nav sidebar would keep showing the old title.

Match the convention already used in reference-items.ts: gate both the
read and write paths on isProd() so `next dev` re-reads the source on
every request. Production behaviour is unchanged.
2026-04-20 19:36:04 -07:00
Jordan Ritter c7d225a48e fix(showcase/shell-docs): oxfmt pass on router-pivot 2026-04-20 19:35:57 -07:00
Jordan Ritter 4c955d9ef7 refactor(showcase/shell-docs): sidebar-link drop dead scope/fallbackHref props + update callers
Both props were accepted but ignored (underscore-destructured). scope
was originally intended to switch resolution logic, but both branches
collapsed to the same href rule. fallbackHref was a pre-hydration
placeholder that became unnecessary once the provider made framework
URL-derived.

Remove them from the public SidebarLinkProps and from the only caller
(docs-page-view), which was computing a scope based on slugHrefPrefix
solely to pass it through.
2026-04-20 19:35:33 -07:00
Jordan Ritter db4f94c9a2 refactor(showcase/shell-docs): framework-tabs cell/region props optional + documented
cell and region are authored on the interface for MDX usage + tooling
but the component doesn't consume them at runtime — the parent MDX
renderer pre-renders one <Snippet> per framework on the server and
emits them as children. Mark them optional and document the contract.

Also filter React.Children.toArray output to valid elements before the
index→framework map, so stray whitespace text nodes injected by MDX
don't shift the mapping and render the wrong snippet.
2026-04-20 19:35:26 -07:00
Jordan Ritter 126416d318 fix(showcase/shell-docs): framework-selector adds touchstart click-outside handler
iOS Safari does not reliably fire mousedown for taps landing outside
focusable UI, so the panel stayed open on mobile. Mirror the existing
click-outside handler on touchstart and widen the handler type to
MouseEvent | TouchEvent. stripFrameworkPrefix already restricts its
match to the first path segment (docstring makes this explicit).
2026-04-20 19:35:19 -07:00
Jordan Ritter 4f08823384 fix(showcase/shell-docs): docs-tabs handles items/children mismatch + stale active
- Dev-only console.warn when author-supplied items.length diverges from
  the number of <Tab> children — silent drop of extra entries hid bugs.
- Dev-only console.warn on duplicate labels — React keys on label below
  so duplicates broke reconciliation.
- Tab button keys now include the index so duplicate labels don't cause
  React key collisions.
- Added a useEffect that re-seeds the active tab when the set of labels
  changes (e.g. MDX edit swaps titles, HMR reload). Previously useState
  initialized once on mount, so a tab whose label disappeared left the
  panel stuck empty.
2026-04-20 19:35:13 -07:00
Jordan Ritter ee9acdf334 fix(showcase/shell-docs): docs-steps filters to Step children; adds list a11y
Previously any valid child element received the injected __index/__total
props — stray non-Step valid elements (e.g. spacer divs) would have those
props passed straight to the DOM, triggering React warnings. Now filter
to elements whose type === Step before cloning.

Also add role='list' to the Steps container and role='listitem' to each
Step so assistive tech can enumerate the sequence.
2026-04-20 19:35:04 -07:00
Jordan Ritter e4c1b94e2b fix(showcase/shell-docs): docs-callout warns on unknown type
Silent fallback to 'info' masked typos in MDX authors' type props. Now
emit a dev-only console.warn listing the known types when an unknown
one slips past TypeScript (e.g. from raw MDX string literals).
2026-04-20 19:34:58 -07:00
Jordan Ritter 7b62d36bfd fix(showcase/shell-docs): brand-nav activeBrand boundary + z-index layering + drop dead props
- activeBrandFromPath now matches /ag-ui exactly or /ag-ui/..., not any
  path starting with those six chars — prevents misclassifying a future
  /ag-ui-* slug as AG-UI.
- Mobile menu panel z-index bumped to z-[51] so layering above the z-50
  backdrop doesn't rely on DOM sibling order.
- BrandNavProps (frameworkOptions, frameworkCategoryOrder) were unused
  dead API — removed from the interface and function signature. Call
  site passes no props, so no caller changes.
2026-04-20 19:34:51 -07:00
Jordan Ritter 92252c6ba5 refactor(showcase/shell-docs): property-reference deep walker for nested collapsable propagation
React.Children.map only visits *direct* children. When an author
wraps nested <PropertyReference> in a <div> or <Fragment> (a common
MDX pattern), the nested references silently lost `collapsable:
true` propagation because the walk stopped at the wrapper.

Replace the single-level `React.Children.map` with
`deepMapPropertyReferences`, a recursive helper that:
  - clones + enhances any PropertyReference at any depth with
    `collapsable: true`
  - preserves arbitrary wrapper elements (div, Fragment, other
    components) and recurses into their children
  - stops recursing once it hits a PropertyReference — that
    reference's own render will deep-walk ITS children on its turn,
    which is the correct nesting semantic

Updates the JSDoc above the enhanced-children block to reflect the
new behavior — nested wrappers are now supported.
2026-04-20 19:34:36 -07:00
Jordan Ritter f6c945a0d4 fix(showcase/shell-docs): router-pivot guards hydration flash via hasHydrated flag
Both FrameworkGuardedContent and RouterPivot used to render against
`storedFramework` on the very first client render, when it is always
null (localStorage is read in a mount effect inside
FrameworkProvider). A returning user who picked LangChain would
briefly see the full pivot grid + MDX body flash in before
storedFramework flipped from null to "langgraph-python" and the
redirect fired.

Gate both components behind a local `hasHydrated` flag flipped in a
mount useEffect. Pre-hydration we render null (for the MDX body) or
a minimal "Loading…" placeholder (for the pivot itself), so
returning users go straight to the redirect placeholder instead of
flashing content that's about to disappear. Fresh visitors see the
pivot on the next tick — imperceptible in practice.
2026-04-20 19:32:32 -07:00
Jordan Ritter 1b8ce7c1ce fix(showcase/shell-docs): framework-provider exposes storageAvailable + cross-tab storage listener
Previously readStoredFramework returned null for both "never set" and
"localStorage unavailable" — consumers couldn't distinguish a fresh
visitor from a private-mode browser. Introduce a tri-state: keep
storedFramework: string | null for the value, and add
storageAvailable: boolean so UIs can branch on "we can't persist
your pick" separately from "you haven't picked yet".

Also subscribe to window `storage` events so that when another tab
clears or changes selectedFramework, this tab's stored state stays
in sync (StoredFrameworkHighlight badge + RouterPivot redirect
react immediately without a reload).

Strengthens the comment above the URL-persistence effect explaining
why `stored` is intentionally excluded from the deps array (prevents
a setState ping-pong loop), and adds behavioral "Covered by:" notes
above each non-trivial fix.
2026-04-20 19:31:46 -07:00
Jordan Ritter 539c860861 refactor(showcase/shell-docs): simplify Dockerfile to match showcase-shell pattern 2026-04-20 18:58:57 -07:00
github-actions[bot] 1e5dfb4b4b style: auto-fix formatting 2026-04-21 00:28:09 +00:00
Jordan Ritter 06309d9dbb fix(showcase/shell-docs): next.config build detection uses NEXT_PHASE
Replace the argv-sniffing 'process.argv.includes("build")' check with
process.env.NEXT_PHASE === "phase-production-build", the Next.js
canonical signal for a production build. The argv approach is fragile:
it breaks under wrappers, programmatic invocation, or any tool that
invokes next via a different argv shape.
2026-04-20 17:23:51 -07:00
Jordan Ritter e637a17dcc fix(showcase/shell-docs): Dockerfile fails fast when COMMIT_SHA is default unknown
COMMIT_SHA defaulted to 'unknown', so a caller that forgot to pass
--build-arg COMMIT_SHA would silently ship an image whose footer,
health checks, and error reports all report 'unknown'. Add a guard in
the builder stage that refuses to proceed when COMMIT_SHA is unset or
still the 'unknown' default.
2026-04-20 17:23:46 -07:00
Jordan Ritter c756b6404e fix(showcase/shell-docs): scripts stage uses npm ci when lockfile present
showcase/scripts/ now ships a package-lock.json (committed here for the
first time), so the Docker builder can use 'npm ci' for deterministic
installs instead of 'npm install'. Update the comment to match actual
state and copy the lockfile into the scripts stage.
2026-04-20 17:23:36 -07:00
Jordan Ritter 91122247a8 fix(showcase/shell-docs): Dockerfile prunes dev deps in runner
The runner stage was copying the entire builder node_modules tree,
shipping tailwindcss, postcss, tsx, typescript, and every @types/*
package into the production image. Add 'npm prune --omit=dev' after
the node_modules copy (and also copy package-lock.json so prune runs
deterministically) to strip dev-only packages from the runtime image.
2026-04-20 17:23:19 -07:00
Jordan Ritter 9a978efec6 refactor(showcase/shell-docs): reuse docs-render stripLeadingImports instead of fence-aware duplicate
The ag-ui and reference pages each carried their own near-identical
stripImportsFenceAware copy with a TODO(dedup) flag. The canonical
implementation already exists in docs-render.tsx as stripLeadingImports
and has better semantics — it only strips imports that appear in the
top-of-file header region (before the first non-import content line),
rather than any import anywhere outside a fence. That distinction
matters for doc bodies that legitimately discuss imports in prose
outside code fences.

Export stripLeadingImports from docs-render and point both pages at it;
remove the local duplicates.
2026-04-20 17:17:37 -07:00
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