@stigmer/react 3.12.9 → 3.14.0-dev.20260910084630

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/cursor-accounts/CursorAccountsConsole.d.ts +3 -2
  2. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  3. package/cursor-accounts/CursorAccountsConsole.js +3 -2
  4. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  5. package/cursor-accounts/MemberCoverageTable.d.ts +11 -3
  6. package/cursor-accounts/MemberCoverageTable.d.ts.map +1 -1
  7. package/cursor-accounts/MemberCoverageTable.js +70 -42
  8. package/cursor-accounts/MemberCoverageTable.js.map +1 -1
  9. package/cursor-accounts/MemberKeysPanel.d.ts.map +1 -1
  10. package/cursor-accounts/MemberKeysPanel.js +4 -1
  11. package/cursor-accounts/MemberKeysPanel.js.map +1 -1
  12. package/internal/ResizableSplit.d.ts +1 -1
  13. package/package.json +4 -4
  14. package/provider-standing/useProviderStanding.d.ts +1 -1
  15. package/provider-standing/useProviderStanding.js +1 -1
  16. package/sidebar/SettingsSidebar.d.ts.map +1 -1
  17. package/sidebar/SettingsSidebar.js +3 -3
  18. package/sidebar/SettingsSidebar.js.map +1 -1
  19. package/sidebar/WorkspaceSidebar.d.ts +1 -1
  20. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  21. package/sidebar/WorkspaceSidebar.js +19 -15
  22. package/sidebar/WorkspaceSidebar.js.map +1 -1
  23. package/sidebar/chrome.d.ts +21 -7
  24. package/sidebar/chrome.d.ts.map +1 -1
  25. package/sidebar/chrome.js +25 -7
  26. package/sidebar/chrome.js.map +1 -1
  27. package/src/cursor-accounts/CursorAccountsConsole.tsx +3 -2
  28. package/src/cursor-accounts/MemberCoverageTable.tsx +222 -200
  29. package/src/cursor-accounts/MemberKeysPanel.tsx +25 -1
  30. package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +64 -8
  31. package/src/cursor-accounts/__tests__/MemberCoverageTable.layout.test.tsx +212 -0
  32. package/src/internal/ResizableSplit.tsx +1 -1
  33. package/src/provider-standing/useProviderStanding.ts +1 -1
  34. package/src/sidebar/SettingsSidebar.tsx +11 -5
  35. package/src/sidebar/WorkspaceSidebar.tsx +75 -67
  36. package/src/sidebar/__tests__/SettingsSidebar.test.tsx +6 -2
  37. package/src/sidebar/chrome.tsx +29 -15
  38. package/src/workflow/__tests__/WorkflowExecutionViewer.reconciliation.test.tsx +11 -9
  39. package/src/workflow/__tests__/WorkflowTaskThread.test.tsx +54 -2
  40. package/src/workflow/thread/WorkflowTaskThread.tsx +47 -22
  41. package/styles.css +1 -1
  42. package/workflow/thread/WorkflowTaskThread.js +34 -11
  43. package/workflow/thread/WorkflowTaskThread.js.map +1 -1
@@ -9,8 +9,9 @@ import { OrgSwitcher } from "../organization/OrgSwitcher.js";
9
9
  // ---------------------------------------------------------------------------
10
10
  // Shared sidebar chrome — the pieces WorkspaceSidebar and SettingsSidebar
11
11
  // have in common: the nav container, the top row (collapse toggle + org
12
- // switcher), the hairline separator, the footer band, and the one nav-row
13
- // class recipe. Internal to the sidebar domain; not exported from the SDK.
12
+ // switcher), the hairline separator, the footer band, and the shared class
13
+ // recipes (nav rows, section labels). Internal to the sidebar domain; not
14
+ // exported from the SDK.
14
15
  //
15
16
  // Every class here is transcribed from the console's sidebars — this file
16
17
  // IS the console's chrome now, so a change here changes the console, the
@@ -107,23 +108,36 @@ export function SidebarSeparator() {
107
108
  }
108
109
 
