Commit Graph

6618 Commits

Author SHA1 Message Date
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 05cd01ec04 Finish the grid: #4085 + shell platform restructure (shell-docs, shell-dashboard, shell-dojo) (#4109)
## Summary

- Merges #4085 (Atai's no-docker-restructure feature port — merge-ready
variant) as the primary base.
- Renames `showcase/shell-internal/` → `showcase/shell-dashboard/` +
provisions Railway service at `dashboard.showcase.copilotkit.ai`.
- Renames `showcase/shell-dojolike/` → `showcase/shell-dojo/` + renames
Railway service in place (domain unchanged).
- Extracts MDX-docs infrastructure (`/docs`, `/[framework]`, `/ag-ui`,
`/reference` + components, libs, content) from `showcase/shell/` into
new standalone `showcase/shell-docs/` package; provisions Railway
service at `docs.showcase.copilotkit.ai`; adds 301 redirects on shell
for every moved route (including 17 per-framework slugs enumerated from
`registry.json`).
- Fixes `starter-langroid` `/api/health` 503 (sed stdout wrappers +
readiness gate); adds red-green regression guards in
`starter-consistency.test.ts`.

## DNS state

- `dashboard.showcase.copilotkit.ai` CNAME → `6u9icvje.up.railway.app` —
✅ live in Route53.
- `docs.showcase.copilotkit.ai` CNAME → `t6ge0qwv.up.railway.app` —
pending Route53 add.

## Railway services

- `showcase-shell-dashboard` (ID `4d5dfd74-be61-40b2-8564-b53b7dd4c15b`)
— provisioned, image pending first deploy.
- `showcase-shell-docs` (ID `7badfb8d-4228-414c-9145-b4026803714f`) —
provisioned, image pending first deploy.
- `showcase-shell-dojo` (ID `7ad1ece7-2228-49cd-8a78-bddf30322907`) —
renamed from shell-dojolike; already live.

## Closes / supersedes

- Supersedes #4084 (docker-restructure variant; preserved on
`jpr5/ftg-shell-ops` for future consideration, not merged here).
- True-merges #4085 via `--no-ff` — that PR auto-closes on this PR's
merge.

## Known non-blockers

- `/ag-ui/introduction` throws a server-component `onMouseEnter` runtime
error on `next start`. Same issue exists in shell today — pre-existing,
not a regression. Follow-up needed.
- `showcase/shell-dojo` lacks `package-lock.json` and `.gitignore`;
trivial follow-up for parity with siblings.
- `search-modal` retains the flat `search-index.json` shape (not the
extensible `{version:1, sources:[...]}` shape). Generator is the single
point of change if multi-shell search is wanted later.
- oxfmt has a pre-existing oscillation bug on 14 `.mdx` files with
`<Steps>` structures; sidestepped by mirroring the existing
`showcase/shell/src/content/**` ignore policy to the new
`showcase/shell-docs/src/content/**` path.

## Test plan

