diff --git a/apps/cockpit/src/components/sidebar/navigation-groups.tsx b/apps/cockpit/src/components/sidebar/navigation-groups.tsx
index 6a7db7772..a55818dcd 100644
--- a/apps/cockpit/src/components/sidebar/navigation-groups.tsx
+++ b/apps/cockpit/src/components/sidebar/navigation-groups.tsx
@@ -76,15 +76,15 @@ function ProductGroup({
key={`${entry.product}-${entry.topic}`}
href={toCockpitPath(entry)}
aria-current={isActive ? 'page' : undefined}
+ className={isActive ? 'border-l-2 border-[var(--ds-accent)]' : 'border-l-2 border-transparent'}
style={{
display: 'block',
- padding: isActive ? '5px 16px 5px 14px' : '5px 16px',
+ padding: '5px 16px 5px 14px',
margin: '0 8px',
borderRadius: 6,
fontSize: '0.825rem',
color: isActive ? 'var(--ds-accent)' : 'var(--ds-text-secondary)',
background: isActive ? 'var(--ds-accent-surface)' : 'transparent',
- borderLeft: isActive ? '2px solid var(--ds-accent)' : 'none',
textDecoration: 'none',
transition: 'all 0.15s',
}}
diff --git a/docs/superpowers/plans/2026-05-13-cockpit-polish.md b/docs/superpowers/plans/2026-05-13-cockpit-polish.md
new file mode 100644
index 000000000..557a1c160
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-13-cockpit-polish.md
@@ -0,0 +1,482 @@
+# Cockpit Polish Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Ship cockpit polish — chat lib theme-attribute rename, cockpit.css hardcoded-color cleanup, border-pattern standardization on Tailwind arbitrary values, and revert of the in-flight `data-ngaf-chat-theme` shim in `installEmbeddedTheme`.
+
+**Architecture:** The chat lib already implements a three-layer cascade (default → `prefers-color-scheme` → programmatic override) — rename only the override selector to `[data-theme]`. Cockpit's `cockpit.css` has dead `@theme inline` + shadcn alias blocks (no Tailwind utilities consume them); replace with direct `var(--ds-*)` references. Border styles standardize on Tailwind arbitrary-value classes (`border-b border-[var(--ds-border)]`).
+
+**Tech Stack:** Tailwind v4, Angular 21, Vitest, Nx, npm workspaces.
+
+**Spec:** `docs/superpowers/specs/2026-05-13-cockpit-polish-design.md`
+
+---
+
+## File Map
+
+| Action | File | Responsibility |
+|---|---|---|
+| Modify | `libs/chat/src/lib/styles/chat-tokens.ts` | Rename `data-ngaf-chat-theme` → `data-theme` (4 selectors + 2 doc comments) |
+| Modify | `libs/example-layouts/src/lib/install-embedded-theme.ts` | Remove `dataset.ngafChatTheme` line + comment (added in-flight, no longer needed) |
+| Modify | `apps/cockpit/src/app/cockpit.css` | Drop `@theme inline` + shadcn alias block; replace 12 hardcoded rgba literals with `var(--ds-*)` |
+| Modify | `apps/cockpit/src/components/cockpit-shell.tsx` | Border inline → Tailwind class (already keeps edge-to-edge padding fix from in-flight) |
+| Modify | `apps/cockpit/src/components/mobile-nav-overlay.tsx` | Border inline → Tailwind class |
+| Modify | `apps/cockpit/src/components/api-mode/api-mode.tsx` | `borderBottomColor` inline → Tailwind class |
+| Modify | `apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx` | `borderRightColor` inline → Tailwind class |
+| Modify | `apps/cockpit/src/components/sidebar/navigation-groups.tsx` | `borderLeft` inline → conditional Tailwind class |
+| Modify | `apps/cockpit/src/components/code-mode/code-mode.tsx` | Hardcoded `rgba(255,255,255,0.06)` → `var(--ds-border)` |
+| Modify | `libs/chat/package.json` | Patch bump |
+| Modify | `libs/example-layouts/package.json` | Patch bump |
+
+---
+
+## Task 1: Chat lib attribute rename
+
+**Files:**
+- Modify: `libs/chat/src/lib/styles/chat-tokens.ts`
+
+The chat lib already has the right three-layer cascade — only the override selector needs renaming. Six instances of the literal string `data-ngaf-chat-theme` exist in the file (4 in CSS selectors, 2 in JSDoc comments).
+
+- [ ] **Step 1: Read current file to confirm exact line content**
+
+```bash
+grep -n "data-ngaf-chat-theme" libs/chat/src/lib/styles/chat-tokens.ts
+```
+
+Expected output: six lines (175, 177, 190, 191, 192, 193).
+
+- [ ] **Step 2: Rename the attribute everywhere in this file**
+
+Use Edit's `replace_all` flag on the literal string `data-ngaf-chat-theme` → `data-theme`.
+
+After the edit, the file should contain selectors:
+
+```ts
+:root[data-theme="light"],
+[data-theme="light"] { ${LIGHT_TOKENS} }
+:root[data-theme="dark"],
+[data-theme="dark"] { ${DARK_TOKENS} }
+```
+
+And JSDoc references like `[data-theme="dark"]` and `[data-theme="light"]`.
+
+- [ ] **Step 3: Verify no other files in the chat lib reference the old attribute**
+
+```bash
+rg "data-ngaf-chat-theme" libs/chat
+```
+
+Expected: no matches.
+
+- [ ] **Step 4: Run chat lib tests**
+
+```bash
+pnpm nx test chat
+```
+
+Expected: all green. (No test currently asserts the old attribute string, so the rename should be invisible to tests. If a test does fail because it asserts the literal `data-ngaf-chat-theme`, update the assertion to `data-theme`.)
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add libs/chat/src/lib/styles/chat-tokens.ts
+git commit -m "refactor(chat): rename theme attribute data-ngaf-chat-theme → data-theme"
+```
+
+---
+
+## Task 2: Drop redundant `dataset.ngafChatTheme` in `installEmbeddedTheme`
+
+**Files:**
+- Modify: `libs/example-layouts/src/lib/install-embedded-theme.ts`
+
+After Task 1's rename, the chat lib reads `data-theme` directly — no need to also set `data-ngaf-chat-theme`. Revert the in-flight change.
+
+- [ ] **Step 1: Edit the file**
+
+Open `libs/example-layouts/src/lib/install-embedded-theme.ts`. The current `apply` function (after the in-flight change) looks like:
+
+```ts
+const apply = (theme: Theme) => {
+ document.documentElement.dataset.theme = theme;
+ // @ngaf/chat keys its internal tokens off `data-ngaf-chat-theme`,
+ // not `data-theme`. Set both so the chat lib flips alongside the
+ // design-tokens palette.
+ document.documentElement.dataset.ngafChatTheme = theme;
+ const vars = cssVars(theme) as Record;
+ for (const [key, value] of Object.entries(vars)) {
+ document.documentElement.style.setProperty(key, value);
+ }
+};
+```
+
+Remove the comment block and the `dataset.ngafChatTheme` line. Result:
+
+```ts
+const apply = (theme: Theme) => {
+ document.documentElement.dataset.theme = theme;
+ const vars = cssVars(theme) as Record;
+ for (const [key, value] of Object.entries(vars)) {
+ document.documentElement.style.setProperty(key, value);
+ }
+};
+```
+
+Also update the docblock above the function. Current:
+
+```ts
+/**
+ * Bootstraps an embedded example app's theme. Call once before the
+ * framework (Angular, Vue, etc.) bootstraps.
+ *
+ * Behavior:
+ * 1. Applies the default theme synchronously: sets `data-theme` and
+ * `data-ngaf-chat-theme` on `` (the latter tells `@ngaf/chat`
+ * to use its matching dark/light token set), plus every `--ds-*`
+ * CSS variable on the same element.
+ * ...
+```
+
+Replace the relevant paragraph with:
+
+```ts
+/**
+ * Bootstraps an embedded example app's theme. Call once before the
+ * framework (Angular, Vue, etc.) bootstraps.
+ *
+ * Behavior:
+ * 1. Applies the default theme synchronously: sets `data-theme` on
+ * `` (which both `@ngaf/design-tokens`-aware code and
+ * `@ngaf/chat` honor) plus every `--ds-*` CSS variable on the
+ * same element.
+ * ...
+```
+
+- [ ] **Step 2: Run example-layouts tests**
+
+```bash
+pnpm nx test example-layouts
+```
+
+Expected: all green. The existing test asserts `data-theme` is set; it never asserted `data-ngaf-chat-theme`, so removing that line doesn't break the test surface.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add libs/example-layouts/src/lib/install-embedded-theme.ts
+git commit -m "refactor(example-layouts): drop redundant data-ngaf-chat-theme set"
+```
+
+---
+
+## Task 3: Rewrite `cockpit.css` — drop dead aliases, migrate literals to tokens
+
+**Files:**
+- Modify: `apps/cockpit/src/app/cockpit.css`
+
+Two things in this task:
+- **Drop dead code**: the `@theme inline` block (lines 3-15) and the shadcn `:root` alias block (lines 17-30). Grep confirms no Tailwind utility in cockpit consumes `bg-background`, `bg-card`, `text-muted`, etc. — they're entirely unused.
+- **Replace 12 hardcoded rgba literals** with `var(--ds-*)` tokens.
+
+- [ ] **Step 1: Read the current file**
+
+```bash
+cat apps/cockpit/src/app/cockpit.css
+```
+
+Note the exact line numbers of `@theme inline` (around 3-15), `:root` block (17-30), and the rgba literals.
+
+- [ ] **Step 2: Delete the `@theme inline` block and the `:root` shadcn alias block**
+
+Replace the top of the file from `@import "tailwindcss";` through the end of the `:root` block:
+
+```css
+@import "tailwindcss";
+```
+
+The `@theme inline` block goes away (those `--color-*` vars had no consumers). The `:root` block with `--background`, `--foreground`, `--primary`, `--card`, `--muted`, `--border`, `--input`, `--ring` goes away (also no consumers).
+
+- [ ] **Step 3: Replace `rgba(0, 64, 144, 0.04)` literals**
+
+In `cockpit.css`, find lines containing `rgba(0, 64, 144, 0.04)` (callout backgrounds, two sites). Replace with `var(--ds-accent-surface)`.
+
+- [ ] **Step 4: Replace `rgba(0, 64, 144, 0.06)` literal**
+
+Find the line containing `rgba(0, 64, 144, 0.06)` (was the shadcn `--muted` alias — but if the alias block is gone, this literal may not appear anymore; verify with grep). If it does appear elsewhere, replace with `var(--ds-accent-surface)`.
+
+- [ ] **Step 5: Replace `rgba(0, 64, 144, 0.08)` literal**
+
+Find `rgba(0, 64, 144, 0.08)` (the bottom border around line 227). Replace with `var(--ds-accent-border)`.
+
+- [ ] **Step 6: Replace `rgba(0, 64, 144, 0.12)` literals**
+
+Find each `rgba(0, 64, 144, 0.12)` (callout borders, divider). Replace with `var(--ds-accent-border)`.
+
+- [ ] **Step 7: Replace `rgba(0, 64, 144, 0.15)` literals**
+
+Find each `rgba(0, 64, 144, 0.15)` (active state highlights, around lines 108 and 149). Replace with `var(--ds-accent-border)`.
+
+- [ ] **Step 8: Replace the dark-assumed `rgba(26, 27, 38, 0.95)`**
+
+Find the single occurrence of `rgba(26, 27, 38, 0.95)` (code-block header bg, around line 142). Replace with `var(--ds-surface)`.
+
+- [ ] **Step 9: Verify no rgba literals remain**
+
+```bash
+grep -nE "rgba\(0, 64, 144|rgba\(26, 27, 38" apps/cockpit/src/app/cockpit.css
+```
+
+Expected: no matches.
+
+- [ ] **Step 10: Verify no orphaned references to dropped shadcn aliases**
+
+```bash
+grep -nE "var\(--(background|foreground|primary|card|muted|border|input|ring)\)\b" apps/cockpit/src/app/cockpit.css
+```
+
+Expected: no matches. (If any remain — they referenced the now-deleted block — replace with appropriate `var(--ds-*)`. The bottom `border-bottom: 1px solid var(--border)` should become `border-bottom: 1px solid var(--ds-border)`.)
+
+- [ ] **Step 11: Run cockpit build to confirm CSS still compiles**
+
+```bash
+pnpm nx build cockpit
+```
+
+Expected: clean build. Any unresolved CSS variable would surface as a build warning.
+
+- [ ] **Step 12: Commit**
+
+```bash
+git add apps/cockpit/src/app/cockpit.css
+git commit -m "refactor(cockpit): drop dead shadcn aliases, migrate hardcoded rgba to --ds-* tokens"
+```
+
+---
+
+## Task 4: Migrate inline border styles to Tailwind arbitrary values
+
+**Files:**
+- Modify: `apps/cockpit/src/components/cockpit-shell.tsx`
+- Modify: `apps/cockpit/src/components/mobile-nav-overlay.tsx`
+- Modify: `apps/cockpit/src/components/api-mode/api-mode.tsx`
+- Modify: `apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx`
+- Modify: `apps/cockpit/src/components/sidebar/navigation-groups.tsx`
+- Modify: `apps/cockpit/src/components/code-mode/code-mode.tsx`
+
+Six files have inline border styles. Migrate each to the Tailwind arbitrary-value pattern (`border-b border-[var(--ds-border)]`). Note that some preserve the in-flight padding fix in `cockpit-shell.tsx`.
+
+- [ ] **Step 1: `cockpit-shell.tsx`**
+
+Currently in the `` element (around line 84):
+
+```tsx
+
+```
+
+Replace with:
+
+```tsx
+
+```
+
+(The `style={...}` prop and its contents go away. The `px-4 py-3` from the in-flight padding fix is preserved.)
+
+- [ ] **Step 2: `mobile-nav-overlay.tsx`**
+
+Find the line with `style={{ borderBottom: '1px solid var(--ds-border)' }}`. Migrate the same way: add `border-b border-[var(--ds-border)]` to the element's `className`, drop the `style` prop.
+
+- [ ] **Step 3: `api-mode/api-mode.tsx`**
+
+Find the line containing `borderBottomColor: 'var(--ds-accent-border)'`. The element likely has `style={{ borderBottom: '1px solid', borderBottomColor: '...' }}` or similar. Read context (3-5 lines before/after) to understand the full inline-style, then replace with:
+
+```tsx
+className=" border-b border-[var(--ds-accent-border)]"
+```
+
+Drop the inline `borderBottom`/`borderBottomColor`/`borderBottomStyle` properties from `style={}`. If the only `style` prop entries were the border ones, drop the entire `style` prop.
+
+- [ ] **Step 4: `sidebar/cockpit-sidebar.tsx`**
+
+Find the line containing `borderRightColor: 'var(--ds-border-strong)'`. Read context — likely paired with `borderRight: '1px solid'` or similar. Replace inline with:
+
+```tsx
+className=" border-r border-[var(--ds-border-strong)]"
+```
+
+- [ ] **Step 5: `sidebar/navigation-groups.tsx`**
+
+Find the line containing `borderLeft: isActive ? '2px solid var(--ds-accent)' : 'none'`. This is a conditional pattern — needs to stay conditional. Replace with Tailwind classes via conditional:
+
+```tsx
+className={` ${isActive ? 'border-l-2 border-[var(--ds-accent)]' : 'border-l-2 border-transparent'}`}
+```
+
+The `border-l-2 border-transparent` on the inactive branch reserves the layout space so the active state doesn't shift the row. (If the existing inline style was using `2px` only on active and `none` on inactive, content shifts when active toggles. Preserving the transparent border keeps layout stable. Verify visually after edit.)
+
+If the existing behavior was intentionally shifting on activation, omit the `border-transparent` and use empty string on the inactive branch.
+
+- [ ] **Step 6: `code-mode/code-mode.tsx`**
+
+Find the line `borderBottom: '1px solid rgba(255,255,255,0.06)'`. This is a dark-mode-assumed hardcoded color. Replace with token-driven Tailwind:
+
+```tsx
+className=" border-b border-[var(--ds-border)]"
+```
+
+Drop the inline `borderBottom` from `style={}`. If `style` is now empty, drop it.
+
+- [ ] **Step 7: Verify no inline border styles remain in cockpit components**
+
+```bash
+rg -g '*.tsx' "borderBottom|borderRight|borderTop|borderLeft" apps/cockpit/src/components
+```
+
+Expected: no matches.
+
+- [ ] **Step 8: Run cockpit build**
+
+```bash
+pnpm nx build cockpit
+```
+
+Expected: clean build.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add apps/cockpit/src/components
+git commit -m "refactor(cockpit): migrate inline border styles to Tailwind arbitrary-value classes"
+```
+
+---
+
+## Task 5: Version bumps + full check stack
+
+**Files:**
+- Modify: `libs/chat/package.json` (patch bump)
+- Modify: `libs/example-layouts/package.json` (patch bump)
+
+- [ ] **Step 1: Read current versions**
+
+```bash
+grep '"version"' libs/chat/package.json libs/example-layouts/package.json
+```
+
+- [ ] **Step 2: Bump patch versions**
+
+Increment the last digit of each. Patch-only release rule applies — never bump to 0.1.x.
+
+- [ ] **Step 3: Run the full check stack**
+
+```bash
+pnpm nx run-many -t lint,test -p design-tokens,ui-react,example-layouts,chat,cockpit
+```
+
+Expected: all green.
+
+```bash
+pnpm nx e2e cockpit
+```
+
+Expected: all green.
+
+```bash
+pnpm nx build website
+```
+
+Expected: green.
+
+```bash
+pnpm nx build cockpit-chat-timeline-angular
+```
+
+Expected: green.
+
+If any failure surfaces:
+- **Chat lib tests** — likely a test asserted the literal `data-ngaf-chat-theme` somewhere. Update assertion to `data-theme`.
+- **Cockpit tests** — possibly a snapshot test or a CSS-class-presence test referencing the old `--muted` / `--border` aliases. Update the test to use `--ds-*` references.
+- **Cockpit e2e** — if any test asserted a specific color value, it may shift slightly (alpha 0.04 → 0.06 etc.). Update the assertion to match the new computed color.
+- **Cockpit build** — unresolved CSS var; grep cockpit.css to find which `var(--*)` reference doesn't resolve.
+
+- [ ] **Step 4: Commit version bumps**
+
+```bash
+git add libs/chat/package.json libs/example-layouts/package.json
+git commit -m "chore: bump chat and example-layouts patch versions"
+```
+
+---
+
+## Task 6: Open PR + merge on green
+
+- [ ] **Step 1: Push branch**
+
+```bash
+git push -u origin cockpit-polish
+```
+
+- [ ] **Step 2: Open PR**
+
+```bash
+gh pr create --title "refactor(cockpit): polish — chat lib data-theme, token migration, border standardization" --body "$(cat <<'EOF'
+## Summary
+
+First in a three-PR sequence (spec: \`docs/superpowers/specs/2026-05-13-cockpit-polish-design.md\`).
+
+- **Chat lib theme attribute rename:** \`[data-ngaf-chat-theme]\` → \`[data-theme]\`. Aligns with Tailwind v4 / shadcn / Storybook conventions. The lib's existing three-layer cascade (default → \`prefers-color-scheme\` → programmatic override) is preserved — only the override selector renames.
+- **\`installEmbeddedTheme\` cleanup:** drop the in-flight \`dataset.ngafChatTheme\` set. After the chat lib rename, \`data-theme\` is sufficient.
+- **\`cockpit.css\` token migration:** drop dead \`@theme inline\` block and unused shadcn alias \`:root\` block (no Tailwind utility in cockpit consumed them). Replace 12 hardcoded \`rgba(0, 64, 144, X)\` and \`rgba(26, 27, 38, 0.95)\` literals with \`--ds-*\` tokens — code blocks and callouts now theme correctly in light AND dark.
+- **Border standardization:** migrate ~6 inline \`style={{ borderBottom: ... }}\` sites to Tailwind arbitrary-value classes (\`border-b border-[var(--ds-border)]\`). Consistent with cockpit's dominant Tailwind-arbitrary-value pattern.
+- Preserves edge-to-edge content padding in \`cockpit-shell.tsx\` (from in-flight pre-spec change).
+
+PR 2 (chat lib polish — text-wrap bug + bubble width handling) and PR 3 (cockpit ↔ website style alignment) follow.
+
+## Test plan
+
+- [x] \`pnpm nx run-many -t lint,test -p design-tokens,ui-react,example-layouts,chat,cockpit\` — green
+- [x] \`pnpm nx e2e cockpit\` — green
+- [x] \`pnpm nx build website\` — green
+- [x] \`pnpm nx build cockpit-chat-timeline-angular\` — green
+- [ ] Manual chrome MCP smoke: cockpit + timeline pilot in light + dark — no white callouts in dark, no dark code-block headers in light, chat input flips with host theme
+
+🤖 Generated with [Claude Code](https://claude.com/claude-code)
+EOF
+)"
+```
+
+- [ ] **Step 3: Wait for green CI**
+
+```bash
+gh pr checks --watch
+```
+
+- [ ] **Step 4: Squash-merge**
+
+```bash
+gh pr merge --squash --delete-branch
+```
+
+---
+
+## Self-review
+
+**Spec coverage:**
+- ✅ Decision 1 (chat lib attribute rename) → Task 1
+- ✅ Decision 2 (border/divider Tailwind convention) → Task 4
+- ✅ Decision 3 (hardcoded color mapping) → Task 3
+- ✅ Decision 4 (no backwards compat — drop shadcn aliases) → Task 3 (drop `@theme inline` + `:root` blocks)
+- ✅ Decision 5 (installEmbeddedTheme cleanup) → Task 2
+
+**Adjustments from spec during plan-prep exploration:**
+1. **Bonus dead-code finding:** the `@theme inline` Tailwind v4 token block in `cockpit.css` is also unused — no `bg-background`/`bg-card`/`text-muted` utilities in cockpit source. Dropping the whole `@theme inline` block alongside the `:root` aliases (spec only mentioned the `:root` block, but plan extends the cleanup since they're paired and equally dead).
+2. **`code-mode.tsx` has its own hardcoded color** — `rgba(255,255,255,0.06)` border. Not in spec's 12-literal count but discovered during plan-prep and added to Task 4 Step 6.
+3. **Two more inline-border sites than spec estimated** — `api-mode.tsx` borderBottomColor and `navigation-groups.tsx` borderLeft (conditional). All covered in Task 4.
+
+**Placeholder scan:** No "TBD" / "TODO". One conditional pattern in Task 4 Step 5 (the `border-transparent` for layout stability) is described with reasoning, not gestured at.
+
+**Type consistency:** No new types. Attribute name `data-theme` consistent across Tasks 1 and 2. CSS var names `--ds-accent-surface`, `--ds-accent-border`, `--ds-border`, `--ds-border-strong`, `--ds-accent` consistent across Tasks 3 and 4.
diff --git a/docs/superpowers/specs/2026-05-13-cockpit-polish-design.md b/docs/superpowers/specs/2026-05-13-cockpit-polish-design.md
new file mode 100644
index 000000000..3b1375980
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-13-cockpit-polish-design.md
@@ -0,0 +1,115 @@
+# Cockpit Polish — Design
+
+**Date:** 2026-05-13
+**Status:** Spec — pending implementation plan
+**Spec series:** First in a three-PR sequence (cockpit polish → chat lib polish → cockpit ↔ website style alignment)
+
+## Goal
+
+Tighten cockpit's dark-mode-correctness, divider/border patterns, and standardize the chat library's theme-attribute API. Land the pilot's chrome-MCP-surfaced fixes in a single reviewable PR, and use the work as a forcing function to surface chat lib follow-ups for PR 2.
+
+Out of scope:
+- Chat lib internals beyond renaming `data-ngaf-chat-theme` → `data-theme` (the text-wrap bug, message-bubble width handling, and other chat lib polish are tracked for PR 2)
+- Marketing website alignment (PR 3)
+- New design-tokens entries (use existing tokens; accept tiny alpha shifts for consistency)
+- Migration of `cockpit///angular/` example apps beyond the timeline pilot (Stage 2 of the examples theme sync work, separate spec)
+
+## Decisions
+
+| # | Decision | Choice |
+|---|---|---|
+| 1 | Chat lib theme attribute | Rename `[data-ngaf-chat-theme]` → `[data-theme]`. Keep the `prefers-color-scheme: dark` cascade. No JS API. |
+| 2 | Border/divider convention in cockpit | Tailwind arbitrary values (`border-b border-[var(--ds-border)]`). Migrate the ~5 inline-style holdouts. |
+| 3 | Hardcoded color mapping in `cockpit.css` | Use existing `--ds-*` tokens. Accept ≤0.04 alpha shifts. No new tokens added. |
+| 4 | Backwards compatibility | None. Drop shadcn-style aliases (`--muted`, `--border`, `--input`) in favor of `--ds-*` references at consumer sites. |
+| 5 | `installEmbeddedTheme()` cleanup | Remove the redundant `data-ngaf-chat-theme` set added in the uncommitted in-flight change. After the rename, `data-theme` is sufficient. |
+
+## Architecture
+
+**Chat lib three-layer cascade** (consumer-facing contract):
+
+```css
+/* L1: Default fallback */
+:root { /* LIGHT_TOKENS */ }
+
+/* L2: OS preference — kicks in only if L3 is unset */
+@media (prefers-color-scheme: dark) {
+ :root { /* DARK_TOKENS */ }
+}
+
+/* L3: Programmatic override — highest specificity, always wins */
+[data-theme="light"] { /* LIGHT_TOKENS */ }
+[data-theme="dark"] { /* DARK_TOKENS */ }
+```
+
+`[data-theme="dark"]` selector specificity (0,1,0,0) > `:root` (0,0,0,0). Programmatic override always wins regardless of source order or OS preference. Consumers who don't wire up `data-theme` get OS-preference behavior automatically.
+
+**Cockpit token discipline:**
+
+- All cockpit-app colors must be `var(--ds-*)` references or theme-flipping computed colors. No `rgba(0, 64, 144, X)` literals (the navy is light-theme accent — flips to bright blue in dark via `--ds-accent`). No `rgba(26, 27, 38, X)` literals (the dark canvas — flips to light surface via `--ds-surface`).
+- Drop the shadcn-style alias vars (`--muted`, `--border`, `--input`) currently defined in `cockpit.css :root`. Migrate consumers to `--ds-*` directly.
+
+**Border pattern:**
+
+- Cockpit uses Tailwind arbitrary values for borders. Pattern: `className="border-b border-[var(--ds-border)]"`.
+- No inline `style={{ borderBottom: '1px solid var(--ds-border)' }}` in cockpit components.
+- This pattern was already dominant; the change is migrating the small number of inline-style holdouts.
+
+## Package changes
+
+**`@ngaf/chat`:** patch bump
+- `src/lib/styles/chat-tokens.ts`: rename `[data-ngaf-chat-theme="light"]` → `[data-theme="light"]`, same for dark. The `:root` and `prefers-color-scheme` blocks remain unchanged.
+- No other chat lib changes (text-wrap bug etc. → PR 2).
+
+**`@ngaf/example-layouts`:** patch bump
+- `src/lib/install-embedded-theme.ts`: remove the `document.documentElement.dataset.ngafChatTheme = theme;` line and its comment. The existing `data-theme` set now drives both design-tokens and chat lib via the renamed selector.
+- Update `theme.css` if any namespace bridge references need adjusting (probably none — bridge stays at `:root` since it's now redundant for chat lib's own theming but still useful for visual cohesion if/when PR 3 unifies palettes).
+
+**`apps/cockpit`:**
+- `src/components/cockpit-shell.tsx`: keep the edge-to-edge content padding change. Migrate the inline `style={{ borderBottom: '1px solid var(--ds-border)' }}` to Tailwind class.
+- `src/components/sidebar/cockpit-sidebar.tsx`: migrate any inline border styles to Tailwind classes.
+- `src/app/cockpit.css`:
+ - Drop `--muted`, `--border`, `--input` aliases from `:root` block.
+ - Replace all `rgba(0, 64, 144, X)` literals with `var(--ds-accent-surface)` (alpha 0.04 / 0.06 sites) or `var(--ds-accent-border)` (alpha 0.12 / 0.15 sites).
+ - Replace `rgba(26, 27, 38, 0.95)` (line 142, dark-assumed) with `var(--ds-surface)`.
+
+## Per-file changes (summary table)
+
+| File | Change |
+|---|---|
+| `libs/chat/src/lib/styles/chat-tokens.ts` | Attribute selector rename only |
+| `libs/example-layouts/src/lib/install-embedded-theme.ts` | Remove `dataset.ngafChatTheme` line |
+| `apps/cockpit/src/components/cockpit-shell.tsx` | Border inline → Tailwind class; preserve edge-to-edge padding |
+| `apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx` | Border inline → Tailwind class wherever present |
+| `apps/cockpit/src/app/cockpit.css` | Replace literals with tokens; drop shadcn aliases |
+| Any cockpit consumer of `--muted` / `--border` / `--input` | Migrate to `--ds-*` (grep to find) |
+| `libs/chat/package.json` | Patch bump |
+| `libs/example-layouts/package.json` | Patch bump |
+
+## Verification
+
+**Unit:** `pnpm nx test chat example-layouts cockpit` — green.
+
+**Visual smoke (chrome MCP):**
+1. Cockpit on 3000, timeline angular on 4507 (pilot already wired)
+2. Navigate to `chat/core-capabilities/timeline/overview/python` (the chat-timeline capability)
+3. Dark mode (default): all surfaces use design-tokens dark palette, no white callouts bleeding through
+4. Toggle to light: same — no dark-assumed code-block backgrounds bleeding through
+5. Chat input area (in iframe) follows host theme — bg matches surrounding shell
+6. No inline `style={{ borderBottom }}` survives anywhere in cockpit components (grep gate)
+7. No `rgba(0, 64, 144` or `rgba(26, 27, 38` literals in `cockpit.css` (grep gate)
+
+## Risks and mitigations
+
+- **Chat lib attribute rename is breaking** for any external consumer that explicitly set `data-ngaf-chat-theme`. Acceptable per "no backwards compat" decision. Patch bump signal is sufficient for now; we're 0.0.x.
+- **Shadcn alias removal** could break code that references `--muted` / `--border` / `--input` directly. Mitigated by grep sweep before removing, plus typecheck (CSS vars are runtime not type-checked, but TS/TSX files that reference them via `style` or `className` would still work — they're just strings). Visual smoke catches regressions.
+- **Alpha shifts** (0.04 → 0.06, 0.12 → 0.15) might look slightly different in some callouts. Mitigated by visual smoke; tiny shifts at low-alpha values are imperceptible.
+- **`theme.css` namespace bridge becomes partially redundant** in the embedded apps after the rename — chat lib's own `[data-theme="dark"]` rule now takes priority over the bridge's `:root` mapping. Chat appears in its OWN dark palette (not design-tokens dark). Acceptable for this PR; visual unification is PR 3 scope.
+
+## Out-of-scope follow-ups (track but defer)
+
+- Chat lib text-wrap bug (`"hello"` rendering as `hel`/`lo`) — PR 2
+- Chat lib internal palette migration to `var(--ds-*)` — PR 3 or later
+- Doc h1/h2/h3 size tokenization (style analysis recommendation #4) — PR 3
+- Website migration to `cssVars('light')` from hardcoded hex (style analysis recommendation #3) — PR 3
+- Stage 2 (remaining 31 example apps) — separate plan/spec already drafted in earlier spec
diff --git a/libs/chat/package.json b/libs/chat/package.json
index 93e562c40..65930e2ad 100644
--- a/libs/chat/package.json
+++ b/libs/chat/package.json
@@ -1,6 +1,6 @@
{
"name": "@ngaf/chat",
- "version": "0.0.29",
+ "version": "0.0.30",
"exports": {
".": {
"types": "./index.d.ts",
diff --git a/libs/chat/src/lib/styles/chat-tokens.ts b/libs/chat/src/lib/styles/chat-tokens.ts
index 7496c9a64..82acafd76 100644
--- a/libs/chat/src/lib/styles/chat-tokens.ts
+++ b/libs/chat/src/lib/styles/chat-tokens.ts
@@ -172,9 +172,9 @@ const REDUCED_MOTION_STYLES = `
*
* Theme switching:
* - `prefers-color-scheme: dark` → dark by default.
- * - `[data-ngaf-chat-theme="dark"]` on `` / `` / any wrapper
+ * - `[data-theme="dark"]` on `` / `` / any wrapper
* forces dark.
- * - `[data-ngaf-chat-theme="light"]` forces light.
+ * - `[data-theme="light"]` forces light.
*/
export const ROOT_TOKEN_STYLES = `
@layer ngaf-chat {
@@ -187,10 +187,10 @@ export const ROOT_TOKEN_STYLES = `
@media (prefers-color-scheme: dark) {
:root { ${DARK_TOKENS} }
}
- :root[data-ngaf-chat-theme="light"],
- [data-ngaf-chat-theme="light"] { ${LIGHT_TOKENS} }
- :root[data-ngaf-chat-theme="dark"],
- [data-ngaf-chat-theme="dark"] { ${DARK_TOKENS} }
+ :root[data-theme="light"],
+ [data-theme="light"] { ${LIGHT_TOKENS} }
+ :root[data-theme="dark"],
+ [data-theme="dark"] { ${DARK_TOKENS} }
}
${KEYFRAMES}
${REDUCED_MOTION_STYLES}
diff --git a/libs/chat/src/lib/styles/chat.css b/libs/chat/src/lib/styles/chat.css
index bc0b5107b..db51d592f 100644
--- a/libs/chat/src/lib/styles/chat.css
+++ b/libs/chat/src/lib/styles/chat.css
@@ -145,7 +145,7 @@
}
@media (prefers-color-scheme: dark) {
- :root:not([data-ngaf-chat-theme="light"]) {
+ :root:not([data-theme="light"]) {
--ngaf-chat-bg: rgb(17, 17, 17);
--ngaf-chat-surface: rgb(28, 28, 28);
--ngaf-chat-surface-alt: rgb(44, 44, 44);
@@ -165,7 +165,7 @@
}
}
-[data-ngaf-chat-theme="dark"] {
+[data-theme="dark"] {
--ngaf-chat-bg: rgb(17, 17, 17);
--ngaf-chat-surface: rgb(28, 28, 28);
--ngaf-chat-surface-alt: rgb(44, 44, 44);
diff --git a/libs/example-layouts/package.json b/libs/example-layouts/package.json
index 3d1283ef8..de7277101 100644
--- a/libs/example-layouts/package.json
+++ b/libs/example-layouts/package.json
@@ -1,6 +1,6 @@
{
"name": "@ngaf/example-layouts",
- "version": "0.0.30",
+ "version": "0.0.31",
"peerDependencies": {
"@angular/core": "^20.0.0 || ^21.0.0",
"@angular/common": "^20.0.0 || ^21.0.0",
diff --git a/libs/example-layouts/src/lib/install-embedded-theme.ts b/libs/example-layouts/src/lib/install-embedded-theme.ts
index 4f68cb9a2..1f4ef977d 100644
--- a/libs/example-layouts/src/lib/install-embedded-theme.ts
+++ b/libs/example-layouts/src/lib/install-embedded-theme.ts
@@ -5,8 +5,10 @@ import { cssVars, type Theme } from '@ngaf/design-tokens';
* framework (Angular, Vue, etc.) bootstraps.
*
* Behavior:
- * 1. Applies the default theme synchronously (sets `data-theme` and
- * every `--ds-*` CSS variable on `document.documentElement`).
+ * 1. Applies the default theme synchronously: sets `data-theme` on
+ * `` (which both `@ngaf/design-tokens`-aware code and
+ * `@ngaf/chat` honor) plus every `--ds-*` CSS variable on the
+ * same element.
* 2. Posts `{ type: 'ngaf:theme-request' }` to `window.parent` so the
* host (cockpit's ``) replies with the current theme
* even if its broadcast ran before this iframe mounted.