Commit 44388f2
feat(cockpit): make Run discoverable from Code (#934)
* docs(cockpit): design for making Run discoverable from Code
Records the brainstormed design: mode stops being sticky, the mode rail
becomes legible as a switch, Run carries a runtime phase dot, and the
Activity dot is re-scoped to unseen problems so the two dots make
distinct claims.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): implementation plan for Run discoverability
Six TDD tasks: drop mode stickiness, make the rail legible as a switch,
add a status-dot slot to the ui-react rail primitive, wire the runtime
phase onto Run, re-scope the Activity indicator to unseen problems, and
verify in a browser.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cockpit): stop persisting the active mode so every capability opens in Run
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cockpit): restore docs-surface coverage, key shell on route, tighten mode tests
Address code-review findings on 940e21d8:
- Restore the only coverage of the docs-surface setExpanded branch, lost
when the mode-persistence tests were rewritten (mutation-verified by
flipping `surface === 'docs'` to `false`).
- Key CockpitShell on canonicalPath in the [...slug] route so client-side
navigation between capabilities always remounts fresh state, instead of
relying on React Router reconciliation behavior that may or may not
preserve activeMode across a params-only navigation. Add a component
test proving a capability switch lands on Run even after switching to
Code, mutation-verified by reverting to a shared key.
- Give the invalid-mode-query test discriminating power by asserting the
query param is stripped, not just that the default mode is shown.
- Fix a prettier violation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): correct the plan's verification commands
apps/cockpit has no lint target (only ui-react does), and nx test
swallows the vitest reporter output in this worktree, so real counts
need vitest run --root directly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(cockpit): add real regression coverage for the per-route CockpitShell key
The component-level test added in 58f55680 hard-coded key={...} in its own
JSX and never imported the route module, so it only proved React's
remount-on-key-change semantics rather than guarding the actual production
wiring. Deleting key={canonicalPath} from page.tsx left the full cockpit
suite green.
Add page.spec.tsx, which imports the [...slug] route module directly and
asserts the returned element's key equals the canonical path for a slug,
and that two different capabilities get two different keys. next/navigation
and the content-bundle loader are mocked so the route can be exercised in
isolation.
page.tsx also needed an explicit `import React from 'react'` (matching the
sibling apps/cockpit/src/app/page.tsx) — without it, evaluating the module
under Vitest's jsx transform threw ReferenceError: React is not defined
before the route logic ever ran.
Mutation-verified: with key={canonicalPath} removed, both new tests fail
(element.key is null); restored via `git checkout --` and confirmed
`git diff --exit-code` was clean before reapplying the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(cockpit): make the mode rail read as a switch
Group the four mode buttons under a "View" caption, separate them
from the Activity/Settings utilities with a rule line, and lift
inactive rail items off --ds-text-muted (the disabled-text token)
onto --ds-text-secondary so the rail reads as a switch, not chrome.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cockpit): make rail separator visible and group label accessible
Two review fixes to 3d43dd54:
- The utilities-group separator used --ds-border, which is a one-value
difference from --ds-surface-tinted (the rail background) in dark
mode (rgb(45,45,45) on rgb(44,44,44)) -- effectively invisible in
the cockpit's default theme. Switch to --ds-border-strong, the token
the pane divider already uses for this same collision.
- The "View" caption on the mode group was aria-hidden, on the theory
that the nav's own aria-label covered it. It doesn't: Activity and
Settings render inside the same <nav>, so a screen-reader user heard
one flat "Cockpit modes" sequence with no group boundary -- sighted
users got the new two-group structure, non-sighted users didn't.
Replace aria-hidden with role="group" + aria-labelledby on the
primary group div, which announces "View" as a group label without
adding a second landmark.
Also bumped the caption to --ds-text-secondary/10px to match
[data-control-plane-rail-label], and made the spec's cockpit.css path
resolution cwd-independent via import.meta.url instead of a
process.cwd() suffix check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(ui-react): add a status dot slot to control plane rail items
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(ui-react): fold rail item status into one object prop
status and statusLabel were independent optional props, so either
could be set without the other: a status with no accessible label
(colour-only, WCAG 1.4.1) or a label with no dot. Combine them into
a single ControlPlaneRailItemStatus object so both invalid states
are unrepresentable. No consumers of either prop existed yet, so
this is a clean API tightening, not a migration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): align Task 4 with the folded rail status prop
Task 3 tightened status/statusLabel into one { kind, label } object;
update Task 4's mapping, wiring and mutation check to match, and record
the flex-gap and tooltip-placement hazards it must handle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(cockpit): show runtime phase on the Run rail item
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cockpit): separate the rail tooltip from the status accessible name
A labelled rail item with a status no longer renders the icon-rail
tooltip, whose positioning is authored for the 56px rail and lands over
the context pane. The status stays in the accessible name via aria-label
and in a visually hidden span. The status dot's ring now tracks the rail
item background so hover does not show a halo, and the dot keeps an
explicit forced-colors treatment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(cockpit): re-scope the Activity indicator to unseen problems
Run's rail dot now reports what the runtime is doing right now, so the
Activity dot no longer duplicates it. Activity means "there are problems
in the log you have not read": errors only, counted from a seen marker
that the panel clears on open. An error that self-recovers keeps its
trace until someone looks.
Also makes runtimeNeedsAttention an exhaustive switch so a new
RuntimePhase fails to compile there as it already does in
runtimeRailStatus, and decouples the Run rail assertions from the now
phase-dependent accessible name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): correct the activity log ordering claim
activityReducer prepends, so the log is newest-first: the unseen window
runs from index 0, not from seenCount. The plan asserted the opposite and
its three specified tests passed under both readings, so the inverted
selector would have shipped green. Record the direction and the
count-marker saturation limit at the 50-event cap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): runtimeNeedsAttention is dead, not live
Both the spec and the plan claimed the helper still had a caller --
the spec said it drives the Run dot's error bucket, the plan said
use-runtime-controller.ts uses it. Neither is true: runtimeRailStatus
covers the Run dot, and removing the Activity dot's call left zero
production callers. Record that it is deleted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(cockpit): drop dead runtimeNeedsAttention and cover the clear path
Re-scoping the Activity dot removed the last caller of
runtimeNeedsAttention; the Run dot goes through runtimeRailStatus. An
exported, tested, uncalled predicate is what gets miswired back later, so
delete it and its test table rather than hardening it.
Adds the missing clear-path coverage: resetting the seen marker on clear
is load-bearing, and dropping it leaves the marker stranded over an empty
log, swallowing the next N problems for the rest of the page visit.
Also drops a provably dead inner clamp in countUnseenProblems (slice
already clamps a too-large end index) while keeping the outer one, which
is not defensive -- a negative end index counts back from the tail and
reports already-seen errors as unseen. The test for it now proves that.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(cockpit): record Task 6 browser verification results
All six tasks verified live in both themes against a fresh build.
Records two false alarms caused by testing under a live dev server
after git-level file swaps, and the procedure that avoids them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 5142688 commit 44388f2
21 files changed
Lines changed: 1976 additions & 124 deletions
File tree
- apps/cockpit/src
- app
- [...slug]
- components
- control-plane
- lib/runtime
- docs/superpowers
- plans
- specs
- libs/ui-react/src
- lib/control-plane
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
363 | 363 | | |
364 | 364 | | |
365 | 365 | | |
| 366 | + | |
366 | 367 | | |
367 | 368 | | |
368 | 369 | | |
| |||
373 | 374 | | |
374 | 375 | | |
375 | 376 | | |
| 377 | + | |
376 | 378 | | |
377 | 379 | | |
378 | 380 | | |
| |||
387 | 389 | | |
388 | 390 | | |
389 | 391 | | |
390 | | - | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
391 | 411 | | |
392 | 412 | | |
| 413 | + | |
393 | 414 | | |
394 | 415 | | |
395 | 416 | | |
| |||
400 | 421 | | |
401 | 422 | | |
402 | 423 | | |
403 | | - | |
| 424 | + | |
404 | 425 | | |
405 | 426 | | |
406 | 427 | | |
407 | 428 | | |
408 | 429 | | |
409 | 430 | | |
| 431 | + | |
410 | 432 | | |
411 | 433 | | |
412 | 434 | | |
| |||
419 | 441 | | |
420 | 442 | | |
421 | 443 | | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
422 | 462 | | |
423 | 463 | | |
424 | 464 | | |
| |||
950 | 990 | | |
951 | 991 | | |
952 | 992 | | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
953 | 997 | | |
954 | 998 | | |
955 | 999 | | |
| |||
0 commit comments