109
110
  /**
110
- * The one nav-row class recipe both sidebars share: 14px medium labels,
111
- * 16px icons (sized by callers), 8px/6px padding, rounded row highlight.
111
+ * The one nav-row class recipe both sidebars share: 13px/16px regular
112
+ * labels, 16px icons (sized by callers), 8px/4px padding 24px rows.
113
+ * The explicit `leading-4` is load-bearing: without it the inherited
114
+ * line-height (~1.5) pads every row to ~28px, and across a full column
115
+ * of rows that difference alone decides whether the sidebar breathes.
116
+ * 24px also stays exactly at the WCAG 2.5.8 minimum pointer-target size.
112
117
  *
113
- * `muted` renders the resting label in the muted sidebar tone used by
114
- * "Back to Sessions", which is an exit, not a destination.
118
+ * Resting rows are muted so the navigation recedes and the content leads;
119
+ * the active row carries the accent fill, hover brightens to the accent
120
+ * foreground. Font weight is constant across states — state is expressed
121
+ * through color and fill only, so activating a row never shifts its text
122
+ * width. The muted-on-sidebar pairing is held to WCAG AA by the theme's
123
+ * contrast contract (sdk/theme/src/contract/pairs.ts) in every preset.
115
124
  */
116
- export function navRowClassName(
117
- active: boolean,
118
- { muted = false }: { muted?: boolean } = {},
119
- ): string {
125
+ export function navRowClassName(active: boolean): string {
120
126
  return cn(
121
- "stg:flex stg:items-center stg:gap-2 stg:rounded-lg stg:px-2 stg:py-1.5 stg:text-sm stg:font-medium stg:transition-colors",
127
+ "stg:flex stg:items-center stg:gap-2 stg:rounded-md stg:px-2 stg:py-1 stg:text-[13px] stg:leading-4 stg:transition-colors",
122
128
  active
123
129
  ? "stg:bg-sidebar-accent stg:text-sidebar-accent-foreground"
124
- : cn(
125
- muted ? "stg:text-sidebar-muted-foreground" : "stg:text-sidebar-foreground",
126
- "stg:hover:bg-sidebar-accent stg:hover:text-sidebar-accent-foreground",
127
- ),
130
+ : "stg:text-sidebar-muted-foreground stg:hover:bg-sidebar-accent stg:hover:text-sidebar-accent-foreground",
128
131
  );
129
132
  }
133
+
134
+ /**
135
+ * The section-label recipe both sidebars share (settings group labels,
136
+ * the Recents heading): 11px/16px medium uppercase in the muted sidebar
137
+ * tone. Spacing around a label stays with the caller — it is a layout
138
+ * concern; this recipe owns only the typography, so the two sidebars
139
+ * cannot drift.
140
+ */
141
+ export function sectionLabelClassName(): string {
142
+ return "stg:text-sidebar-muted-foreground stg:px-2 stg:text-[11px] stg:leading-4 stg:font-medium stg:tracking-wide stg:uppercase";
143
+ }
@@ -383,19 +383,21 @@ describe("WorkflowExecutionViewer (center-column Thread|Graph toggle, S9)", () =
383
383
  it("a thread card offers no selection or drill-down gesture — the card IS the surface (T06)", () => {
384
384
  renderViewer();
385
385
 
386
- // build-report has kind 0 (unspecified summary disclosure): its header
387
- // is the expand gesture, never a selection.
388
- const header = screen.getByRole("button", { name: /^build-report/ });
389
- expect(header.getAttribute("aria-expanded")).toBe("false");
390
- expect(header.getAttribute("aria-pressed")).toBeNull();
386
+ // build-report renders from the snapshot fallback (no events): kind 0
387
+ // (unspecified summary disclosure) with nothing beyond what its
388
+ // header row already carries so it is a PLAIN row (stigmer#886): no
389
+ // expand chevron, no selection, no Inspect drill-down. (Expansion
390
+ // being card-local for content-bearing summary cards is pinned in
391
+ // WorkflowTaskThread.test.tsx.)
392
+ expect(
393
+ screen.queryByRole("button", { name: /^build-report/ }),
394
+ ).toBeNull();
391
395
  expect(
392
396
  screen.queryByRole("button", { name: "Inspect build-report" }),
393
397
  ).toBeNull();
394
398
 
395
- fireEvent.click(header);
396
-
397
- // Expansion is card-local; the panel never opens on a card gesture.
398
- expect(header.getAttribute("aria-expanded")).toBe("true");
399
+ // Clicking the row is inert: the panel never opens on a card gesture.
400
+ fireEvent.click(screen.getByText("build-report"));
399
401
  expect(screen.queryByRole("radio", { name: "Inspect" })).toBeNull();
400
402
  });
401
403
 
@@ -451,6 +451,48 @@ describe("WorkflowTaskThread", () => {
451
451
  expect(screen.getByText(/at dial tcp 10\.0\.0\.1:443/)).toBeTruthy();
452
452
  });
453
453
 
454
+ it("a summary-kind card with a body-less detail renders no expand gesture (stigmer#886)", () => {
455
+ // A settled wait card with nothing the header cannot carry — no retry,
456
+ // no usage, no error, no I/O. Since R6-6 moved Status/Duration onto
457
+ // the header, its detail body would be empty, so the header is a plain
458
+ // row: offering a chevron that reveals nothing is worse than none.
459
+ render(
460
+ <WorkflowTaskThread
461
+ taskStates={statesOf(
462
+ taskState({ taskName: "blocking_wait", taskKind: WorkflowTaskKind.wait }),
463
+ )}
464
+ totalTasks={1}
465
+ isRunning={false}
466
+ />,
467
+ );
468
+
469
+ expect(screen.getByText("blocking_wait")).toBeTruthy();
470
+ expect(screen.queryByRole("button", { name: /^blocking_wait/ })).toBeNull();
471
+ });
472
+
473
+ it("the expand gesture returns the moment the detail body has content (stigmer#886)", () => {
474
+ // The same wait card with a retry behind it: the Attempt row gives the
475
+ // body content, so the header is the expand gesture again.
476
+ render(
477
+ <WorkflowTaskThread
478
+ taskStates={statesOf(
479
+ taskState({
480
+ taskName: "blocking_wait",
481
+ taskKind: WorkflowTaskKind.wait,
482
+ attemptNumber: 2,
483
+ }),
484
+ )}
485
+ totalTasks={1}
486
+ isRunning={false}
487
+ />,
488
+ );
489
+
490
+ const header = screen.getByRole("button", { name: /^blocking_wait/ });
491
+ fireEvent.click(header);
492
+ expect(screen.getByText("Attempt")).toBeTruthy();
493
+ expect(screen.getByText("2")).toBeTruthy();
494
+ });
495
+
454
496
  it("shows the full error in a summary-kind card's expanded body", () => {
455
497
  render(
456
498
  <WorkflowTaskThread
@@ -471,9 +513,15 @@ describe("WorkflowTaskThread", () => {
471
513
  });
472
514
 
473
515
  it("keeps a card's expanded state across streaming updates (no remount)", () => {
516
+ // The wait card carries input so its detail body has content — a
517
+ // body-less summary card has no expand gesture at all (stigmer#886).
474
518
  const running = () =>
475
519
  statesOf(
476
- taskState({ taskName: "settled", taskKind: WorkflowTaskKind.wait }),
520
+ taskState({
521
+ taskName: "settled",
522
+ taskKind: WorkflowTaskKind.wait,
523
+ inputSummary: { duration: "10s" },
524
+ }),
477
525
  taskState({ taskName: "live", status: "running", messagesCount: 1 }),
478
526
  );
479
527
  const { rerender } = render(
@@ -495,7 +543,11 @@ describe("WorkflowTaskThread", () => {
495
543
  rerender(
496
544
  <WorkflowTaskThread
497
545
  taskStates={statesOf(
498
- taskState({ taskName: "settled", taskKind: WorkflowTaskKind.wait }),
546
+ taskState({
547
+ taskName: "settled",
548
+ taskKind: WorkflowTaskKind.wait,
549
+ inputSummary: { duration: "10s" },
550
+ }),
499
551
  taskState({ taskName: "live", status: "running", messagesCount: 2 }),
500
552
  )}
501
553
  totalTasks={2}
@@ -288,7 +288,12 @@ function ThreadEmptyState({ isRunning }: { readonly isRunning: boolean }) {
288
288
  * card chevron — `BoundedContent` owns its own in-place reveal, so the
289
289
  * old "expand, then Show more" double control never comes back).
290
290
  * - `"summary"` kinds expand from the header — the session card's own
291
- * gesture, the chevron appended by the shell (T06).
291
+ * gesture, the chevron appended by the shell (T06) — but ONLY when the
292
+ * detail body would carry content (stigmer#886): since R6-6 moved
293
+ * Status/Duration onto the header, a settled wait card often has
294
+ * nothing left to reveal, and a chevron that opens an empty body is
295
+ * worse than no chevron. The gate is the summary twin of
296
+ * `showPreviewBody` below.
292
297
  * - AGENT_CALL cards with a spawned child render the child's inline
293
298
  * transcript as the body (T07) — keyed on the VARIANT, not the
294
299
  * disclosure, so a platform builder's presenter override can never route
@@ -362,6 +367,17 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
362
367
  !showTranscript &&
363
368
  isPreview &&
364
369
  (outputIO !== null || !!item.error || showApprovalSummary);
370
+ // The summary twin of that gate (stigmer#886): the expand gesture exists
371
+ // only when `ThreadTaskDetail` would render something. Short-circuits
372
+ // before the row build for preview/transcript cards, which never render
373
+ // the detail body.
374
+ const summaryHasDetail =
375
+ !isPreview &&
376
+ !showTranscript &&
377
+ (taskDetailRows(item).length > 0 ||
378
+ !!item.error ||
379
+ inputIO !== null ||
380
+ outputIO !== null);
365
381
 
366
382
  const meta = formatMetaChips({
367
383
  durationMs: item.durationMs,
@@ -378,19 +394,19 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
378
394
  accent={item.status === "waiting_approval" ? "warning" : null}
379
395
  cursorTarget="workflow-task-row"
380
396
  >
381
- {/* The session card's gestures exactly (T06): summary rows expand
382
- from the header (chevron appended by the shell); preview and
383
- transcript rows' bodies are always visible, so the header is a
384
- plain layout row. */}
397
+ {/* The session card's gestures exactly (T06): summary rows with a
398
+ content-bearing detail expand from the header (chevron appended
399
+ by the shell); preview, transcript, and body-less summary rows
400
+ are plain layout rows. */}
385
401
  <ThreadCardHeader
386
402
  gesture={
387
- isPreview || showTranscript
388
- ? { kind: "none" }
389
- : {
403
+ summaryHasDetail
404
+ ? {
390
405
  kind: "expand",
391
406
  expanded,
392
407
  onToggle: () => setExpanded((v) => !v),
393
408
  }
409
+ : { kind: "none" }
394
410
  }
395
411
  >
396
412
  <StatusGlyph status={item.status} />
@@ -486,7 +502,7 @@ const ThreadTaskCard = memo(function ThreadTaskCard({
486
502
  </ThreadCardBody>
487
503
  )}
488
504
 
489
- {!isPreview && !showTranscript && expanded && (
505
+ {summaryHasDetail && expanded && (
490
506
  <ThreadCardBody>
491
507
  <ThreadTaskDetail item={item} inputIO={inputIO} outputIO={outputIO} />
492
508
  </ThreadCardBody>
@@ -630,19 +646,15 @@ function ThreadTaskPreviewBody({
630
646
  );
631
647
  }
632
648
 
633
- function ThreadTaskDetail({
634
- item,
635
- inputIO,
636
- outputIO,
637
- }: {
638
- readonly item: WorkflowThreadItem;
639
- readonly inputIO: TaskDetailIO | null;
640
- readonly outputIO: TaskDetailIO | null;
641
- }) {
642
- // No Status/Duration rows (R6-6): the card header is the single source
643
- // for both — the status glyph and the duration meta chip. The detail
644
- // body carries only what the header cannot: attempt count, usage, the
645
- // agent slug.
649
+ /**
650
+ * The detail body's label/value rows. No Status/Duration rows (R6-6): the
651
+ * card header is the single source for both — the status glyph and the
652
+ * duration meta chip. The detail carries only what the header cannot:
653
+ * attempt count, usage, the agent slug. Shared with the card's
654
+ * `summaryHasDetail` gate so "would the body render anything?" can never
655
+ * drift from what the body actually renders (stigmer#886).
656
+ */
657
+ function taskDetailRows(item: WorkflowThreadItem): Array<[string, string]> {
646
658
  const rows: Array<[string, string]> = [];
647
659
  if (item.attemptNumber > 1) rows.push(["Attempt", String(item.attemptNumber)]);
648
660
  const costChip = formatMetaChips({
@@ -653,6 +665,19 @@ function ThreadTaskDetail({
653
665
  if (item.variant === "agent-call" && item.agentSlug) {
654
666
  rows.push(["Agent", item.agentSlug]);
655
667
  }
668
+ return rows;
669
+ }
670
+
671
+ function ThreadTaskDetail({
672
+ item,
673
+ inputIO,
674
+ outputIO,
675
+ }: {
676
+ readonly item: WorkflowThreadItem;
677
+ readonly inputIO: TaskDetailIO | null;
678
+ readonly outputIO: TaskDetailIO | null;
679
+ }) {
680
+ const rows = taskDetailRows(item);
656
681
 
657
682
  return (
658
683
  <div className="stg:flex stg:flex-col stg:gap-2">