- [ ] CI green end-to-end (Validate Showcase, unit tests on 20/22/24,
format, oxlint, commitlint, starter-smoke).
- [ ] \`showcase_deploy.yml\` builds shell-dashboard + shell-docs images
to GHCR and Railway pulls them successfully.
- [ ] Add \`docs.showcase\` CNAME in Route53
(\`t6ge0qwv.up.railway.app\`) — without this,
docs.showcase.copilotkit.ai won't resolve and Railway won't provision
its TLS cert.
- [ ] Verify \`dashboard.showcase.copilotkit.ai/\` returns 200.
- [ ] Verify \`docs.showcase.copilotkit.ai/\` returns 200 and
\`/langgraph-python/quickstart\` renders.
- [ ] Verify \`showcase.copilotkit.ai/\` returns grid (200);
\`/docs/foo\` 301s to \`docs.showcase.copilotkit.ai/foo\`;
\`/react/foo\` 301s; \`/integrations\` and \`/matrix\` do NOT redirect.
- [ ] \`starter-langroid\` redeploy via \`showcase-deploy\` workflow:
\`/api/health\` returns 200.
2026-04-20 19:25:39 -07:00
Jordan Ritter 5964b44b57 fix(ci): shell-dashboard ALL_SERVICES entry mirrors shell-docs structure 2026-04-20 18:58:57 -07:00
Jordan Ritter d143a197e2 refactor(showcase/shell-dashboard): simplify Dockerfile to match showcase-shell pattern 2026-04-20 18:58:57 -07:00
Jordan Ritter 539c860861 refactor(showcase/shell-docs): simplify Dockerfile to match showcase-shell pattern 2026-04-20 18:58:57 -07:00
Jordan Ritter 863bee7966 fix(showcase/shell-dashboard): add missing Dockerfile modeled after shell-docs 2026-04-20 18:18:41 -07:00
Jordan Ritter 1c85fc0d70 fix: clarify pin-drift alert wording (DRIFT vs FAIL) (#4110)
## Summary
- Rename `FAIL=N` → `DRIFT=N` in the weekly showcase pin-drift Slack
alert; "FAIL" reads as a regression even when N matches the ratchet
baseline.
- Restructure three paths: `[stable]` (match), `[REGRESSION]` (count or
hash grew), `[IMPROVED, ratchet me]` (count shrunk).
- Red path retains run URL link. Ratchet logic, baseline file, and
hashing unchanged.

## Test plan
- [ ] Verify `.github/workflows/<name>.yml` YAML parses.
- [ ] Confirm next weekly run posts `[stable]` framing (or dry-run the
step manually if feasible).
- [ ] No change to `showcase/scripts/fail-baseline.json` or ratchet
logic.
2026-04-20 18:16:01 -07:00
Jordan Ritter c3ad48a8af fix: clarify pin-drift alert wording (DRIFT vs FAIL)
The weekly Showcase pin-drift Slack alert previously read
"📉 Showcase pin-drift (weekly): FAIL=N
(baseline N) [ok]". The token "FAIL=N" read as a regression even when N
matched the ratchet baseline exactly (a stable week), generating recurring
false-alarm fatigue on #oss-alerts.

Reframe the notification text into three explicit states:

- [stable]   — DRIFT=N matches baseline AND hash matches
- [REGRESSION] — DRIFT=N grew OR hash mismatch (set drifted); keeps the
  🚨 emoji + run URL for actionable alerts
- [IMPROVED, ratchet me] — DRIFT=N shrank; reminder to update the baseline

Presentation-only. The ratchet logic in validate-pins.ts, the baseline file
(showcase/scripts/fail-baseline.json), and the FAIL-line hashing are
unchanged.
2026-04-20 18:14:13 -07:00
Jordan Ritter 41c006fa31 chore: re-trigger CI on formatting commit 2026-04-20 18:02:57 -07:00
github-actions[bot] dc587abbcd style: auto-fix formatting 2026-04-21 00:51:40 +00:00
Jordan Ritter c91c7c567d fix(showcase/scripts): generate-search-index warns on missing scan dirs
If shell-docs/src/content/{reference,ag-ui,docs} didn't exist the
script silently produced a tiny index with no warning — operators
only found out by noticing Cmd-K search returning nothing.
Warn per missing directory and exit non-zero if ALL scan roots are
missing (that means we're running outside a prepared tree, e.g.
shell-docs didn't emit into the expected layout).
2026-04-20 17:49:59 -07:00
Jordan Ritter 1bc90bc9ec fix(ci): showcase_deploy prior-deploy jq uses // empty for null normalization
The main-deploy step already uses '// empty' on PRIOR_DEPLOY_ID but
the verify step extracted DEPLOY_ID / STATUS / DOMAIN without it, so
when Railway's edges array was transiently empty these became the
literal string 'null'. That breaks the prior-vs-fresh comparison and
the DOMAIN emptiness guard in inconsistent ways. Add '// empty' to
all three jq filters so empty-edge responses normalize to '' the
same way everywhere.
2026-04-20 17:49:59 -07:00
Jordan Ritter c647a1dc9b fix(showcase/scripts): bundle-demo-content watch-mode tracks failure state
The watch loop logged '[watch] bundle failed' once and then fell
silent — repeat failures looked like success, and recoveries were
invisible (no news = assumed fine). Track the last error in module
scope so we distinguish first-failure from repeat-failure, and log an
explicit 'bundle recovered' note when the next green run clears the
state. Makes dev-mode transitions visible instead of silent.
2026-04-20 17:49:58 -07:00
Jordan Ritter 018be75633 fix(showcase/scripts): loadDocsLinks parse failures surface via allErrors
A malformed docs-links.json was only console.warn'd and treated as
empty — build ran green while the override file silently rotted.
Accept an errors accumulator and push parse failures into it so
main()'s non-zero exit path fires. Missing file and stale-shape
tolerance are unchanged (both are legitimate states).
2026-04-20 17:49:58 -07:00
Jordan Ritter 3326b3c1e1 fix(showcase/scripts): probeOg adds timeout + error logging
Without a timeout on the inner fetch, a hung upstream would stall the
entire docs-probe run indefinitely (Node's fetch has no default
timeout). Add a 10s AbortController. The bare catch also left 'error'
states opaque — log URL + error kind so operators can tell an
abort from DNS from TLS.
2026-04-20 17:49:58 -07:00
Jordan Ritter 5068852a29 fix(showcase/scripts): probeHealth logs error details
The bare catch returned 'unknown' with no trace, making it impossible
to distinguish DNS failures from aborts from TLS errors in the unknown
bucket. Log the URL plus a kind:code pair so spikes of 'unknown' are
actually diagnosable.
2026-04-20 17:49:58 -07:00
Jordan Ritter ef9b856ed6 fix(showcase/scripts): generate-status env-gates mock e2e/smoke/qa + warning banner
mockE2E/mockSmoke/mockQA were called unconditionally from main(), so
production dashboards shipped seeded test data in columns that aren't
wired to real CI yet. Gate each behind GENERATE_STATUS_MOCK_*
env vars (default off → emit null / 'unknown'). Print a prominent
banner to stderr naming which columns are mock whenever any gate is
on, so operators can't discover stub data by surprise.
2026-04-20 17:49:57 -07:00
Jordan Ritter 22b9050608 fix(showcase/shell): next.config throws on corrupt registry in production; logs in dev
A bare catch swallowed JSON.parse failures, silently returning [] and
making every /<slug> framework redirect disappear. Throw in production
(registry is a required build-time artifact) and console.warn in dev
(so a transient mid-write doesn't kill the dev loop). Also throw if
the file is missing in production for the same reason.
2026-04-20 17:49:57 -07:00
github-actions[bot] 61c3833110 style: auto-fix formatting 2026-04-21 00:42:20 +00:00
Jordan Ritter 3a3b3e37b4 refactor(showcase/scripts): starter-consistency uses agent-URL factory instead of .source munging
The cross-starter consistency test had two AGENT_URL matchers:

    function re8000() {
      return new RegExp(AGENT_URL_LOCALHOST_8000_RE.source, flags);
    }

    function re8123() {
      return new RegExp(
        AGENT_URL_LOCALHOST_8000_RE.source.replace(/8000\\b/, "8123\\b"),
        flags,
      );
    }

The 8123 variant does regex-source string munging — brittle: the
replacement target `8000\\b` has to stay in lockstep with the
shared regex's literal source, and a future port format change would
snap in a subtle way (the test could keep passing while matching
subtly wrong content, or start failing for reasons unrelated to the
starter it's guarding).

Replace the `.source.replace` hack with an exported factory
`makeAgentUrlLocalhostPortRE(port)` in `generate-starters.ts` and
have both `re8000`/`re8123` use it. The existing
`AGENT_URL_LOCALHOST_8000_RE` constant is preserved (now delegating to
the factory) so any external import stays happy.

Bonus: the factory validates port bounds so a typo can't silently
produce a pattern that never matches.
2026-04-20 17:40:22 -07:00
Jordan Ritter 571519b6ab fix(showcase/scripts): bundle-demo-content collates README regions; skips binary files
Two orthogonal bugs in the demo-content bundler:

1) README regions were silently dropped. `collectDemoFiles` pulls the
   demo-dir root README out into the `readme` field rather than
   appending to `files`, but it STILL wrote any `@region[...]`
   slices it found inside the README into `perFileRegions` under the
   README's bundled path. The region-collation loop at write time
   iterated `fileOrder = files.map(f => f.filename)`, which by
   construction never contains the README path — so those regions
   went nowhere. Walk any `perFileRegions` keys not in fileOrder
   after the main pass, so README regions still land in the public
   map. Preserves the stable file-order rule (README contributions
   come after source files, alphabetical tiebreak).

2) Binary-like files were being passed through
   `fs.readFileSync(abs, "utf-8")` and then stringified into the
   bundle. A PNG or font file typically produces a mess of
   replacement characters in `demo-content.json` — waste at best,
   visible corruption at worst when the Code tab ever tried to
   render them. Extend `SKIP_EXTENSIONS` with the usual binary
   suspects (images, fonts, archives, media, PDFs) so the walker
   drops them at scan time.
2026-04-20 17:40:11 -07:00
Jordan Ritter 91d847be06 fix(showcase/scripts): probe-docs reads shell_docs_path with legacy fallback
The canonical schema in `scripts/generate-registry.ts` + per-package
`docs-links.json` calls the shell path `shell_docs_path`. probe-docs
was still reading ONLY the legacy `shell_docs_url` key, so any
future cleanup of `shared/feature-registry.json` to the canonical
key would silently turn every shell-docs status into 'missing'.

Prefer `shell_docs_path`, fall back to `shell_docs_url` so older
registry snapshots still contribute. Emit a one-shot dev-mode warning
when we see only the legacy key, so a lingering registry stays visible
without spamming CI logs.
2026-04-20 17:39:58 -07:00
Jordan Ritter 1c1a26b3cb fix(ci): showcase_deploy notify surfaces jq errors; drop top-level 'local'
Two small but load-bearing hygiene fixes in the notify job's shell:

1) The matrix-legs collection pipeline used `jq -r '...' 2>/dev/null`,
   swallowing any jq parse or schema errors before the surrounding
   pipeline substitution could see them. If the gh-api payload shape
   ever shifts (schema rev, partial response, encoding glitch), we
   now want the error in the job log so an operator knows WHY notify
   fell back to softer wording — the existing `-z "$pairs"` guard
   still handles the empty-output path without relying on suppression.

2) The duplicated inline `truncate_csv` helper was declaring
   `local ws=...` on its OWN line AFTER `local budget=...` on the
   function's first line. Redeclaring `local` inside the same
   function body is legal but prints 'local: not in a function'
   warnings on some bash versions (3.x, noexec edge cases) and is
   outright forbidden by `set -eu` hardening profiles. Collapse `ws`
   into the single `local` declaration at the top and use a plain
   assignment where it's computed.
2026-04-20 17:39:49 -07:00
Jordan Ritter a6b7941cd0 fix(ci): showcase_deploy classifier — skipped no longer misclassified as failure
The BUILD outcome classifier read:

    elif [ "$BUILD" = "failure" ] || [ -n "$BUILD" ]; then

After the earlier `success` + `skipped && detect=success` branches
had claimed the obvious green cases, any non-empty BUILD string fell
through the `-n` test and was tagged FAILURE — including values like
"skipped" that reach this branch when detect-changes did NOT emit
`success` (e.g. cancelled pre-stage races or a partial matrix skip).
That means the Slack bot would occasionally cry red at runs that never
failed.

Simplify to an exact string match on "failure"; the final `else`
branch already handles the empty/unknown case by emitting OUTCOME=skip,
which is the correct disposition for indeterminate state.
2026-04-20 17:39:23 -07:00
Jordan Ritter b4b3a80515 ci(workflows): bump actions/checkout to v4 in check-binaries
The rest of the showcase workflows already pin `actions/checkout@v4`.
This one lagged on v3, which is now long-deprecated (Node 16 runner
EOL). Bringing it in line with the fleet avoids a future forced bump
when GitHub retires v3 runners entirely.
2026-04-20 17:39:08 -07:00
Jordan Ritter 28e74e2a9f chore(ci): purge test-integration-tmp pollution from workflows
Remove every trace of the defunct test-integration-tmp service from the
showcase deploy workflow:

- drop the `test-integration-tmp` entry from the workflow_dispatch
  inputs.service dropdown,
- drop the detect-changes `test_integration_tmp` output + paths-filter
  entry, and
- delete the entire build-test_integration_tmp job (including its
  placeholder RAILWAY_SERVICE_ID literal).

Matches the jpr5/test_integration_tmp_leak cleanup — the test harness
leak has been patched at its source (create-integration.test.ts), but
these workflow vestiges were never part of that fix and only survived
because nothing read them after the regression guard landed.
2026-04-20 17:39:02 -07:00
github-actions[bot] 1e5dfb4b4b style: auto-fix formatting 2026-04-21 00:28:09 +00:00
Jordan Ritter 6379cbbda0 Merge jpr5/ftg-shell-ops-v2: Fix Cycle 1+2 (smart rigor) 2026-04-20 17:26:32 -07: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