Skip to content

feat(chat): ChatApprovalCard dialog composition + ChatInterruptPanel refresh#552

Merged
blove merged 8 commits into
mainfrom
claude/hitl-chat-approval-card
May 28, 2026
Merged

feat(chat): ChatApprovalCard dialog composition + ChatInterruptPanel refresh#552
blove merged 8 commits into
mainfrom
claude/hitl-chat-approval-card

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 27, 2026

Summary

Library foundation for the HITL refund forcing-function work stream (PR #1 of 3).

  • Pin structured-resume behavior. New libs/langgraph test demonstrates multi-field structured objects ({ approved, amount, idempotency_key, meta }) forwarding through agent.submit({ resume }) to LangGraph's Command(resume=…) verbatim. The forwarding logic already existed; the test locks the contract PR feat(cockpit): server-side content bundle with Shiki highlighting #2 will depend on.
  • New ChatApprovalCard composition. Native HTML <dialog> + showModal(), top-layer rendering, focus trap and Escape for free. API: [agent], [matchKind], [title], [showEdit], (action) emits 'approve' | 'edit' | 'cancel'. Body is a content-projected <ng-template #body let-payload> slot. 9 unit tests cover body projection, matchKind filter (both branches), all three action emits, showEdit visibility, dialog open/close transitions, and Escape → cancel with preventDefault.
  • ChatInterruptPanel visual refresh. Drops the thick amber left border + triangle warning icon. Adopts the eyebrow + small amber dot pattern. Shares button styles with the new approval card. No API change — same inputs, same (action) enum, same DOM placement.
  • Spec + plan docs committed to docs/superpowers/.

Visual design

Approval card uses an HTML <dialog> modal — explicitly halts the conversation for high-stakes consent. Backdrop click does NOT close (must use a button). Escape → cancel. Refreshed interrupt panel stays inline (lower-stakes "agent paused, look at this" pattern). The two compositions share visual vocabulary but signal different intent.

Test plan

Spec: docs/superpowers/specs/2026-05-25-hitl-refund-cockpit-blog-design.md
Plan: docs/superpowers/plans/2026-05-25-hitl-refund-cockpit-blog.md

🤖 Generated with Claude Code

blove added 4 commits May 27, 2026 16:06
Also include the design spec + implementation plan for the HITL refund
work stream.
Drop the thick amber left border and triangle warning icon. Adopt the
eyebrow + small dot pattern that matches the new ChatApprovalCard
composition. Share .btn / .btn-primary / .btn-secondary / .btn-text
styles across both compositions. No API change.
Drop the typeof-function guards from ChatApprovalCardComponent — they
were coupling production code to a JSDOM limitation. Stub
HTMLDialogElement.prototype.showModal/close in the spec's beforeEach so
Vitest still runs. Add two missing tests covering the dialog
open/close effect on interrupt presence and the native cancel event
(Escape) emitting 'cancel' with preventDefault.
@blove blove enabled auto-merge (squash) May 27, 2026 23:55
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment May 28, 2026 12:42am

Request Review

blove and others added 3 commits May 27, 2026 17:39
Added in PR #433 with `test.skip(...)` and no condition or comment
explaining the skip. Two years later it's still skipped and adds
nothing to CI signal.

The test's intent — assert the "Stop generating" button is visible
mid-stream, click it, observe abort — is fundamentally flaky against
aimock-replayed streams: the streaming chunks arrive in <100ms and
signal-batched updates can collapse the visible-then-hidden state
below Playwright's polling resolution. This is already documented
in test-helpers.ts:95-100 as the reason `submitAndWaitForResponse`
asserts on the rendered `chat-message` element's `data-streaming`
attribute (not on input/button affordances). The same constraint
makes this test unimplementable as-written against aimock.

If/when we add a slow-streaming fixture path (or run e2e against a
non-aimock backend), reintroduce the test alongside that. Until then
it's dead code.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onstraint (#549)

* fix(docs): defer sidebar reveal to lg breakpoint

Mobile audit flagged the docs sidebar as 500–925% viewport-height sticky on tablet (768px). At that width it crowded the article and was the largest sticky/fixed offender. Pushing the reveal from md:block (768px) to lg:block (1024px) hides it for tablet/narrow-desktop and lets the article use the full width. Mobile menu's docs tab already covers nav at <lg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(website): constrain layout viewport on mobile

Follow-up to #547. body { overflow-x: hidden } prevents the user-visible scrollbar but doesn't stop mobile browsers from EXPANDING the layout viewport when child content (code blocks, wide grids) exceeds device-width. The fixed nav then renders at that expanded width — its right side (hamburger button) ends up clipped offscreen.

Two additions:

1. html { overflow-x: hidden } — anchors the layout viewport to the visual viewport so the fixed nav and other position:fixed elements stay aligned with what the user sees.
2. pre { max-width: 100%; overflow-x: auto } as a universal rule (was already on .shiki and .docs-prose pre; this catches any other pre that might appear in landing components without those classes).

Verified live in prod via Chrome MCP: the previous PR's body { overflow-x: hidden } and h1..h6 { word-break } are both applied; the remaining overflow numbers come from layout-viewport expansion, which this PR addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gistry (#551)

* docs(spec): cockpit port registry design

* docs(plan): cockpit port registry implementation plan

* feat(ci): cockpit ports registry + verifier spec (skeleton)

Registry starts empty; the verifier spec tests both the registry
shape (positive int ports, no duplicates, range/convention checks)
and cross-file invariants (python --port + playwright baseURL match
registry). Empty registry intentionally fails the 'covers every
cap' test until Task 2 populates it.

Spec: docs/superpowers/specs/2026-05-27-cockpit-port-registry-design.md

* feat(ci): populate cockpit ports registry from on-disk values

31 entries, extracted from each cap's proxy.conf.json target +
playwright.config.ts baseURL (or langgraph - 1000 for non-e2e caps).
Verifier confirms the registry round-trips cleanly against every
python/project.json --port literal.

* test(ports): verifier skips python --port check for caps without literal

Only chat caps have --port in their python/project.json serve target;
langgraph/deep-agents/render caps rely on the e2e harness spawning
langgraph with explicit --port from global-setup-impl.ts. Skip
verifier check for those rather than fail.

* refactor(cockpit): 31 proxy.conf.json → proxy.conf.mjs (import ports registry)

Every cockpit cap's proxy.conf.json is replaced with proxy.conf.mjs
that imports portsFor(capName) from cockpit/ports.mjs and templates
the langgraph port into the target URL. Each angular/project.json's
serve.options.proxyConfig is updated to point at the .mjs file.

cockpit/ag-ui/streaming is excluded — non-LangGraph backend, kept
as proxy.conf.json with its /agent → :3000 literal.

* refactor(cockpit): 24 e2e configs import ports from registry

Each cap's e2e/global-setup-impl.ts now imports portsFor() and reads
angular/langgraph ports from the registry instead of literal numbers.
playwright.config.ts is updated similarly for baseURL.
All 24 e2e tsconfig.json files gain allowJs: true so tsc accepts the
.mjs import without a declaration file.

The verifier's playwright-baseURL check now skips templated configs
(its regex only matches literal URLs); drift is impossible because
the value imports directly from the registry.

* ci: gate cockpit-ports.spec.mjs in ci-scope test job

Appends the new verifier spec to the existing node --test invocation
so registry drift is caught at PR time.

* test(cockpit): wiring spec reads ports from registry; recognizes .mjs proxy

The drift-guard test previously parsed proxy.conf.json target + the
literal langgraphPort/angularPort lines in global-setup-impl.ts. Both
were removed by the port-registry migration:
- proxy.conf.json → proxy.conf.mjs (templates target from registry)
- global-setup-impl.ts now reads `ports.langgraph` / `ports.angular`

Updated wiring spec:
- Imports `portsFor` from cockpit/ports.mjs to look up expected ports
  by project name. Falls back to literal parsing for the ag-ui
  exception (not in registry).
- Replaces the proxy.conf.json content assertion with a proxy.conf.mjs
  existence + `portsFor('<name>')` call check. Drift is impossible
  because the .mjs templates from the same registry.
- Legacy proxy.conf.json branch retained for ag-ui (single-cap
  exception).

79/79 cockpit unit tests pass locally.
@blove blove merged commit 00d493f into main May 28, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant