From efdce199839f6b8971ad5d8065d5d2ec46aad65b Mon Sep 17 00:00:00 2001 From: Brian Love Date: Thu, 18 Jun 2026 22:12:05 -0700 Subject: [PATCH 1/5] docs: plan for demo welcome-suggestion clarity + palette validation Co-Authored-By: Claude Fable 5 --- .../plans/2026-06-19-demo-welcome-clarity.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-19-demo-welcome-clarity.md diff --git a/docs/superpowers/plans/2026-06-19-demo-welcome-clarity.md b/docs/superpowers/plans/2026-06-19-demo-welcome-clarity.md new file mode 100644 index 00000000..736e682c --- /dev/null +++ b/docs/superpowers/plans/2026-06-19-demo-welcome-clarity.md @@ -0,0 +1,117 @@ +# Demo Welcome-Suggestion Clarity + Palette Validation — Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). + +**Goal:** Make the demos' welcome suggestions self-explanatory to a developer (clearer, inferrable titles + a short description per suggestion, surfaced as a multiline dropdown subtitle and a hover tooltip on chips), repo-wide; and stop stale `localStorage` palette values from breaking the demos on a fresh checkout (validate-on-read). + +**Design (settled via audit + decisions):** +- **Lib:** `ChatSelectOption` gains an optional `description?: string`; `chat-select` renders it as a subtitle line (multiline option). `chat-welcome-suggestion` (the chip) gains an optional `description?` input rendered as a `title` + `aria-description` **hover/focus tooltip** (no visible subtitle — keeps chips compact). One `description` field serves both surfaces. +- **Copy:** Every welcome suggestion across the 13 apps gets a developer-inferrable title + a 1-line description. Guidelines below. +- **Persistence (Item B):** the two demo palette services validate each read against the current allowed value-set; an unknown/stale value falls back to the default (no versioning). + +**Tech Stack:** Angular 21 (signals, `input`), vitest, Nx. No backcompat constraint. + +**Audit inventory (the source of truth for which apps/suggestions exist):** see the AP1 repo-wide audit — 13 apps. `examples/chat` (17 suggestions) + `examples/ag-ui` (7) use a featured-chip + `chat-select` "More prompts" dropdown; 11 cockpit apps use plain `chat-welcome-suggestion` chips (1–2 each). + +--- + +## Copy guidelines (apply to every suggestion) + +- **Title:** a short, developer-inferrable noun/verb phrase that names the *capability or pattern* being shown (not jargon). Sentence case. Examples: "Consent-gated clear" → **"Clear a day (asks first)"**; "Human approval" → **"Approve before a refund"**; "Demo: render a contact form" → **"Generative UI: contact form"**. +- **Description:** one concise sentence (≤ ~90 chars) saying what the prompt exercises / what the user will see. Example: "Streams a form component the model fills in, then you submit it back." +- **Value (the prompt):** keep existing prompts unless a title change makes a small wording tweak natural; do **not** alter prompts that e2e specs assert on (see Verify). +- Keep capitalization/style consistent across apps. + +--- + +## Task 1: Lib — `ChatSelectOption.description` + multiline option + chip tooltip + +**Files:** +- `libs/chat/src/lib/primitives/chat-select/chat-select.component.ts` +- `libs/chat/src/lib/styles/chat-select.styles.ts` +- `libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.ts` +- specs for both components + +- [ ] **Step 1 (chat-select):** add `description?: string` to `ChatSelectOption`. Update the option ` +``` +Update `chat-select.styles.ts`: make `.chat-select__option` a vertical flex (`display:flex; flex-direction:column; align-items:flex-start`), add `.chat-select__option-desc` (smaller, `--ngaf-chat-text-muted`/the muted token used elsewhere, `white-space:normal`, `line-height:1.3`, small margin-top). Keep label-only options rendering unchanged (no desc → no subtitle). + +- [ ] **Step 2 (chat-welcome-suggestion):** add `readonly description = input()`. In the template, bind it as a tooltip on the button: `[attr.title]="description() || null"` and `[attr.aria-description]="description() || null"`. Do NOT add a visible subtitle (keep the chip compact). Keep `label`/`value`/icon slot unchanged. + +- [ ] **Step 3:** Update the existing component specs (`chat-select.component.spec.ts`, `chat-welcome-suggestion.component.spec.ts`): assert an option with a `description` renders the `.chat-select__option-desc` text; assert a chip with `description` sets `title`/`aria-description`; assert absence renders neither (no regression). + +- [ ] **Step 4:** `npx nx run-many -t test lint build --projects=chat --skip-nx-cache` — green. Commit: +```bash +git add libs/chat/src/lib/primitives/chat-select libs/chat/src/lib/primitives/chat-welcome libs/chat/src/lib/styles/chat-select.styles.ts +git commit -m "feat(chat): optional description on ChatSelectOption (multiline) + chat-welcome-suggestion (tooltip)" +``` + +--- + +## Task 2: examples/* — improved titles + descriptions + wire description through + +**Files:** +- `examples/chat/angular/src/app/modes/welcome-suggestions.ts` + `welcome-suggestions.component.ts` +- `examples/ag-ui/angular/src/app/modes/welcome-suggestions.ts` + `welcome-suggestions.component.ts` + +- [ ] **Step 1:** Extend the `WelcomeSuggestion` interface in each app to `{ label: string; value: string; description: string }`. Rewrite every suggestion's `label` (per the copy guidelines) and add a `description`. Do NOT change `value` prompts that e2e relies on (the examples/chat `url-routing`/`send-receive` specs and examples/ag-ui specs may assert on sent prompts — check `e2e/*.spec.ts` first; keep those prompt strings intact). +- [ ] **Step 2:** Wire `description` through: in `welcome-suggestions.component.ts`, pass `[description]` to the featured `chat-welcome-suggestion`, and include `description` in the `moreOptions` mapping to `ChatSelectOption[]` (so the dropdown is multiline). +- [ ] **Step 3:** Build both apps: `npx nx run-many -t build --projects=examples-chat-angular,examples-ag-ui-angular --skip-nx-cache` — green. +- [ ] **Step 4:** Commit: +```bash +git add examples/chat/angular/src/app/modes examples/ag-ui/angular/src/app/modes +git commit -m "feat(examples): clearer welcome-suggestion titles + descriptions (multiline dropdown)" +``` + +--- + +## Task 3: cockpit/* — improved titles + descriptions (chip tooltips) + +**Files (11 apps, inline suggestion arrays in each `*.component.ts`):** tool-calls, subagents, a2ui, interrupts, generative-ui (chat); streaming, interrupts, persistence (langgraph); json-render, a2ui, interrupts (ag-ui). (See the AP1 inventory for each file + current suggestions.) + +- [ ] **Step 1:** In each cockpit app's component, the inline suggestions array gains a `description` per item and improved `label`s (copy guidelines). Pass `[description]` to each `chat-welcome-suggestion` chip in the template. Where two apps mirror each other (e.g. chat/interrupts ≈ ag-ui/interrupts), use identical copy. +- [ ] **Step 2:** Build the affected cockpit apps (a representative `nx run-many -t build` across the 11, or `--all` if faster) — green. +- [ ] **Step 3:** Commit: +```bash +git add cockpit +git commit -m "feat(cockpit): clearer welcome-suggestion titles + descriptions (chip tooltips)" +``` + +--- + +## Task 4: Item B — validate-on-read in the palette services + +**Files:** +- `examples/chat/angular/src/app/shell/palette-persistence.service.ts` +- `examples/ag-ui/angular/src/app/shell/palette-persistence.service.ts` + +- [ ] **Step 1:** Each palette service exposes typed reads (model/effort/genUiMode/theme/colorScheme/sidenavMode/etc.). Add a validation layer: for each enum-like field, define the **current allowed value-set** (mirror the dropdown option values the shell already declares — import/reuse them rather than duplicating where possible) and, on read, return the stored value only if it's in the allowed set; otherwise return `undefined` (so the caller's `?? default` kicks in). Non-enum fields (e.g. `selectedProjectId`, free strings, booleans) pass through unchanged. This makes a stale/renamed value (e.g. an old `effort`) fall back to the default instead of loading an invalid selection. +- [ ] **Step 2:** Add/extend the palette service spec: a stored value outside the allowed set reads back as `undefined` (→ default); a valid value reads back unchanged. +- [ ] **Step 3:** Build both apps + run the chat/ag-ui shell specs. Commit: +```bash +git add examples/chat/angular/src/app/shell/palette-persistence.service.ts examples/ag-ui/angular/src/app/shell/palette-persistence.service.ts examples/*/angular/src/app/shell/*.spec.ts +git commit -m "fix(examples): validate palette localStorage reads (stale value → default)" +``` + +--- + +## Task 5: Verify + PR + +- [ ] **Step 1:** `npx nx run-many -t test lint build --projects=chat --skip-nx-cache`; build all affected example + cockpit apps; run the `examples/chat` + `examples/ag-ui` e2e (free ports first) to confirm no welcome-suggestion/prompt assertion broke. If an e2e asserts a label/prompt that changed, reconcile (prefer keeping the prompt; update the spec only if the label is what's asserted and the new label is correct). +- [ ] **Step 2:** Final review (correctness of the lib option/tooltip change; copy quality + consistency; the palette validation doesn't drop valid values; no e2e regressions). +- [ ] **Step 3:** Regenerate api-docs (chat gained a `description` on `ChatSelectOption` + chip input); commit if changed. Open PR, arm auto-merge, self-healing watcher. + +--- + +## Self-Review +- Coverage: lib option+tooltip (T1); examples copy+wiring (T2); cockpit copy (T3); palette validation (T4); verify+PR (T5). ✓ +- Consistency: one `description` field powers both the dropdown subtitle and the chip tooltip; copy guidelines applied uniformly. +- Risk: changing welcome-suggestion `value` prompts could break e2e — explicitly guarded (keep prompts; check specs first). Palette validation must not drop valid values — covered by the spec. +- Conflict-safety vs #685: touches `libs/chat` primitives + example/cockpit apps; does NOT touch `agent.ts`/`public-api` agent exports, so no overlap with the #685 DX branch. From 99b01e3061c30c28ac915d46382e3688778553dd Mon Sep 17 00:00:00 2001 From: Brian Love Date: Thu, 18 Jun 2026 22:23:31 -0700 Subject: [PATCH 2/5] feat(chat): optional description on ChatSelectOption (multiline) + chat-welcome-suggestion (tooltip) Co-Authored-By: Claude Fable 5 --- .../chat-select/chat-select.component.spec.ts | 13 +++++++++++++ .../chat-select/chat-select.component.ts | 6 +++++- .../chat-welcome-suggestion.component.spec.ts | 14 ++++++++++++++ .../chat-welcome-suggestion.component.ts | 10 +++++++++- libs/chat/src/lib/styles/chat-select.styles.ts | 11 ++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/libs/chat/src/lib/primitives/chat-select/chat-select.component.spec.ts b/libs/chat/src/lib/primitives/chat-select/chat-select.component.spec.ts index 2f017e29..f9e43e49 100644 --- a/libs/chat/src/lib/primitives/chat-select/chat-select.component.spec.ts +++ b/libs/chat/src/lib/primitives/chat-select/chat-select.component.spec.ts @@ -63,6 +63,19 @@ describe('ChatSelectComponent', () => { expect(opts.length).toBe(3); }); + it('renders an option description as a subtitle, and omits it when absent', () => { + setSignalInput(fixture, 'options', [ + { value: 'a', label: 'Alpha', description: 'The first letter' }, + { value: 'b', label: 'Bravo' }, + ] as readonly ChatSelectOption[]); + fixture.detectChanges(); + host.querySelector('.chat-select__trigger')!.click(); + fixture.detectChanges(); + const opts = host.querySelectorAll('.chat-select__option'); + expect(opts[0].querySelector('.chat-select__option-desc')?.textContent).toContain('The first letter'); + expect(opts[1].querySelector('.chat-select__option-desc')).toBeNull(); + }); + it('emits valueChange and closes the menu on option click', () => { let emitted: string | undefined; fixture.componentInstance.value.subscribe((v) => { emitted = v; }); diff --git a/libs/chat/src/lib/primitives/chat-select/chat-select.component.ts b/libs/chat/src/lib/primitives/chat-select/chat-select.component.ts index e012f006..d41e0394 100644 --- a/libs/chat/src/lib/primitives/chat-select/chat-select.component.ts +++ b/libs/chat/src/lib/primitives/chat-select/chat-select.component.ts @@ -19,6 +19,7 @@ import { CHAT_SELECT_STYLES } from '../../styles/chat-select.styles'; export interface ChatSelectOption { value: string; label: string; + description?: string; disabled?: boolean; } @@ -72,7 +73,10 @@ export interface ChatSelectOption { [attr.aria-selected]="opt.value === value()" (click)="selectOption(opt)" > - {{ opt.label }} + {{ opt.label }} + @if (opt.description) { + {{ opt.description }} + } } diff --git a/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.spec.ts b/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.spec.ts index 72df7b28..ddb76ac0 100644 --- a/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.spec.ts +++ b/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.spec.ts @@ -51,4 +51,18 @@ describe('ChatWelcomeSuggestionComponent', () => { button!.click(); expect(emitted).toBe('tell-me'); }); + + it('exposes description as a title + aria-description tooltip when set', () => { + setSignalInput(fixture.componentInstance.description, 'What this prompt demonstrates'); + fixture.detectChanges(); + const button = (fixture.nativeElement as HTMLElement).querySelector('button')!; + expect(button.getAttribute('title')).toBe('What this prompt demonstrates'); + expect(button.getAttribute('aria-description')).toBe('What this prompt demonstrates'); + }); + + it('omits the tooltip attributes when no description is provided', () => { + const button = (fixture.nativeElement as HTMLElement).querySelector('button')!; + expect(button.getAttribute('title')).toBeNull(); + expect(button.getAttribute('aria-description')).toBeNull(); + }); }); diff --git a/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.ts b/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.ts index 90dbff88..69c94d53 100644 --- a/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.ts +++ b/libs/chat/src/lib/primitives/chat-welcome/chat-welcome-suggestion.component.ts @@ -9,7 +9,13 @@ import { CHAT_WELCOME_SUGGESTION_STYLES } from '../../styles/chat-welcome.styles changeDetection: ChangeDetectionStrategy.OnPush, styles: [CHAT_HOST_TOKENS, CHAT_WELCOME_SUGGESTION_STYLES], template: ` -