Commit Graph

9639 Commits

Author SHA1 Message Date
Env Seed Test e48d90ba8d feat(repo): add 1password env seeding 2026-05-26 15:44:57 +02:00
Alem Tuzlak 551d6a5746 fix(showcase): stabilize ms agent demo fixtures 2026-05-21 14:15:33 +02:00
Alem Tuzlak 7ed1d2a618 docs: add showcase demo debugging skill (#4949)
## What does this PR do?

Adds a repo-local `showcase-demo-debugging` skill for both Codex and
Claude.

The skill documents the required workflow for showcase demo work:

- read the showcase runbooks before changing code or fixtures
- keep demos on CopilotKit v2
- use `langgraph-python` as the 1:1 gold standard for showcase demos
- route real-LLM local runs through aimock record mode
- convert recorded interactions into deterministic D5 fixtures
- verify every suggestion pill, including repeated and interleaved pill
clicks under aimock replay
- add D5/e2e regression coverage after the user verifies the fixed flow

## Validation

- `python
C:\Users\AlemTuzlak\.codex\skills\.system\skill-creator\scripts\quick_validate.py
F:\projects\cpk\CopilotKit\.agents\skills\showcase-demo-debugging`
- `python
C:\Users\AlemTuzlak\.codex\skills\.system\skill-creator\scripts\quick_validate.py
F:\projects\cpk\CopilotKit\.claude\skills\showcase-demo-debugging`

Pre-commit hooks were attempted but failed in this environment because
`oxlint` and `nx` were not resolvable by the hook runner.
2026-05-21 10:41:21 +02:00
Alem Tuzlak 4cf0c5fed3 docs: add showcase demo work skill 2026-05-21 10:35:12 +02:00
Tyler Slaton ad8d6a9d69 feat(shell-docs): upgrade to fumadocs 16 + next 16; layout polish; llms.txt + page actions (#4946)
## Summary

Upgrades `showcase/shell-docs` to fumadocs 16.8.12 / Next.js 16 / React
19.2, polishes the layout (sidebar card, framework picker, accent icons,
custom `<ThemeSwitch>`, mobile fixes, TOC-less content stretch), and
adds the fumadocs LLMs integration (`/llms.txt`, `/llms-full.txt`,
per-page `.md` / `.mdx` with `<Snippet>` regions inlined). Plus a
page-actions bar (Copy Markdown + Open in Claude / Claude Code /
Windsurf / Codex) and an in-PR CR-loop pass with three rounds of fixes
on top.

## What's in this branch

10 commits, 91 files (3822 insertions, 1699 deletions). Each commit is
one logical batch — designed so a regression can be reverted in
isolation if needed.

- `5728611df` — Stack upgrade + layout polish + LLMs integration +
content fixes (the big initial commit)
- `4a951848f` — Harden `MarkdownCopyButton` (cache poisoning, 404 body,
`res.ok`, prop-spread order) + fix `ViewOptionsPopover` SSR/CSR
hydration via `getBaseUrl()`
- `9417b2e74` — Emit framework root URLs in `/llms.txt` (was silently
dropping them); log silent file reads in `llm-text.ts`; drop dead `void
CONTENT_DIR;` in `llms-mdx` route
- `3700b885b` — `SidebarFolderStatePreserver` synthetic-click guard via
`WeakSet`; fix `PopoverClose` resolving to `undefined` (Radix exports
`Close`, not `PopoverClose`)
- `086e68c88` — Replace 3× empty `runAgent.catch(() => {})` with
`console.error` logging across LGP / LGT / ADK headless-simple; correct
LGT reasoning graph-name comment
- `04c638399` — `launch.json` port-3000 collision (`shell` → 3004);
harden `shell-docs.sh` against silent install failures; untrack
auto-generated `next-env.d.ts`
- `0186ae9f2` — Unbreak preview build: drop server-only `fs`/`path`
import from client `page-actions.tsx`; route `shell` port via `PORT` env
so it actually reaches `next dev`
- `64ffd19d8` — CR Round 2 cleanup: ADK reasoning graph name comment
(was `reasoning_agent`, actual is `_thinking_chat`); dead CSS rule;
orphaned comment placement; framework-prefixed log tags
- `aad480721` — Restore throw in `MarkdownCopyButton` so failed copies
don't show ✓ (Round 2 swallow was triggering a false-success because
`useCopyButton`'s `.then()` fires on resolved callbacks)
- `23c0a8453` — Theme-init script handles `localStorage.theme ===
"system"` (was missing the explicit-system case → light flash for
dark-preferring users); snippet file headers use language-correct
comment syntax (`#` for Python/YAML, `<!-- -->` for HTML/MDX, empty for
JSON, `//` for C-family)

## Verified manually

- Preview server runs cleanly on :3003
- `Copy Markdown` works end-to-end (user-confirmed)
- Theme switch toggles light ↔ dark with no flash on either direction
(user-confirmed)
- `/llms.txt`, `/llms-full.txt`, `/<path>.md` all return expected
content with `<Snippet>` regions inlined
- Sidebar folder open/closed state persists across navigations
- Reasoning page (Google ADK) loads the right cells (`reasoning-default`
/ `reasoning-custom`)

## Known follow-ups (intentionally out of scope)

Filed during the CR loop as bucket (d) — real load-bearing issues whose
subject belongs to a different PR:

- `gpt-5.4-mini` model name typo across ~118 occurrences in docs MDX
(docs-quality sweep)
- `display.mdx` code-fence formatting inside `<Tab>` containers (MDX
content sweep)
- `<CopilotUI />` stub in `mdx-registry.tsx` is a div wrapper (line
1041) but `STUB_PARTIAL_MAP.CopilotUI` exists (line 100) — partial never
loads. Pre-existing since 2026-04-19; affects
`crewai-flows/quickstart.mdx` (renders empty div)
- `buildGitHubUrl` fallback uses `blob/main/<dir>` which 404s for
directories (pre-existing)
- BrandNav right-wing div-as-button keyboard accessibility
(pre-existing)
- Free Developer Access hydration warning (task tracked separately)
- `next.config.ts` already throws on missing `NEXT_PUBLIC_BASE_URL` /
`NEXT_PUBLIC_SHELL_URL` at build time, but
`.github/workflows/showcase_build.yml` line 186 doesn't plumb either
through to the Docker build. Pre-existing since the throw was added in
`b30e01ad8`. **Verify the next deploy** — if production CI starts
failing on that env var, the fix is to add `build_args_base_url` /
`build_args_shell_url` to the shell-docs matrix entry.

## Test plan

- [ ] CI builds the shell-docs Docker image cleanly
- [ ] Preview deploy renders the docs site
- [ ] Sample page (`/google-adk/generative-ui/reasoning`) shows the
embedded demo, page actions, and correct snippets
- [ ] `/llms.txt` includes framework root URLs (e.g.
`/langgraph-python`, `/built-in-agent`)
- [ ] `/llms-full.txt` contains snippet code inlined with correct
language comment syntax (no `// foo.py` in Python blocks)
- [ ] Copy Markdown copies the actual page body; failed copies do NOT
show the ✓ indicator

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-20 21:35:38 -07:00
Tyler Slaton a07e97b224 chore: run pnpm format
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
2026-05-20 21:30:28 -07:00
Tyler Slaton 1a9cb461e0 chore(claude): remove leftover logic from past dev sessions
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
2026-05-20 21:29:31 -07:00
Tyler Slaton 79181d4d38 feat(shell-docs): re-add markdown_copied + open_in_llm_clicked PostHog events
The original analytics commit (4d67fe269) was reverted because it
also contained a botched BrandNav height change. Re-apply just the
PostHog instrumentation cleanly:

- `MarkdownCopyButton` fires `markdown_copied`
  `{ path: pathname, markdown_url: markdownUrl }` after a successful
  clipboard write. Coexists with the global `cli_command_copied`
  (Benjamin's monkey-patch in `lib/track-command-copy.ts` that
  intercepts every clipboard write at the navigator level); the
  dedicated event lets the dashboard distinguish page-content copies
  from CLI copies (which classify under the existing tracker as
  `code` — not meaningful for the new docs-as-context surface).
- Each `ViewOptionsPopover` item gains a `target` discriminator
  (`github`, `view-as-markdown`, `windsurf`, `claude-code`, `codex`,
  `chatgpt`, `claude`, `cursor`) and an `onClick` that captures
  `open_in_llm_clicked` `{ target, path }`. PostHog buffers locally
  so the new tab opens without waiting on the network.

Closes Sam's P0 analytics ask from PR #4946 — verified by the QA
audit that every existing CTA + capture (`try_for_free_clicked`,
`talk_to_us_clicked`, etc.) survives intact across the branch.
2026-05-20 21:17:34 -07:00
Tyler Slaton b7f3e4a1b5 fix(shell-docs): stop sidebar dropping 54px when banner is present
Fumadocs's docs grid sets `--fd-docs-row-1: var(--fd-banner-height, 0px)`
in `node_modules/fumadocs-ui/dist/layouts/docs/slots/container.js:25`,
and the sidebar wrapper uses that value as both its sticky-top offset
AND its in-grid top offset (`top-(--fd-docs-row-1)` plus
`h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))]`). That design
assumes the banner is sticky / fixed at the viewport top — so the
sidebar starts BELOW the banner.

shell-docs renders the banner in NORMAL body flow above BrandNav.
Banner pushing things down via flow is sufficient; the extra
`--fd-docs-row-1` offset double-counts the banner height. Result:
when the banner appears, BrandNav drops 54px (correct, flow) AND
the sidebar drops an ADDITIONAL 54px below BrandNav (wrong) —
visible as a yawning gap between BrandNav's bottom edge and the
sidebar's framework picker that didn't exist when the banner was
dismissed.

Pin `--fd-docs-row-1: 0px` on `#nd-docs-layout` for md+ so the
sidebar tracks BrandNav's bottom edge consistently, banner or no
banner. The mobile branch (where MobileTopNav is `position: fixed`)
keeps its existing `padding-top: var(--fd-nav-height)` so the docs
grid clears the fixed mobile nav — independent concern, untouched.

Verified at 1440×900 with banner present: BrandNav bottom = 142,
sidebar top = 166, gap = 24px (matches `main`'s `md:mt-6`). Without
banner: BrandNav bottom = 88, sidebar top = 112, gap = 24px. Same
gap in both cases.
2026-05-20 21:15:38 -07:00
Tyler Slaton 885f5cd036 Revert "feat(shell-docs): wire markdown_copied / open_in_llm_clicked + roomier BrandNav"
This reverts commit 4d67fe2691.
2026-05-20 21:12:28 -07:00
Tyler Slaton 00b0fc684d Revert "fix(shell-docs): give sidebar framework picker more headroom inside the card"
This reverts commit 070916302a.
2026-05-20 21:12:27 -07:00
Tyler Slaton 070916302a fix(shell-docs): give sidebar framework picker more headroom inside the card
The sidebar's first child (the SidebarBanner that hosts the framework
picker pill) had `padding: 1rem 1rem 0 1rem`. With the BrandNav and the
sidebar's outer `rounded-2xl border` chrome, 1rem (16px) at the top
read as scrunched against the card's rounded edge — the picker pill
has its own rounded corners and a border, so the gap to the parent
card edge needs to exceed the gap to the first nav link below it to
feel balanced.

Bump the top padding to 1.5rem (24px). Side and bottom padding stay
at 1rem and 0 respectively — the bottom-to-first-nav-link gap is
governed by the scroll viewport's 1rem top padding (defined further
down in this file), so the picker now sits with `24px-pill-16px` above
and below it instead of `16px-pill-16px`.
2026-05-20 21:09:29 -07:00
Tyler Slaton 4d67fe2691 feat(shell-docs): wire markdown_copied / open_in_llm_clicked + roomier BrandNav
Two follow-ups in one commit since they share the same shell-docs scope.

(1) Analytics events for the new docs-as-context surface (per Sam's
    P0 ask on PR #4946). The existing global `cli_command_copied`
    tracker in `lib/track-command-copy.ts` monkey-patches every
    `navigator.clipboard.writeText` call, so my `MarkdownCopyButton`
    was already being captured — but classified as `code` (the
    fallback when the text doesn't match an install command). That's
    not useful for the new "Copy Markdown" affordance.

    - `MarkdownCopyButton` fires `markdown_copied`
      `{ path: pathname, markdown_url: markdownUrl }` after a
      successful clipboard write. Coexists with the global capture;
      the dedicated event lets the analytics dashboard distinguish
      page-content copies from CLI copies.
    - Each `ViewOptionsPopover` item gains a `target` discriminator
      (`github`, `view-as-markdown`, `windsurf`, `claude-code`,
      `codex`, `chatgpt`, `claude`, `cursor`) and an `onClick` that
      captures `open_in_llm_clicked` `{ target, path }`. PostHog
      buffers locally so the new tab opens without waiting on the
      network.

(2) BrandNav was visually flush against the viewport top with
    minimal breathing room around its inner chrome. Bump the nav
    height (`h-[68px] xl:h-[88px]` → `h-[80px] xl:h-[104px]`) and
    matching interior padding so the content row (logo + tabs +
    Talk-to-engineer pill + search) sits comfortably-centered with
    a clear gap above. The taller nav also reads better when the
    rotating banner is visible — the banner / nav / sidebar stack
    now has clear vertical separation rather than feeling stacked.

    Update `--fd-nav-height` accordingly:
    - mobile (unchanged): 56px
    - md (768-1280px): 80px (was 88px hardcoded, which didn't match
      BrandNav's actual md height of 68px — a pre-existing mismatch)
    - xl+ (≥1280px): 104px (was 88px, now matches BrandNav's xl
      height)

    Update the `--fd-docs-height` calcs to track the same numbers
    (68 → 80, 88 → 104) so the sticky sidebar grid-area's height
    stays correct after the BrandNav grows.

Call-site enumeration:
- `MarkdownCopyButton` / `ViewOptionsPopover` — only callers are
  the MDX registry; existing analytics CTAs (try_for_free_clicked,
  talk_to_us_clicked, etc.) untouched.
- `--fd-nav-height` — read by `#nd-docs-layout`'s `pt-(...)` on
  mobile (load-bearing for MobileTopNav clearance) and by the
  Fumadocs sidebar's `top:` offset. Both branches verified visually
  with banner toggled at 1014px and 1440px viewports.
- `--fd-docs-height` — drives the sticky sidebar grid wrapper's
  height; the calc subtracts banner + nav + 2.25rem margin.
2026-05-20 21:05:29 -07:00
Tyler Slaton 1cdaae9369 chore: merge origin/main into tyler/jolly-liskov-74539a
Resolves merge conflict in `showcase/shell-docs/src/components/mobile-top-nav.tsx`:
- v16 of fumadocs moved `SidebarTrigger` from
  `components/layout/sidebar` to `components/sidebar/base` (this PR's
  upgrade). Keep the v16 path.
- `main` added Calendar / Lightbulb icons + `usePostHog` import for the
  expanded mobile CTAs (Get-Intelligence-free + Talk-to-Engineer pill).
  Keep those — they're referenced by the file body.

Combined resolution = main's import set with v16's import path for
SidebarTrigger. Other auto-merged files (brand-nav, snippet,
mdx-registry, etc.) merged cleanly; typecheck passes.
2026-05-20 20:47:21 -07:00
Tyler Slaton 23c0a8453c fix(shell-docs): theme-init handles 'system' value + snippet headers use language-correct comment syntax
CR Round 3 surfaced two more real bucket-(a) findings.

(1) `app/layout.tsx` theme-init script — When a user explicitly picks
    the "system" theme via the next-themes API, the persisted value in
    `localStorage.theme` is the literal string `"system"`, not absent.
    The previous inline script only fell back to `matchMedia(...)` when
    the value was unset (`!t`); for a system-mode user on a dark-
    preferring OS, the script would skip the matchMedia branch (because
    `t === "system"` is truthy), then skip the `.dark` class application
    (because `t !== "dark"`), and the page would paint in light before
    next-themes resolves post-hydration. The light-flash this script
    exists to prevent. Extend the fallback condition to `!t ||
    t === "system"` so the matchMedia path also handles the explicit-
    system case.

(2) `lib/llm-text.ts` Snippet file headers — `resolveSnippet`
    hardcoded `// <filename>` as the in-fence header regardless of the
    snippet's language. For Python regions this emits `//` (integer
    division — invalid syntax), for YAML / Bash / TOML it emits the
    wrong comment marker, for JSON it emits literal `//` (no comments
    allowed in spec JSON). An LLM ingesting `/llms-full.txt` sees what
    looks like real code from the file but with a broken first line.
    Add a `fileHeaderComment(language, text)` helper that picks the
    right comment shape:
    - `#` for Python / Bash / YAML / TOML / Ruby / R / Dockerfile / etc.
    - `/* ... */` for CSS / SCSS / Less
    - `<!-- ... -->` for HTML / XML / Markdown / MDX
    - `-- ` for SQL
    - empty (drop the header) for JSON / JSONC
    - `//` for C-family (TS / JS / Java / Go / Rust / C# / etc.) — the
      previous behavior, preserved as the default.
    Apply across all three snippet emission paths (region, file, file
    + lines). Pass the language through to the helper; when the helper
    returns empty (JSON case), skip the header line entirely so the
    fenced block contains only the code.

Call-site enumeration:
- theme-init inline script — no external callers; the next-themes
  ThemeProvider reads/writes localStorage on its own schedule, our
  script only seeds the `.dark` class pre-hydration. Behavior change
  is strictly additive (one extra matchMedia call when t === "system").
- `fenceFor` — unchanged signature.
- `fileHeaderComment` (new) — used only within `resolveSnippet`. Three
  call sites, all in the same function, all updated.
- `resolveSnippet` — three return paths updated; output shape change
  is invisible to all current callers (`renderPageToLlmText`,
  `inlineSnippets`) which treat the return value as opaque markdown.
2026-05-20 20:40:17 -07:00
Tyler Slaton aad4807213 fix(shell-docs): restore throw in MarkdownCopyButton so failed copies don't show ✓
CR Round 3 caught a real regression I introduced in commit `0186ae9f2`.

The Round 1 commit threw the caught error inside the `useCopyButton`
callback to keep the button in its idle state on failure. The comment
claimed Fumadocs's `useCopyButton` "respects throws" — that wording was
wrong, but the BEHAVIOR was right: `useCopyButton` runs
`Promise.resolve(callback()).then(() => setChecked(true))` with no
`.catch()`, so a rejected callback skips the `.then()` and the button
stays in its idle (Copy) state. Cost: one unhandled rejection in the
browser console per failure.

Round 2's regression-fix removed the throw to suppress that unhandled
rejection. Net effect: the callback now returns normally on failure,
the outer `.then()` fires, `setChecked(true)` flips the button to the
green checkmark — and the user sees a "Copied!" indicator on a copy
that actually failed. They paste stale clipboard content into Claude /
ChatGPT / Cursor and get garbage responses from the LLM.

Restore the throw and update the comment to accurately describe the
trade-off. Unhandled-rejection console noise is the lesser evil
compared to silently misleading the user. A follow-up PR (filed in
the bucket-d follow-up list) can introduce an explicit error UI state
(e.g. an alert icon for 2s) so failures are surfaced visibly without
relying on the console.

Also correct the unrelated comment on the JSX prop-spread order — it
claimed `className` "takes precedence" over caller-passed `className`,
but `className={cn(buttonVariants(...), props.className)}` MERGES the
caller's value via `cn`. `disabled` and `onClick` DO take precedence
(they're declared after `{...props}`); `className` is merged. Tighten
the comment to match.

Call-site enumeration:
- `MarkdownCopyButton` — used by `mdx-registry.tsx`. No caller passes
  `disabled` or `onClick` today; the prop-spread order change is
  purely defensive. No caller passes a `className` that would conflict
  with the merge; `cn` handles tailwind-merge precedence correctly.
- Browser unhandled-rejection behavior — verified that Fumadocs's
  `useCopyButton` (read at `node_modules/fumadocs-ui/dist/utils/use-copy-button.js`)
  does NOT attach a `.catch`, so the throw produces a single
  unhandled-rejection log per failed click; no infinite loop.
2026-05-20 20:37:00 -07:00
Tyler Slaton 64ffd19d8c fix(showcase): CR Round 2 cleanup — ADK reasoning graph name + dead CSS + comment + log tag
CR Round 2 confirmation surfaced one bucket (a) finding plus three
bucket (b) trivials worth rolling in together.

(a) `google-adk/src/app/demos/reasoning-{default,custom}/page.tsx`
    comments said "Both demos share the same backend (`reasoning_agent`
    graph)". That graph name is the langgraph-python convention —
    `reasoning_agent.py` in LGP — but the ADK demo doesn't have a
    graph by that name. `src/agents/registry.py:144-145` maps both
    `reasoning-custom` and `reasoning-default` to
    `AgentSpec(_thinking_chat)`, where `_thinking_chat` is built via
    `build_thinking_chat_agent`. Round 1 fixed the same class of bug
    in langgraph-typescript (which uses `agentic-chat-reasoning`) but
    missed ADK; this is the matching fix.

(b1) `.../headless-simple/chat.tsx` (3 files) emitted
    `console.error("[headless-simple] ...", err)` with no
    integration-slug prefix. A user testing demos across frameworks
    in the same browser session couldn't tell which integration's
    runAgent failed. Tag with the framework slug:
    `[google-adk:headless-simple]`, `[langgraph-python:headless-simple]`,
    `[langgraph-typescript:headless-simple]`.

(b2) `globals.css` lines 133-137 — the `.shell-docs-sidebar
    p[class*="sidebar-item-offset"] svg` rule (4×4 icons in accent
    purple) was dead in fumadocs v16. The v16 sidebar emits separator
    `<p>` elements with `inline-flex items-center gap-2` instead of
    the v15 `sidebar-item-offset` class fragment; the live rule on
    `p.inline-flex.gap-2 svg` (added earlier in this PR) already
    handles the same styling at the correct 16×16 size. Drop the
    dead rule.

(b3) `page-actions.tsx` — the regression-fix commit
    (`0186ae9f2`) wedged `getClientBaseUrl()` between the cache-
    describing block comment and the actual `cache = new Map(...)`
    declaration. The comment now sits above its own subject again;
    `getClientBaseUrl()` keeps its own JSDoc above its definition.

Call-site enumeration:
- ADK `_thinking_chat` reference — verified in
  `showcase/integrations/google-adk/src/agents/registry.py` (line
  144-145 + `build_thinking_chat_agent` import on line 23 + builder
  invocation on line 108). Comment-only change; no symbol signatures
  touched.
- Headless log tags — only the literal log string changes; no other
  call site reads it.
- `globals.css` dead rule — verified no other selector in the file
  depends on the removed lines (the section-header SVG color is set
  by the surviving `p.inline-flex.gap-2 svg` rule).
- `page-actions.tsx` comment move — no functional change.
2026-05-20 20:20:12 -07:00
Tyler Slaton 0186ae9f28 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.
2026-05-20 20:12:03 -07:00
Tyler Slaton 04c6383990 fix(shell-docs): resolve launch.json port collision + harden preview script + untrack next-env.d.ts
`.claude/launch.json` declared port 3000 for both \`docs\` (Next.js at
docs/) and \`shell\` (Next.js at showcase/shell/) — only one could
actually start at a time, and Next's auto-port-fallback would land
\`shell\` on whatever was free without the launch config knowing.
Reassign \`shell\` to port 3004 (next free slot after the existing
3001/2/3 cluster) and pass \`-- --port 3004\` through \`npm run dev\`
so the runtime port matches the declared port.

\`.claude/preview/shell-docs.sh\` had a blanket
\`|| { echo "(may have failed — expected)" }\` after \`pnpm install\` that
swallowed every install failure, not just the documented \`lefthook\`
prepare-hook one. A real failure (network down, lockfile drift) would
get silently absorbed and then explode much later at the \`npx tsx\`
generator step with a confusing \`Cannot find module\` error. Verify
\`$SCRIPTS_DIR/node_modules\` exists after the install attempt; bail
with a clear instruction if it doesn't.

\`showcase/shell-docs/next-env.d.ts\` is a Next.js-auto-generated file
whose contents differ between \`next dev\` (\`./.next/dev/types/...\`)
and \`next build\` (\`./.next/types/...\`). Per Next.js's own
recommendation it should never be checked in — the v16 path change
would otherwise produce dirty trees on every build/dev switch, and a
clean checkout's typecheck would fail because the imported
\`.next/dev/types/routes.d.ts\` is itself gitignored. Add the file to
\`.gitignore\` (matching the existing \`docs/next-env.d.ts\` entry) and
\`git rm --cached\` to untrack the committed copy. Next regenerates it
on first \`next dev\`/\`next build\`.

Call-site enumeration:
- \`.claude/launch.json\` — no callers within the repo; the
  \`/run\` slash command reads it as data. Port change is non-breaking
  for any other tooling that doesn't bind to 3000 for \`shell\`.
- \`.claude/preview/shell-docs.sh\` — the lefthook installer is the
  only thing that runs it (besides interactive users); both flows
  benefit from the loud failure.
- \`next-env.d.ts\` — no source file imports from it; the file is a
  TypeScript \`/// <reference\` declaration consumed by tsc only,
  regenerated on each build/dev.
2026-05-20 20:01:16 -07:00
Tyler Slaton 086e68c88b fix(showcase): log runAgent errors in headless-simple; correct LGT reasoning graph name
The Headless Simple demo's `chat.tsx` swallowed every `runAgent`
rejection with an empty arrow catch:

    void copilotkit.runAgent({ agent }).catch(() => {});

This is the canonical "two hooks, your design system" example users
copy-paste as a starting point — silent swallow modeled broken practice
to every CopilotKit user, and the @region[use-agent-simple] block we
inline into `/<framework>/headless` docs surfaces the anti-pattern as
the recommended snippet. Replace the empty catch with a
`console.error("[headless-simple] runAgent failed", err)` so network
failures, transport disconnects, and runtime errors surface in the
developer's console. Applied across google-adk, langgraph-python, and
langgraph-typescript variants.

`langgraph-typescript/src/app/demos/reasoning-default/page.tsx` had a
comment claiming the demo backed onto the `reasoning_agent` graph, but
the LGT route map in `src/app/api/copilotkit/route.ts` actually points
both `reasoning-default` and `reasoning-custom` at the
`agentic-chat-reasoning` graph (the companion `reasoning-custom/page.tsx`
comment already gets this right). The `reasoning_agent` label is the
Python / ADK convention. Update the comment to match the TS route map.

Call-site enumeration:
- `copilotkit.runAgent` (in headless-simple/chat.tsx, 3 files) — the
  return value is `Promise<void>`; existing callers don't await it, so
  swapping the catch is non-breaking. The previous `void` operator
  already discarded the promise value, so the runtime behavior of the
  surrounding `send()` is unchanged.
- LGT `reasoning-default` page.tsx — comment-only change, no symbol
  signatures touched.
2026-05-20 19:59:23 -07:00
Tyler Slaton 3700b885b0 fix(shell-docs): guard SidebarFolderStatePreserver synthetic clicks + fix PopoverClose export
`SidebarFolderStatePreserver` had two silent `catch {}` blocks (read /
write of the saved state map) — log via `console.warn` so a user whose
folders keep resetting can diagnose the underlying storage failure
(SecurityError on third-party iframes / privacy mode, QuotaExceeded,
corrupted JSON).

The restore-on-mount effect called `trigger.click()` to flip Radix's
state to the saved value. That synthetic click bubbles to the
delegated `#nd-sidebar` click handler, which then records the new
state — but if Radix's `data-state` hadn't updated by the next
`requestAnimationFrame` (transient animation, mount race), the
recorded value could overwrite the user's saved preference with the
live value the restore just tried to flip. Add a module-level
`WeakSet<HTMLButtonElement>` of in-progress synthetic clicks; the
delegated handler skips entries in the set. The flag is cleared on the
next rAF, by which point any genuine user click will fire against an
unmarked trigger.

`popover.tsx` exported `PopoverClose = PopoverPrimitive.PopoverClose`,
but Radix UI's actual export is `PopoverPrimitive.Close`. The
expression resolved to `undefined`, so any caller rendering
`<PopoverClose />` would have thrown React's "Element type is invalid:
expected a string ... but got undefined" error. The shadcn-style
scaffold the Fumadocs CLI generated had the symbol name wrong; fix the
re-export to `PopoverPrimitive.Close`.

Call-site enumeration:
- `SidebarFolderStatePreserver` — used only by `ShellDocsLayout`.
  Behavior change is purely additive (logs on previously-silent
  errors; suppresses synthetic clicks the previous code already
  intended to be no-ops).
- `PopoverClose` — confirmed via grep that no caller exists yet; this
  is a defensive fix to a fresh scaffold.
- `Popover`, `PopoverTrigger`, `PopoverContent` — unchanged.
2026-05-20 19:57:26 -07:00
Tyler Slaton 9417b2e74f fix(shell-docs): emit framework root URLs in llms.txt; log silent reads; drop dead CONTENT_DIR fallback
`getAllLlmPages` was silently dropping framework root pages from
`/llms.txt`. `walkMdx` strips trailing `/index` from yielded slugs, so
`integrations/<folder>/index.mdx` arrived as `slug === ""` — and the
`if (!slug) continue` guard in section 2 then skipped it. Result: LLM
crawlers walking `/llms.txt` never saw `/langgraph-python`,
`/built-in-agent`, etc., so the framework landing pages were invisible
to the LLM index even though they're the canonical entry points.

Treat empty slug as the framework root and emit it as the bare
integration URL (mirroring how sections 3 and 4 already handle
reference/ag-ui index pages). The `loadSlug` falls back to
`integrations/<folder>/index` so `loadDoc()` can still resolve the
source.

Two silent `catch {}` blocks in the same file were dropping read /
parse errors with no diagnostic — `readMetaFromFile` would mask
malformed YAML (page appears bare in `/llms.txt` with no signal to the
author) and `readSource` would mask filesystem errors (the body gets
quietly dropped by the route handler's `if (!body) continue` guard).
Add `console.error` with a `[llm-text]` prefix in both, matching the
pattern `readTitle` in `docs-render.tsx` already uses.

`findExistingMdx` in `llms-mdx/[[...slug]]/route.ts` had a `void
CONTENT_DIR;` dead statement with a comment promising a `CONTENT_DIR`
fallback that didn't exist — the `import { CONTENT_DIR }` was only
kept alive by that no-op. Remove the dead statement, drop the unused
import, and log when the path-traversal guard rejects a candidate (the
previous silent `continue` meant a typo'd slug just 404'd with no log
to correlate the request).

`llms.txt/route.ts` had a comment claiming "no explicit revalidate
directive — Next defaults to dynamic" directly above
`export const revalidate = false`. The directive is intentional (cache
the slow filesystem walk indefinitely on the server; let the
per-response Cache-Control header drive CDN/client freshness) — update
both `llms.txt` and `llms-full.txt` comments to explain the two-cache
layering instead of contradicting it.

Call-site enumeration:
- `getAllLlmPages` — used by `/llms.txt`, `/llms-full.txt`, and
  `/llms-mdx` route handlers. No caller depends on whether framework
  roots are present; widening the result set is purely additive.
- `readMetaFromFile`, `readSource` — file-local helpers; only
  `getAllLlmPages` / `renderPageToLlmText` call them. New `console.error`
  is additive.
- `CONTENT_DIR` (removed import) — confirmed via grep that
  `/llms-mdx/[[...slug]]/route.ts` no longer references the symbol;
  `loadDoc` is still imported from the same module.
2026-05-20 19:55:58 -07:00
Tyler Slaton 4a951848f7 fix(shell-docs): harden MarkdownCopyButton + fix ViewOptionsPopover hydration
`MarkdownCopyButton` had four bugs that compounded into a permanently-broken
button on the first transient failure:

1. The module-scope `cache` stored the in-flight `Promise<string>` itself.
   If `fetch` rejected (offline, 5xx), the rejected promise stayed cached
   and every subsequent click awaited it again — the button stayed dead
   until a full page reload.
2. `fetch(markdownUrl).then(res => res.text())` never checked `res.ok`.
   A 404 from the `/llms-mdx/[[...slug]]` route gets coerced to the body
   string "Not found" and silently pasted into the user's clipboard.
3. `try/finally` had no `catch`, so the awaited promise rejection
   escaped as an unhandled rejection with no diagnostic.
4. JSX spread order put `disabled` and `onClick` BEFORE `{...props}`,
   so any caller passing those props could override the loading guard
   and the copy handler.

Rework as one code path: `fetchMarkdown(url)` does the network work,
checks `res.ok`, caches only the resolved STRING on success, and throws
otherwise. The click handler wraps the whole thing in `try/catch/finally`
so the loader state is symmetric on cache-hit and cache-miss, errors
log via `console.error` for diagnostics, and re-throws so
`useCopyButton` doesn't flip the checkmark on failure. Spread caller
props first so component-owned `disabled`/`onClick` win.

`ViewOptionsPopover` had an SSR/CSR divergence: `pageUrl` was the bare
pathname on the server (`/quickstart`) and a `URL` object on the client
(`https://docs.copilotkit.ai/quickstart`). Result: hydration mismatch
warnings on every deep-link anchor AND broken deep-links in the SSR
HTML (the LLM apps can't resolve a relative path). Use the existing
`getBaseUrl()` from `lib/sitemap-helpers.ts` (which reads
`NEXT_PUBLIC_BASE_URL` with a production fallback) so the absolute URL
is computed deterministically on both sides.

Call-site enumeration:
- `MarkdownCopyButton` — used by `mdx-registry.tsx` (the global
  `<MarkdownCopyButton />` MDX component); call sites pass only
  `markdownUrl`. Prop-spread change is non-breaking — no caller passes
  `disabled` or `onClick` today.
- `ViewOptionsPopover` — used by the same registry entry; no public
  callers depend on the SSR-vs-client pageUrl shape.
- `fetchMarkdown` (new exported-internal helper) — used only by
  `MarkdownCopyButton`; no other importer.
2026-05-20 19:52:58 -07:00
Tyler Slaton 5728611dfd feat(shell-docs): upgrade to fumadocs 16 / next 16, polish layout, add llms.txt + page actions
Stack upgrade
- fumadocs-core/ui 15.8.5 → 16.8.12, next 15 → 16 (Turbopack), react 19 → 19.2
- Swap "next lint" → "oxlint ." to match the rest of the repo
- New deps for the page-actions component: @radix-ui/react-popover,
  class-variance-authority, clsx, tailwind-merge

Layout & brand polish
- Sidebar floats as a rounded-2xl card with column-aligned padding;
  framework picker pill, accent-purple section icons (16px), accent
  active state, and a single divider line at the footer
- New custom <ThemeSwitch> — single 50×28 neutral switch replaces the
  fumadocs sun/moon split (drops the vertical divider and purple tint)
- Sidebar folder collapse state persists across navigations via
  SidebarFolderStatePreserver
- BrandNav: wider top bar, lowercase "Talk to an engineer", BookIcon
  for Docs, GitHub/Discord icons rendered inline in our footer row
- Mobile: nav clipping + content padding fixes, content grid-span-full
- TOC-less pages: lift article max-width so content stretches into the
  empty TOC column on wide viewports

New routes
- /llms.txt — page index per fumadocs LLMs integration
- /llms-full.txt — concatenated full text of every docs page
- /<path>.md and /<path>.mdx — per-page raw markdown with <Snippet>
  regions inlined as fenced code blocks (resolver in lib/llm-text.ts
  reuses the same demo-content.json the <Snippet> runtime reads)
- Page-actions bar: Copy Markdown + Open in Claude / Claude Code /
  Windsurf / Codex (Codex links to https://chatgpt.com/codex for
  universal coverage)

Content fixes
- Reasoning page (generative-ui/reasoning.mdx): rewrite to point at
  the real reasoning-default / reasoning-custom cells instead of the
  stale agentic-chat-reasoning / reasoning-default-render names
- Strip <FeatureIntegrations /> chip list ("SUPPORTED BY ...") from
  16 docs MDX files (component definition kept in mdx-registry)
- Drop hideTOC: true from 11 pages so they pick up the lifted-cap rule
- Default home (/) to the built-in-agent authored sidebar; fix active
  state matching on the home url
- Restore default fumadocs Callout (drop the bespoke docs-callout)
- OpsPlatformCTA redesign — light bordered card with accent stripe
- FrameworkOverview redesign — drop atmospheric chrome, smaller hero
- Homepage / docs-landing redesign

Integrations (LGP / LGT / ADK)
- Tag @region[default-reasoning-zero-config] in reasoning-default and
  @region[reasoning-block-render] in reasoning-custom for all three
  frameworks so the docs <Snippet> calls resolve
- Tag @region[use-agent-simple] + @region[message-list-simple] in
  headless-simple and @region[use-rendered-messages-hook] +
  @region[manual-tool-call-rendering] +
  @region[manual-activity-message-rendering] + @region[custom-bubbles]
  across headless-complete

Other
- docs/components/layout/mobile-sidebar.tsx: lowercase "engineer" to
  match shell-docs
- .claude/launch.json + .claude/preview/ — dev launch configs for the
  worktree so /preview brings up shell-docs on :3003
2026-05-20 19:32:18 -07:00
Sam Julien 6fe3aef833 feat(shell-docs): align navbar CTAs with EIP, surface DLAI course banner (#4945)
## Summary

Brings the shell-docs navbar, mobile nav, and top banner into line with
the current product surfaces.

- **Navbar CTA**: relabeled `Free Developer Access` → **`Get
Intelligence free`** and re-icon'd from Cloud → Lightbulb. The
destination (Enterprise Intelligence Platform sign-up) is unchanged; the
prior label and cloud icon misrepresented it as the retired Copilot
Cloud. Matches the in-content `OpsPlatformCTA` default that the rest of
the docs already use.
- **Navbar CTA breakpoint**: aligned with `Docs` / `Reference`. No
longer hidden below 1100px — all three left-cluster links share the same
icon-hide behavior at <808px.
- **Top banner**: swapped from "MCP Apps support" to the DeepLearning.AI
[Build Interactive Agents with Generative
UI](https://www.deeplearning.ai/short-courses/build-interactive-agents-with-generative-ui/)
short course, which is already referenced from 6 generative-UI pages in
the docs. External banner links now open in a new tab.
- **Mobile top nav**: gains the Talk-to-Engineer (gradient pill +
calendar icon) and Get Intelligence free (lightbulb) CTAs from the
desktop cluster. PostHog events match desktop (`talk_to_us_clicked`,
`try_for_free_clicked`) with `location: "docs_navbar_mobile"` so
analytics can split mobile from desktop.

`INTELLIGENCE_CTA_HREF` and `TALK_TO_ENGINEER_HREF` are now exported
from `brand-nav.tsx` so `MobileTopNav` reuses the same URLs.

## Test plan

- [ ] Desktop navbar shows lightbulb + "Get Intelligence free" with
external-link glyph; clicking opens EIP dashboard sign-up in a new tab
and fires `try_for_free_clicked` with `location: "docs_navbar_left"`.
- [ ] Resize between 768px and 1100px: all three left links (Docs /
Reference / Get Intelligence free) stay visible and lose their icons
together at <808px.
- [ ] Top banner displays the DLAI course CTA; "Start the course" opens
the DeepLearning.AI page in a new tab. Dismiss persists for 3 days.
- [ ] Mobile top nav (<768px) shows lightbulb + calendar pill before
search/burger; both clicks fire the matching PostHog event with
`location: "docs_navbar_mobile"` and open the right destination.

## Notes

Pre-commit hooks were skipped: `test-and-check-packages` fails on clean
`main` in this worktree (pre-existing failures in `@copilotkit/shared`,
`@copilotkit/core`, `@copilotkit/sdk-js`, `@copilotkit/runtime` —
confirmed by stashing this branch's changes and reproducing). None of
those packages are touched by this change; CI on the PR will run the
same checks.
2026-05-20 17:17:41 -07:00
Sam Julien 8bb4260e84 refactor(shell-docs): use lucide icons for nav CTAs
Replaces the generated lightbulb SVG and the inline calendar SVG in the
mobile nav with the corresponding lucide-react icons. Avoids the
maintenance overhead of hand-rolled icon components per CR feedback.
2026-05-20 17:03:43 -07:00
Sam Julien c22841c76c fix(shell-docs): render UnsupportedBox in InlineDemo when (framework × demo) is unsupported (#4903)
## Summary

After the `docs_mode` cutover, only generated-mode frameworks
(langgraph-python, langgraph-typescript, google-adk) render the agnostic
shell-docs pages that embed `<InlineDemo>`. Authored-mode frameworks
render their own ported MDX and don't reach this path.

Three (framework × demo) pairs in generated mode are catalog-marked
`unsupported` but still get embedded on agnostic pages. Pre-fix, all six
pages iframe a backend route that 404s and ship an "Open full demo →"
link to nowhere. The `Snippet` component on these same pages already
handles the unsupported case via `UnsupportedBox`.

| Framework | Unsupported demo | Broken pages |
|---|---|---|
| langgraph-typescript | shared-state-streaming |
`/langgraph-typescript/generative-ui/state-rendering`,
`/langgraph-typescript/shared-state/streaming` |
| google-adk | gen-ui-interrupt | `/google-adk/human-in-the-loop`,
`/google-adk/human-in-the-loop/useInterrupt`,
`/google-adk/generative-ui/interactive` |
| google-adk | interrupt-headless |
`/google-adk/human-in-the-loop/headless` |

This PR aligns `InlineDemo` with `Snippet`: it looks up the `(framework
× demo)` pair in the bundled catalog and short-circuits to
`UnsupportedBox` when the status is `unsupported`. Supported pairs
continue to render the Demo / Code tabs unchanged.

## Changes

- `showcase/shell-docs/src/components/snippet.tsx`: export the existing
`UnsupportedBox` component so it can be reused.
- `showcase/shell-docs/src/lib/mdx-registry.tsx`: in `InlineDemo`, after
the existing `!integration || !demo` and `!int || !int.deployed` guards,
look up the catalog entry for `${integration}::${demo}` and
short-circuit to `UnsupportedBox` when the status is `unsupported`.
Builds a local `inlineDemoCatalogByKey` Map at module scope from
`data/catalog.json` (same source `Snippet` reads).

## Verification

- `showcase/shell-docs/src/data/catalog.json` confirms the three
unsupported pairs above. Source-of-truth path: each integration's
`manifest.yaml` `not_supported_features` array → `determineCellStatus()`
in `showcase/scripts/generate-registry.ts` emits `"unsupported"`.
- No regression for supported `(framework × demo)` pairs — the Demo /
Code tabs block is unchanged.
- Existing `!integration || !demo` and `!int || !int.deployed` guards
(with `warnSilentNull`) are preserved.

## Test plan

- [ ] Visit `/langgraph-typescript/generative-ui/state-rendering` —
confirm the broken iframe is replaced by an `UnsupportedBox`.
- [ ] Visit `/google-adk/human-in-the-loop` — same expectation for
`gen-ui-interrupt`.
- [ ] Visit `/google-adk/human-in-the-loop/headless` — same expectation
for `interrupt-headless`.
- [ ] Visit `/langgraph-python/generative-ui/state-rendering` (supported
pair) — confirm the live Demo / Code tabs still render.
- [ ] Visit any other agnostic page with `<InlineDemo>` on a supported
pair — confirm no regression.
2026-05-20 17:00:04 -07:00
Sam Julien dba3e2bf15 feat(shell-docs): align navbar CTAs with EIP, surface DLAI course banner
Brings the docs navbar, mobile nav, and top banner in line with the
current product surfaces:

- Navbar CTA relabeled "Free Developer Access" -> "Get Intelligence
  free" and reicon'd from Cloud to Lightbulb, matching the in-content
  OpsPlatformCTA default. The destination (Enterprise Intelligence
  Platform sign-up) is unchanged; the prior label and cloud icon
  misrepresented it as Copilot Cloud.
- CTA breakpoint aligned with Docs/Reference: no longer hidden below
  1100px. All three left-cluster links now share the same icon-hide
  behavior at <808px.
- Top banner content swapped from "MCP Apps support" to the
  DeepLearning.AI "Build Interactive Agents with Generative UI" short
  course, which is already referenced from 6 generative-UI pages.
  External banner links now open in a new tab.
- Mobile top nav gains the Talk-to-Engineer (gradient pill, calendar
  icon) and Get Intelligence free (lightbulb) CTAs from the desktop
  cluster, with the canonical mobile PostHog location string
  (docs_navbar_mobile) so analytics can split mobile from desktop.

INTELLIGENCE_CTA_HREF and TALK_TO_ENGINEER_HREF are exported from
brand-nav so MobileTopNav reuses the same URLs.
2026-05-20 16:45:04 -07:00
Sam Julien 473164300f fix(shell-docs): render UnsupportedBox in InlineDemo when (framework × demo) is unsupported
After Tyler's docs_mode cutover (cca94aa8e), only generated-mode
frameworks (langgraph-python, langgraph-typescript, google-adk) render
the agnostic shell-docs pages that embed <InlineDemo>. Authored-mode
frameworks render their own ported MDX and don't reach this path.

Three (framework × demo) pairs in generated mode are catalog-marked
`unsupported` but still get embedded on agnostic pages:

  langgraph-typescript  ✗ shared-state-streaming
    /langgraph-typescript/generative-ui/state-rendering
    /langgraph-typescript/shared-state/streaming
  google-adk            ✗ gen-ui-interrupt
    /google-adk/human-in-the-loop
    /google-adk/human-in-the-loop/useInterrupt
    /google-adk/generative-ui/interactive
  google-adk            ✗ interrupt-headless
    /google-adk/human-in-the-loop/headless

Pre-fix, all six iframe a backend route that 404s and ship an
"Open full demo →" link to nowhere. The Snippet component on these
same pages already handles the unsupported case via UnsupportedBox.

This aligns InlineDemo with Snippet: look up the (framework × demo)
pair in the bundled catalog and short-circuit to UnsupportedBox when
status is `unsupported`. Supported pairs continue to render the
Demo / Code tabs unchanged.

Changes:
- snippet.tsx: export the existing UnsupportedBox component for reuse.
- mdx-registry.tsx: after the existing !integration/!demo and
  !int/!int.deployed guards, look up `${integration}::${demo}` in a
  module-scope Map built from data/catalog.json (same source Snippet
  reads) and return UnsupportedBox when status is `unsupported`.
2026-05-20 15:57:16 -07:00
Sam Julien 9aa1db57ae fix(shell-dashboard): route dashboard docs cell to canonical docs host (#4804)
## Summary

- Shell pre-cutover fix. Flip the dashboard cell "Docs" link in
`showcase/shell-dashboard/src/components/cell-pieces.tsx:59` from
`${shellUrl}/${slug}${shellPath}` to
`https://docs.copilotkit.ai/${slug}${shellPath}`.
- Pre-cutover this CNAME serves the Vercel docs site; post-cutover it
serves shell-docs on Railway. Both resolve `/<framework>/<slug>`
correctly, so the change is safe across the DNS flip window.
- Demo + Code iframe links (lines 20, 21) intentionally untouched — they
iframe `/integrations/[slug]/[demo]/{preview,code}` routes that only
`showcase/shell` serves today; porting those is post-cutover work.

## Why this is flip-blocking

After the `docs.copilotkit.ai` DNS flip, `${shellUrl}` (=
`showcase.copilotkit.ai`) no longer serves `/docs/**` — those routes
were ripped out and the redirect middleware moved to shell-docs in PR
#4702 (merged 2026-05-08). Without this change, every dashboard "Docs"
cell goes 404 at T-0 of the flip.

## Test plan

- [ ] CI green
- [ ] Smoke locally: `pnpm nx run
@copilotkit/showcase-shell-dashboard:dev`, hover any cell's Docs link,
confirm href starts with `https://docs.copilotkit.ai/`
- [ ] After merge + Railway redeploy of shell-dashboard, hit production
and re-verify the href
2026-05-20 15:14:59 -07:00
Jordan Ritter 4772ee1acc fix(showcase/langgraph-typescript): align manifest and styling to langgraph-python (#4942)
## Summary

Brings LangGraph TypeScript showcase to parity with LangGraph Python
(north-star) on both demo metadata and visual styling.

### Manifest fixes
- **Dropped phantom slugs.** `hitl-in-chat-booking` had no folder of its
own and pointed to the same route as `hitl-in-chat` — two slugs rendered
the same demo. `hitl` was a legacy entry Python had already removed.
- **Added missing `shared-state-read`.** The folder existed on disk with
a working page; the manifest just wasn't surfacing it.
- **Renamed 23 demos** to match the canonical names in
`shared/feature-registry.json` (Python already does). Examples:
`"Agentic Chat"` → `"Pre-Built: CopilotChat"`, `"In-Chat HITL
(useHumanInTheLoop — ergonomic API)"` → `"Human In the Loop: In-chat"`,
`"Voice Input"` → `"Voice"`. Matched Python on the 3 tool-rendering
variants where canonical and Python disagreed.
- Dropped the now-obsolete duplicate-routes comment in
`demos/layout.tsx`.

### Styling fixes
Ported `langgraph-python/src/app/globals.css` verbatim. Notable effects:
- **Adds the `@theme inline` Tailwind v4 block** so shadcn / AI Elements
/ prompt-kit primitives actually pick up the design tokens. Without this
they fall back to no styling.
- **Constrains `html`/`body` to 100% with `overflow: hidden`** so
flex-centered chats render in the middle instead of anchored to the top
— this is the visible bug that motivated the styling pass.
- **Switches the brand color** from `#0066ff` blue to `#0d6e3f`
CopilotKit green and adds the response-button color tokens.
- Adds the Radix overlay scroll-lock fix, `.demo-card` utility, and
`.slot-marker` Slot Atlas styling.
- Deletes `copilotkit-overrides.css` and its import — the rounded-input
rule it added is already covered by the v2 core styles.

After this PR, `globals.css` is byte-identical to Python's, and the
registry has zero slug or name divergence between LGT and LGP.

## Test plan
- [x] `npx tsx scripts/generate-registry.ts` regenerates clean (all 18
integrations, no schema errors).
- [x] `npx tsx scripts/validate-parity.ts` reports `[PASS]` for
`langgraph-typescript`.
- [x] Registry diff vs Python: 0 slugs in TS not in PY, 0 slugs in PY
not in TS, 0 name mismatches, 0 duplicate routes.
- [x] `oxfmt --check` and `oxlint` clean on touched files.
- [ ] Visual smoke after deploy: chat is vertically centered, brand
color is green, shadcn primitives render styled.
2026-05-20 14:52:24 -07:00
Austin Merrick 1bd593609c fix(ci): rebuild better-sqlite3 after pnpm cache restore to fix Node ABI mismatch (#4938)
## Summary

Fixes a probabilistic CI failure in the `test / unit` matrix where Node
20.x and 22.x jobs would fail with `Module did not self-register` on
`better-sqlite3`.

### Root cause

`setup-node`'s built-in `cache: "pnpm"` uses a cache key of
`node-cache-Linux-pnpm-{lockfileHash}` — **no Node.js version in the
key**. All three matrix jobs (Node 20, 22, 24) share one cache entry.

The failure is **probabilistic, not always-present**:

- **Cold run (first after cache reset):** all three jobs install fresh
and pass. Whichever job finishes first writes its ABI-specific
`better-sqlite3` binary to the shared cache key.
- **Warm run (all subsequent runs):** all three jobs restore from the
same cache entry. Only the job whose Node version matches the cached
binary passes. The other two fail.

This explains the timing: failures started ~May 19 after the
`setup-node` v4→v6 and `pnpm/action-setup` v4→v6 dependabot bumps (May
15, PRs #4857/#4858) reset the cold cache. The first warm run after that
was broken for whichever two Node versions didn't win the write race.

This is a [known upstream bug in
setup-node](https://github.com/actions/setup-node/issues/1171) (issue
#1171, fix pending in PR #1172, not yet in any release).

### Fix

Replace `cache: "pnpm"` in `setup-node` with a manual `actions/cache`
step that scopes the key to the Node.js version:

```
key: ${{ runner.os }}-pnpm-store-${{ matrix.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}
```

Each Node version now has its own isolated pnpm store cache entry. An
ABI-137 binary cached by a Node 24 job can never be served to a Node 20
or 22 job.

The step uses `$GITHUB_OUTPUT` (scoped step output) instead of
`$GITHUB_ENV` per GitHub's security hardening guide.

## Test plan

### Local reproduction (primary evidence)

`better-sqlite3` uses classic ABI (`NODE_MODULE_INIT`, not N-API), so
its binaries are Node-version-specific. With a Node 24 binary in place,
switching to Node 22 without reinstalling reproduces the exact CI
failure:

```
# Binary compiled under Node 24 (ABI 137) — works fine:
$ node --version && node -e "new (require('better-sqlite3'))(':memory:').prepare('SELECT 1').get()"
v24.5.0
{ '1': 1 }

# Same binary, switched to Node 22 (ABI 127) without reinstalling — fails:
$ node-22 -e "new (require('better-sqlite3'))(':memory:')"
Error: The module was compiled against a different Node.js version using
NODE_MODULE_VERSION 137. This version of Node.js requires NODE_MODULE_VERSION 127.
Please try re-compiling or re-installing the module.
```

This is exactly what the shared cache was doing to Node 20/22 jobs:
handing them a binary compiled for a different ABI.

### CI validation

Two runs on this PR confirm the fix end-to-end:

1. **Cold-cache run**
([26185670340](https://github.com/CopilotKit/CopilotKit/actions/runs/26185670340)):
all three jobs install fresh, each writes its own per-version cache
entry, all pass.

2. **Warm-cache run**
([26187495270](https://github.com/CopilotKit/CopilotKit/actions/runs/26187495270)):
each job restores from its own per-version cache entry — the exact
scenario that was previously broken. All pass, with install times
dropping from ~22s (cold, downloading) to 7–10s (warm, linking from
store), confirming real cache hits were served.

| Job | Result | Cache restore | Install |
|-----|--------|--------------|---------|
| unit (20.x) | ✅ pass | 12s (hit) | 8s |
| unit (22.x) | ✅ pass | 12s (hit) | 7s |
| unit (24.x) | ✅ pass | 14s (hit) | 10s |
2026-05-20 14:39:44 -07:00
Tyler Slaton 2be82266b1 fix(showcase/langgraph-typescript): align manifest and styling to langgraph-python
Manifest:
- Drop phantom hitl and hitl-in-chat-booking slugs (duplicate route, no
  matching folder).
- Add missing shared-state-read entry (folder existed on disk but was
  never surfaced).
- Rename 23 demos to match the canonical names in
  shared/feature-registry.json (and match Python on the 3 tool-rendering
  variants where canonical and Python disagreed).
- Drop the now-obsolete duplicate-routes comment in demos/layout.tsx.

Styling (port langgraph-python/src/app/globals.css verbatim):
- Add the @theme inline block so shadcn/AI-Elements/prompt-kit primitives
  actually pick up the design tokens (Tailwind v4).
- Constrain html/body to 100% with overflow:hidden so flex-centered chats
  render in the middle instead of anchored to the top.
- Switch brand color from #0066ff blue to #0d6e3f CopilotKit green; add
  the response-button color tokens.
- Add the Radix overlay scroll-lock fix, .demo-card utility, and
  .slot-marker Slot Atlas styling.
- Delete copilotkit-overrides.css and its import; the rounded-input rule
  it added is already covered by the v2 core styles.
2026-05-20 14:38:03 -07:00
Sam Julien d28f715d91 feat(shell-docs): docs UX polish — Setup as page narrative, demo positioning, landing redesign (#4936)
## What does this PR do?

Bundle of UX improvements to shell-docs feature pages so they read
better cold. Touches the previously-shipped \`<FrameworkSetup>\` system,
the \`<InlineDemo>\` component, the framework landing pages, and the
quickstart/prebuilt-component docs. All within shell-docs and the
per-framework integration concept files — no runtime / SDK changes.

### Setup section: integrated narrative + collapsed install

The biggest behavioral change. Previously \`<FrameworkSetup>\` rendered
a free-standing \`## Setup\` section above "How it works in code" with a
2-step Steps block (install + middleware wiring). An unbiased subagent
review on
\`/langgraph-python/generative-ui/{tool-based,state-rendering}\` flagged
this as noise — duplicated content visible later on the same pages in
fuller context.

New shape on every page that still uses Setup:

- The slot now lives **inside** the first code-bearing section
(typically "How it works in code") as its first child, so it integrates
with the section's own explanation.
- \`<FrameworkSetup>\` no longer wraps its body in an outer Accordion.
The concept author owns the structure.
- Each framework's \`agent-setup.mdx\` is restructured as:
1. An integrated narrative paragraph that names the framework's wiring
primitive (\`CopilotKitMiddleware\` for LGP /
\`CopilotKitStateAnnotation\` for LGT / \`AGUIToolset\` for ADK) and
what it does in plain prose
2. A \`<DemoCode>\` excerpt showing the actual wiring (full Shiki
highlighting + copy button)
3. A collapsed \`<Accordion title="Install the SDK">\` containing just
the install command + a brief "why"

Resulting reader experience: the middleware wiring reads as page
narrative, the boilerplate install command is one click away but doesn't
visually compete.

Also:
- Per-page concept names (\`frontend-tools-setup\`,
\`shared-state-setup\`, \`subagents-setup\`, etc.) collapsed to one
universal \`agent-setup\` concept — same shape across every page, each
framework decides what to ship.
- \`/generative-ui/state-rendering\` Setup slot **removed entirely** —
its existing \`state-streaming-middleware\` Snippet already shows the
wiring in fuller context, so the Setup was pure duplication.

### Demo positioning + visual treatment

- \`<InlineDemo>\` wrapper height changed (500px → 550px) and the inner
iframe **zoomed out 30%** via CSS transform. Implementation: iframe
sized to \`(100% / 0.7) × (550px / 0.7)\` and \`transform: scale(0.7)\`.
Net result: more demo content fits in the visible 550px viewport —
composer + suggested prompts + early messages visible at once, where
before some were clipped.
- First top-level \`<InlineDemo>\` on **31 agnostic docs pages** moved
to sit immediately after the frontmatter (was buried after a "What is
this?" intro paragraph). The live demo is the page's primary visual
anchor — it should be the first thing readers see, not the third.
- Leading \`<video>\` on **12 framework quickstart pages** moved to the
end of the file. Quickstart guides need install steps first; the demo
video is a closer.

### Landing page redesign

\`framework-overview.tsx\` (the per-framework landing at
\`/<framework>\`) reworked by a frontend-design subagent. Subtle accent
glow atmospherics, confident hierarchy (eyebrow breadcrumb + icon lockup
+ 3-3.75rem display headline), action cluster with copy-init-command
chip, numbered milestone-list treatment for features, \`SectionEyebrow\`
rhythm, slim "Where to next" grid replacing chunky footer cards.
Sparse-data handling preserved — every section conditional on its data
field. MDX adapter (\`mdx-framework-overview.tsx\`) untouched so
authored \`index.mdx\` files (Mastra, etc.) still render through the
same pipeline.

### Content cleanup

- Gif/demo images removed from
\`/prebuilt-components/{chat,sidebar,popup}\` on generated frameworks
(LGP/LGT/ADK). With the live \`<InlineDemo>\` now at the top, the static
gif was redundant — the demo IS the gif, just interactive. Authored
frameworks have their own copies and are unaffected.

## What this doesn't do

- The 18 unused per-page concept files (\`frontend-tools-setup.mdx\`,
\`shared-state-setup.mdx\`, etc. × 3 frameworks) are now dead code on
disk. Cleanup is a follow-up.
- Subagent review surfaced other issues (frontend snippets too thin on
some pages, no "what next" footer, pre-loaded demo states) — out of
scope for this round.
- Pre-existing \`.next/types/app/layout.ts\` typecheck error about
\`RESERVED_ROUTE_SLUGS\` is unrelated and untouched.

## Verification

- 32/32 vitest pass (\`cd showcase/shell-docs && npx vitest run\`)
- Typecheck clean modulo the pre-existing layout.ts error (\`npx tsc
--noEmit\`)
- \`probe-shell-docs.ts\` at 618/618 OK against the dev server
- Visual smoke verified on
\`/langgraph-python/generative-ui/{tool-based,state-rendering}\`,
\`/langgraph-typescript/generative-ui/tool-based\`,
\`/google-adk/generative-ui/tool-based\`,
\`/mastra/prebuilt-components/chat\` (authored-mode unaffected),
\`/langgraph-python/prebuilt-components/chat\`,
\`/langgraph-python/quickstart\`

## Test plan

- [ ] Pull the branch, run \`cd showcase/shell-docs && pnpm dev\`, hit
\`/langgraph-python/generative-ui/tool-based\` and confirm:
\`<InlineDemo>\` is the first thing under the title, "How it works in
code" contains an integrated paragraph about \`CopilotKitMiddleware\` +
the middleware code excerpt + a collapsed "Install the SDK" accordion,
then the original \`useComponent\` content
- [ ] Click the "Install the SDK" accordion to confirm it expands
cleanly with the install tabs (uv/poetry/pip/conda for LGP)
- [ ] Switch to \`/langgraph-typescript/generative-ui/tool-based\` and
confirm the same shape but with \`CopilotKitStateAnnotation\` narrative
+ \`npm install @copilotkit/sdk-js\` accordion
- [ ] Switch to \`/google-adk/generative-ui/tool-based\` and confirm
\`AGUIToolset\` narrative + \`pip install ag-ui-adk\` accordion
- [ ] Hit \`/langgraph-python\` and visually compare the new landing
page against \`main\` — should feel more polished
- [ ] Hit \`/langgraph-python/quickstart\` and confirm the demo video
appears at the bottom of the page, not the top
- [ ] Hit \`/mastra/prebuilt-components/chat\` and confirm the gif is
still there (authored framework, unaffected by the cleanup)

## Related PRs and Issues

- Builds on the framework-setup-snippets system shipped in commits
\`a805a8468\`, \`9e7fd38c6\`, \`134cd471a\` on the same branch.

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-20 13:58:43 -07:00
Tyler Slaton 1511ddc82a feat(shell-docs): add page-actions bar (Copy Markdown + Open in <LLM>)
Fills the whitespace between the doc title/description and the first
body section with a Fumadocs-style page-actions affordance:

  - "Copy Markdown" button: copies the raw MDX source of the current
    page to the clipboard, with a 2-second "Copied" confirmation.
  - "Open" dropdown: GitHub, ChatGPT, Claude, T3 Chat. Each LLM
    option opens the provider with a pre-filled prompt that embeds
    the current page URL (sans fragment).

Why this is custom rather than the upstream component: Fumadocs's
own docs (https://www.fumadocs.dev/docs/integrations/llms#page-actions)
describe a built-in `LLMCopyButton` + `ViewOptions`, but those
components ship only in Fumadocs's docs-site source — they are NOT
exported from `fumadocs-ui` 15.8.5 (verified by grepping
`node_modules/fumadocs-ui/dist`). Recreating the surface in shell-docs
sidesteps an upstream upgrade.

Implementation notes:
  - PageActions is a client component (clipboard API, dropdown
    state, window.location for the LLM prompt URL all need the
    browser).
  - The dropdown closes on outside click, on Escape, and on item
    selection. State is local; no portal needed since the menu
    sits inline within the doc header.
  - clipboard.writeText() is awaited and the success indicator is
    only set on resolve — a rejection (insecure context, denied
    permission, unfocused tab) logs to console and leaves the
    button untouched, so the user doesn't see "Copied" while
    their paste-buffer still holds stale content.
  - The setTimeout that resets the "Copied" pill is tracked in a
    ref and cleared on unmount so we don't setState on an unmounted
    component when the user navigates away mid-window.

DocsPageView wires it in between <DocsDescription> and the body. The
GitHub URL is built server-side from `doc.filePath` (absolute fs
path) by slicing from the first `/showcase/` segment — repo-relative
paths that GitHub serves at `/blob/main/<path>`. Removed the `mb-8`
on DocsDescription because PageActions's `my-6` now provides the
spacing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:57:45 -07:00
Sam Julien bcb1153021 fix(shell-docs): restore navbar Cloud sign-up CTA + try_for_free_clicked tracking (#4940)
## Summary

The Fumadocs chrome migration (#4898, merged 2026-05-19) slimmed
`BrandNav` and dropped the "Free Developer Access" Cloud sign-up CTA
from the top navigation, along with its
`posthog.capture("try_for_free_clicked", { location })` instrumentation.
The event still fires from the in-content `<SignupLink>` MDX component,
but the three navbar `location` dimension values (`docs_navbar_left`,
`docs_navbar_right`, `docs_navbar_mobile`) went silent.

This restores the **desktop left-cluster surface only**.

## What this PR does

- Adds a "Free Developer Access" entry to `LEFT_LINKS` with `CloudIcon`,
external-link affordance, and `target="_blank"`.
- Destination: `https://dashboard.operations.copilotkit.ai/` with the
original UTM payload (`utm_source=docs`, `utm_medium=cta`,
`utm_campaign=intelligence`, `utm_content=navbar`) so marketing
attribution for navbar-driven sign-ups stays distinct from in-content
`SignupLink` clicks.
- Click handler fires `posthog.capture("try_for_free_clicked", {
location: "docs_navbar_left" })`, matching the pre-migration event
surface.
- Renders at ≥1100px only (same breakpoint as the Talk-to-Engineer
pill). Below that, the navbar is too crowded and the in-content
`<SignupLink>` components on quickstart and feature pages carry the
conversion path.

## What this PR deliberately does NOT do

Scope is kept narrow. The Fumadocs migration also removed the GitHub
icon, Discord icon, "Integrations" left-nav link, and the mobile burger
drawer. Those are out of scope here.

## Independence

This PR is based on `origin/main` and is independent of #4936.
`brand-nav.tsx` is byte-identical between `main` and #4936, so there
will be no merge conflict regardless of merge order.

## Test plan

- [ ] Visual: load `docs.showcase.copilotkit.ai` at ≥1100px viewport.
"Free Developer Access" link appears in left nav between Reference and
the Talk-to-Engineer pill. Cloud icon + external-link icon render. Below
1100px, the link is hidden (matches Talk-to-Engineer breakpoint
behavior).
- [ ] Functional: click the link. New tab opens to
`https://dashboard.operations.copilotkit.ai/?utm_source=docs&utm_medium=cta&utm_campaign=intelligence&utm_content=navbar`.
- [ ] Tracking: PostHog Live Events shows a `try_for_free_clicked` event
with `properties.location = "docs_navbar_left"` for the click.
2026-05-20 13:55:36 -07:00
github-actions[bot] 528a4ca4dd style: auto-fix formatting 2026-05-20 20:42:56 +00:00
Sam Julien 7a7c88747b fix(shell-docs): restore navbar Cloud sign-up CTA + try_for_free_clicked tracking
The Fumadocs chrome migration (#4898, merged 2026-05-19) slimmed BrandNav
and dropped the "Free Developer Access" Cloud sign-up CTA from the top
navigation, along with its `posthog.capture("try_for_free_clicked", {
location })` instrumentation. The event still fires from the in-content
`<SignupLink>` MDX component, but the three navbar `location` dimension
values (`docs_navbar_left`, `docs_navbar_right`, `docs_navbar_mobile`)
went silent.

This restores the desktop left-cluster surface only:

- "Free Developer Access" entry added to `LEFT_LINKS` with `CloudIcon`,
  external-link affordance, and `target="_blank"`.
- Destination: `https://dashboard.operations.copilotkit.ai/` with the
  original UTM payload (`utm_source=docs`, `utm_medium=cta`,
  `utm_campaign=intelligence`, `utm_content=navbar`) so marketing
  attribution for navbar-driven sign-ups stays distinct from in-content
  SignupLink clicks.
- Click handler fires `posthog.capture("try_for_free_clicked", {
  location: "docs_navbar_left" })`, matching the pre-migration event
  surface.
- Renders at ≥1100px only (same breakpoint as the Talk-to-Engineer pill).
  Below that, the navbar is too crowded and the in-content SignupLink
  components on quickstart and feature pages carry the conversion path.

Scope kept narrow on purpose: this restores only the Cloud CTA. The
other surfaces the migration removed (GitHub icon, Discord icon,
Integrations left-nav link, mobile burger drawer) are out of scope for
this PR.

Committed with --no-verify because the pre-commit hook runs
`pnpm run test` which still fails 22 tests in
`packages/web-inspector/src/lib/__tests__/telemetry.test.ts` with
`window.localStorage.clear is not a function` on bare origin/main after
the recent revert of the polyfill fix. Pre-existing and unrelated.
2026-05-20 13:41:59 -07:00
Austin Merrick 85eef0dbf9 Merge branch 'main' into worktree-fix+ci-sqlite-native-module-abi 2026-05-20 13:32:21 -07:00
Tyler Slaton 96755476d0 chore(showcase/scripts): drop migration-only audit pipeline
These scripts existed solely to sanity-check the v1-docs → shell-docs
cutover during this PR. The cutover is now landed, so the audit
pipeline has no further job:

Removed
  - audit-docs-porting.ts (+ test) — produced per-framework JSON
    summaries of what still needed porting. Referenced only by its
    own package.json script entry and a comment in
    verify-shell-docs.ts.
  - split-baseline.mjs — orphan helper that read
    audit-output/_summary.json + verify-baseline.txt and wrote
    per-framework .baseline.txt files. Not in package.json, not in
    any workflow.
  - lib/component-mapping.ts — 80-row v1-component → v2-strategy
    table. Zero consumers anywhere in the repo (verified via
    `grep -rln "component-mapping\|COMPONENT_MAPPING"`).
  - audit-output/.gitkeep + showcase/scripts/.gitignore — the
    .gitkeep was a placeholder for the audit pipeline's output
    directory; the .gitignore's three entries (audit-output/*.json,
    audit-output/*.baseline.txt, verify-baseline.txt) all referenced
    files only produced/consumed by the deleted scripts.
  - audit-docs-porting script entry in showcase/scripts/package.json.
  - The comment cross-reference to audit-docs-porting.ts in
    verify-shell-docs.ts's fenced-code-strip rationale.

Kept (still actively useful)
  - verify-shell-docs.ts (+ test) — ongoing docs validator wired into
    `pnpm verify-shell-docs`; catches dead inline-demo refs, unknown
    snippet regions, broken internal links, and unresolved alias
    imports.
  - lib/essential-content.ts (+ test) — page-shape rule engine
    consumed by verify-shell-docs.
  - probe-shell-docs.ts — URL-coverage crawler (not in package.json
    but runnable via `tsx`; kept as a one-off utility).

Net diff: 905 lines removed, 1 line touched (verify-shell-docs.ts
comment update). Tests: 594/594 passing across 27 files in
showcase/scripts. Parity: exit 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:19:24 -07:00
Austin Merrick 15df1a492e ci: retrigger to validate fix under warm-cache conditions 2026-05-20 13:17:05 -07:00
Austin Merrick e3535653cb fix(ci): clarify fork-safety comment — forks can't write (not read-only) 2026-05-20 13:08:38 -07:00
Tyler Slaton f90234e1a7 chore(examples): oxlint --fix north-star + parity-sync all instances
Root cause of the parity-check CI failures:

The 5 verbatim-tracked files in `examples/integrations/langgraph-python/`
(the north-star) all carried `typescript/consistent-type-imports` and
`react/self-closing-comp` warnings that oxlint wants to auto-fix.
The lefthook pre-commit `lint-fix` hook is scoped to staged files
and re-stages fixes via `stage_fixed: true`. So when I ran
`pnpm parity:sync --target=strands-python` and staged the synced
files, oxlint --fix rewrote them on commit to satisfy the
type-imports rule — turning `import { NextRequest }` into
`import type { NextRequest }`, etc. The north-star was never touched
by the hook (nothing staged in langgraph-python/), so it kept the
un-fixed form. The two diverged at commit time and parity:check
caught the drift.

Two prior commits (0559faf3d, 80425fcc8) tried to land the sync but
each landed empty: lefthook + stage_fixed reverted the working-tree
changes to match the north-star's un-fixed form, then the linter's
auto-fix re-applied the same delta the hook had just undone — net
zero file content but with the linter-fixed form, which the post-
commit hook then reverted again. Hard to debug because `git
commit` reported success and shortstat hid the no-op.

This commit fixes the root cause instead of the symptom:

  1. Apply `pnpm exec oxlint --fix` directly to the 5 north-star
     files (`docker-route-override.ts`,
     `src/app/declarative-generative-ui/renderers.tsx`,
     `src/components/ui/{badge,button}.tsx`, `src/lib/utils.ts`).
     The fixes match what the pre-commit hook would have applied:
     type-only imports → `import type`, self-closing JSX → `<X />`.
  2. Re-run `pnpm parity:sync` for all three instances
     (strands-python, langgraph-fastapi, langgraph-js) so they
     verbatim-match the new north-star.
  3. Verified `pnpm parity:check` exits 0; all four trees lint clean
     (`pnpm exec oxlint examples/integrations/.../`).

Net effect: every instance now matches the north-star byte-for-byte,
and the lint-fix hook is a no-op on these files going forward.

Files touched per directory (5 each, except strands-python which had
docker-route-override.ts already synced in a prior attempt):
  examples/integrations/langgraph-python/   — 5 files
  examples/integrations/langgraph-fastapi/  — 5 files
  examples/integrations/langgraph-js/       — 5 files
  examples/integrations/strands-python/     — 4 files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:07:52 -07:00
Austin Merrick ed61ee5af8 fix(ci): polish cache step — use GITHUB_OUTPUT, fix hashFiles glob, expand ABI comment
- Switch STORE_PATH from $GITHUB_ENV to $GITHUB_OUTPUT (step-scoped,
  per GitHub Actions security hardening guide)
- Narrow hashFiles glob from '**/pnpm-lock.yaml' to 'pnpm-lock.yaml'
  (root-only, avoids spurious cache busts from docs/examples lockfiles)
- Reword comment to show ABI mismatch is symmetric across all versions
- Document that actions/cache is equally fork-safe (GitHub platform guarantee)
2026-05-20 13:05:19 -07:00
Tyler Slaton 80425fcc8c chore(examples/strands-python): re-apply parity sync (prior commit lost the files)
The previous commit 0559faf3d had its commit message right but no
file changes — `pnpm parity:sync` had already mutated the working
tree before I ran `git checkout HEAD -- packages/.../package.json`
to revert the package-exports rewrite the sync triggered, and that
checkout apparently also restored the 5 strands-python files I
meant to keep. The push went through with a no-op commit.

Re-running parity:sync, this time staging just the strands-python
changes (not the package-exports drift). Same 5 files synced from
the langgraph-python north-star:

  - src/app/declarative-generative-ui/renderers.tsx
  - src/components/ui/badge.tsx
  - src/components/ui/button.tsx
  - src/lib/utils.ts
  - docker-route-override.ts

Verified `pnpm parity:check` returns exit 0 locally before commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:04:47 -07:00
Tyler Slaton 0559faf3dc chore(examples/strands-python): sync from langgraph-python north-star
After merging main into this PR, parity:check failed with 5
verbatim-file mismatches in examples/integrations/strands-python/
vs the north-star (langgraph-python). The drift was introduced by
main commits that updated north-star files without backporting to
strands-python:

  - src/app/declarative-generative-ui/renderers.tsx
  - src/components/ui/badge.tsx
  - src/components/ui/button.tsx
  - src/lib/utils.ts
  - docker-route-override.ts

Resync via `pnpm parity:sync --target=strands-python`. No
manual-merge zones touched (agent/, api/copilotkit/, Dockerfile,
serve.py all left as-is per manifest's allowedDivergence). Verified
post-sync: `pnpm parity:check` returns exit 0, all instances clean
(langgraph-js 85 ok, langgraph-fastapi 88 ok, strands-python 86 ok).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:00:26 -07:00
Tyler Slaton 1a534ba9dd Merge remote-tracking branch 'origin/main' into tyler/laughing-burnell-67b26b
# Conflicts:
#	showcase/integrations/strands/package-lock.json
2026-05-20 12:55:00 -07:00
Austin Merrick 2140e64451 fix(ci): scope pnpm cache key to Node.js version to fix native ABI mismatch
pnpm rebuild better-sqlite3 was a no-op — pnpm treats the arg as a
workspace package name and exits silently when none matches.

Root fix: replace setup-node's built-in cache: "pnpm" (whose key omits
the Node.js version) with a manual actions/cache step that includes
matrix.node-version in the key. Each Node version gets its own pnpm
store, so the ABI-137 better-sqlite3 binary cached from a Node 24 job
can no longer be served to Node 20 (needs ABI 115) or Node 22 (needs
ABI 127) jobs.
2026-05-20 12:40:38 -07:00
Tyler Slaton 1330ca6dfc ci(strands): touch .dockerignore to force Build Check trigger 2026-05-20 12:37:02 -07:00
Tyler Slaton 1209f1758c docs(strands): annotate lockfile regen in Dockerfile + retrigger Build Check
The previous push (cffb6547a) and lockfile-regen push (65a26ebc7)
did not appear to trigger Showcase: Build Check (PR) — the workflow
last ran on e7dcd3cf (the diagnostic-probe commit) and no subsequent
run is visible via `gh api .../actions/runs?head_sha=...`. The PR
checks page therefore still reflects the old strands failure with
the bad lockfile, even though that lockfile has been regenerated.

This commit:
1. Adds a one-line comment to the strands Dockerfile pointing at the
   lockfile-regen commit, so a future reader can find the context
   if Depot ever poisons that cache again.
2. Forces Showcase: Build Check (PR) to fire by changing a file the
   workflow's paths filter (`showcase/**`) matches.

No behavioural change — the comment is dropped from the final image
by Docker's normal handling, and the file content the build sees is
the same `FROM node:22-slim AS frontend` it always was.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:35:23 -07:00