@ciridae/ui 1.0.3 → 1.0.5

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 (124) hide show
  1. package/dist/chat/conversation-source.d.ts +15 -1
  2. package/dist/chat/describe-tool-call.d.ts +3 -3
  3. package/dist/chat/describe-tool-call.js +3 -3
  4. package/dist/chat/message-text.js +2 -2
  5. package/dist/ciridae.css +16 -4
  6. package/dist/components/activity-feed.d.ts +1 -0
  7. package/dist/components/activity-feed.js +1 -0
  8. package/dist/components/app-frame.d.ts +13 -1
  9. package/dist/components/app-frame.js +45 -3
  10. package/dist/components/app-navigation.d.ts +28 -1
  11. package/dist/components/app-navigation.js +110 -14
  12. package/dist/components/badge.d.ts +29 -4
  13. package/dist/components/badge.js +134 -15
  14. package/dist/components/bulk-action-bar.js +3 -3
  15. package/dist/components/button.d.ts +10 -3
  16. package/dist/components/button.js +77 -44
  17. package/dist/components/calendar.js +44 -17
  18. package/dist/components/centered-page.js +2 -2
  19. package/dist/components/chart.d.ts +1 -1
  20. package/dist/components/chart.js +16 -11
  21. package/dist/components/chat-client-source.js +47 -6
  22. package/dist/components/chat-sheet-controller.d.ts +1 -0
  23. package/dist/components/chat-sheet-controller.js +1 -0
  24. package/dist/components/chat-sheet.d.ts +10 -2
  25. package/dist/components/chat-sheet.js +74 -37
  26. package/dist/components/chat.d.ts +90 -14
  27. package/dist/components/chat.js +401 -101
  28. package/dist/components/checkbox-item.d.ts +5 -2
  29. package/dist/components/checkbox-item.js +9 -5
  30. package/dist/components/chip.d.ts +5 -0
  31. package/dist/components/chip.js +27 -1
  32. package/dist/components/column-settings.js +1 -1
  33. package/dist/components/command-menu.d.ts +5 -1
  34. package/dist/components/command-menu.js +98 -32
  35. package/dist/components/command-palette.d.ts +6 -2
  36. package/dist/components/command-palette.js +20 -10
  37. package/dist/components/data-display.d.ts +35 -4
  38. package/dist/components/data-display.js +138 -19
  39. package/dist/components/data-grid.d.ts +14 -2
  40. package/dist/components/data-grid.js +61 -26
  41. package/dist/components/date-range-picker.js +9 -20
  42. package/dist/components/drawer.d.ts +5 -0
  43. package/dist/components/drawer.js +91 -54
  44. package/dist/components/editable-field.d.ts +17 -3
  45. package/dist/components/editable-field.js +57 -27
  46. package/dist/components/email-input.js +6 -5
  47. package/dist/components/environment-frame.d.ts +1 -1
  48. package/dist/components/environment-frame.js +37 -6
  49. package/dist/components/filter-bar.js +39 -28
  50. package/dist/components/form-controls.d.ts +9 -0
  51. package/dist/components/form-controls.js +46 -22
  52. package/dist/components/form-layout.d.ts +7 -3
  53. package/dist/components/form-layout.js +68 -23
  54. package/dist/components/glass-menu.d.ts +3 -0
  55. package/dist/components/glass-menu.js +63 -0
  56. package/dist/components/gradient-edge.d.ts +9 -0
  57. package/dist/components/gradient-edge.js +31 -0
  58. package/dist/components/input.d.ts +4 -2
  59. package/dist/components/input.js +26 -15
  60. package/dist/components/keycap.d.ts +9 -5
  61. package/dist/components/keycap.js +57 -19
  62. package/dist/components/lightbox.js +2 -1
  63. package/dist/components/locale.d.ts +10 -0
  64. package/dist/components/locale.js +18 -2
  65. package/dist/components/log-entry.d.ts +40 -0
  66. package/dist/components/log-entry.js +146 -0
  67. package/dist/components/mention-input.js +4 -3
  68. package/dist/components/menu.d.ts +6 -2
  69. package/dist/components/menu.js +34 -37
  70. package/dist/components/meter.d.ts +9 -1
  71. package/dist/components/meter.js +64 -6
  72. package/dist/components/navigation.d.ts +21 -4
  73. package/dist/components/navigation.js +84 -19
  74. package/dist/components/notification-tray.js +5 -4
  75. package/dist/components/overlays.d.ts +3 -1
  76. package/dist/components/overlays.js +69 -40
  77. package/dist/components/page-frame.js +1 -1
  78. package/dist/components/panel.d.ts +1 -1
  79. package/dist/components/panel.js +18 -12
  80. package/dist/components/radio-card.js +10 -9
  81. package/dist/components/record.d.ts +25 -1
  82. package/dist/components/record.js +147 -18
  83. package/dist/components/segmented-bar.d.ts +3 -1
  84. package/dist/components/segmented-bar.js +16 -6
  85. package/dist/components/select.d.ts +5 -2
  86. package/dist/components/select.js +103 -127
  87. package/dist/components/shell-skeleton.js +1 -1
  88. package/dist/components/shell.d.ts +2 -2
  89. package/dist/components/shell.js +43 -45
  90. package/dist/components/sidebar.d.ts +7 -2
  91. package/dist/components/sidebar.js +55 -27
  92. package/dist/components/single-line-text.d.ts +2 -0
  93. package/dist/components/single-line-text.js +16 -0
  94. package/dist/components/site-banner.d.ts +5 -2
  95. package/dist/components/site-banner.js +17 -6
  96. package/dist/components/skeleton.d.ts +7 -6
  97. package/dist/components/skeleton.js +28 -16
  98. package/dist/components/skeletons.d.ts +12 -0
  99. package/dist/components/skeletons.js +71 -8
  100. package/dist/components/stages.d.ts +78 -6
  101. package/dist/components/stages.js +512 -156
  102. package/dist/components/stat-card.d.ts +10 -2
  103. package/dist/components/stat-card.js +92 -37
  104. package/dist/components/status-banner.js +1 -1
  105. package/dist/components/tabs.js +26 -13
  106. package/dist/components/theme-toggle.d.ts +4 -0
  107. package/dist/components/theme-toggle.js +67 -0
  108. package/dist/components/toast.d.ts +9 -2
  109. package/dist/components/toast.js +78 -29
  110. package/dist/preset.js +291 -33
  111. package/dist/styled-system/styles.css +3107 -1039
  112. package/dist/styled-system/tokens/index.mjs +602 -22
  113. package/dist/styled-system/tokens/tokens.d.ts +11 -11
  114. package/dist/styled-system/types/prop-type.d.ts +2 -2
  115. package/dist/styles.css +16 -4
  116. package/dist/theme/color.d.ts +3 -0
  117. package/dist/theme/color.js +5 -0
  118. package/dist/theme/derive.d.ts +9 -2
  119. package/dist/theme/derive.js +72 -12
  120. package/dist/theme/palettes.d.ts +49 -6
  121. package/dist/theme/palettes.js +84 -15
  122. package/dist/theme/tokens.d.ts +3 -3
  123. package/dist/theme/tokens.js +108 -32
  124. package/package.json +9 -1
@@ -1,159 +1,215 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /*
3
3
  * A record's path through its stages, and the work at the stage it is at. `StepDot` marks a
4
- * step's status wherever one is listed; `StageStrip` is the path as one row of arrowed segments,
5
- * each a button that chooses its stage; `CurrentStep` is the attention row for the step the
6
- * reader acts on; `StageChecklist` lists what stages need; `ConfirmationRow` says a move landed.
4
+ * step's status wherever one is listed; `StageStrip` is the path as one row of stages joined by
5
+ * connectors, each a button that chooses its stage, and `PartsRollup` stands in for it on a record
6
+ * split into parts; `CurrentStep` is the attention row for the step the reader acts on, and
7
+ * `StepChain` the steps that follow from it, as `StepChip`s, and `HelpMenu` its ways forward when
8
+ * stuck; `Requirement` is one thing a stage needs and `StageChecklist` lists them per stage;
9
+ * `ParallelChip` is someone else's work under way; `ConfirmationRow` says a move landed.
7
10
  */
8
- import { CaretDownIcon, CaretUpIcon, CheckIcon, LockIcon, MinusIcon, SparkleIcon, WarningCircleIcon, } from "@phosphor-icons/react";
11
+ import { Menu as BaseMenu } from "@base-ui/react/menu";
12
+ import { CaretDownIcon, CaretUpIcon, CheckIcon, SparkleIcon } from "@phosphor-icons/react";
9
13
  import { css, cva, cx } from "#primitives/css";
10
14
  import { Button } from "./button.js";
11
15
  import { useControllableState } from "./controllable-state.js";
12
16
  import { DateTime } from "./date-time.js";
17
+ import { HotkeyLayer } from "./hotkeys.js";
13
18
  import { icon, Spinner } from "./icon.js";
14
19
  import { Cluster, MutedText } from "./layout.js";
15
20
  import { useCiridaeLocale } from "./locale.js";
16
21
  import { Menu } from "./menu.js";
17
22
  import { Popover } from "./overlays.js";
18
- /* Step dot: 16 on the strip, 14 on rows, 12 inside chips. */
23
+ /*
24
+ * Step dot: 16 on the strip, 14 on rows, 12 inside chips. A ring in its status's hue, solid or
25
+ * dashed, with a mark inside: a check on the green glass once done, a dot for the reader's step
26
+ * and the agent's, a bar for a blocked one. Waiting, ahead and locked are the ring alone.
27
+ */
19
28
  const stepDot = cva({
20
29
  base: {
21
30
  display: "inline-grid",
22
31
  placeItems: "center",
23
32
  flexShrink: "0",
24
33
  borderRadius: "round",
25
- border: "1px solid currentColor",
26
- color: "fg.muted",
34
+ borderStyle: "solid",
35
+ borderColor: "currentColor",
36
+ color: "fg.secondary",
27
37
  lineHeight: "tight",
38
+ "&[data-status=waiting], &[data-status=agent]": { borderStyle: "dashed" },
28
39
  },
29
40
  variants: {
30
41
  status: {
31
- done: { background: "accent.green", borderColor: "accent.green", color: "fg.onAccent" },
32
- current: { color: "accent.yellow" },
33
- waiting: { color: "fg.muted" },
42
+ // The green glass, its hairline drawn by the `[data-edge]` ring in place of the border.
43
+ done: {
44
+ borderWidth: "0!",
45
+ background: "token(gradients.stepDoneGlass)",
46
+ color: "fg.primary",
47
+ "--edge": "token(gradients.border.stepDoneGlass)",
48
+ _light: { color: "accent.green" },
49
+ },
50
+ current: { color: "accent.yellowIcon" },
51
+ waiting: { color: "fg.label" },
34
52
  agent: { color: "accent.blue" },
35
53
  blocked: { color: "accent.red" },
36
- locked: { color: "fg.muted", opacity: "disabled" },
37
- todo: { color: "border.panel" },
54
+ // The kit's Gray 50 ring in dark and Light Gray 66 in light; a locked one fades to half.
55
+ locked: { color: "border.control", opacity: "locked.step" },
56
+ todo: { color: "border.control" },
38
57
  },
39
58
  size: {
40
- chip: { width: "avatar.micro", height: "avatar.micro", fontSize: "icon.tiny" },
41
- small: { width: "control.knob", height: "control.knob", fontSize: "icon.tiny" },
42
- medium: { width: "control.checkbox", height: "control.checkbox", fontSize: "icon.check" },
59
+ // The kit's strokes per size: a 12px ring is 1.25 and an empty one 1.5; a 14px ring is 1,
60
+ // and 1.3125 for the dashed and blocked ones; a 16px ring is 1.5 throughout.
61
+ chip: {
62
+ "--step-dot-size": "token(sizes.avatar.micro)",
63
+ width: "avatar.micro",
64
+ height: "avatar.micro",
65
+ borderWidth: "1.25px",
66
+ fontSize: "icon.tiny",
67
+ "&:is([data-status=todo], [data-status=locked])": { borderWidth: "1.5px" },
68
+ },
69
+ small: {
70
+ "--step-dot-size": "token(sizes.control.knob)",
71
+ width: "control.knob",
72
+ height: "control.knob",
73
+ borderWidth: "1px",
74
+ fontSize: "icon.tiny",
75
+ "&:is([data-status=waiting], [data-status=agent], [data-status=blocked])": {
76
+ borderWidth: "1.3125px",
77
+ },
78
+ },
79
+ medium: {
80
+ "--step-dot-size": "token(sizes.control.checkbox)",
81
+ width: "control.checkbox",
82
+ height: "control.checkbox",
83
+ borderWidth: "1.5px",
84
+ fontSize: "icon.check",
85
+ },
43
86
  },
44
87
  },
45
88
  defaultVariants: { status: "todo", size: "medium" },
46
89
  });
47
- const stepDotCore = css({
48
- width: "indicator.radio",
49
- height: "indicator.radio",
90
+ // The marks inside, as shares of the dot so every size draws them alike: the kit's 7px centre in
91
+ // a 16px dot, measured across the whole dot rather than inside its ring.
92
+ const stepDotCenter = css({
93
+ width: "calc(var(--step-dot-size) * 0.4375)",
94
+ height: "calc(var(--step-dot-size) * 0.4375)",
95
+ borderRadius: "round",
96
+ background: "currentColor",
97
+ "[data-status=agent] > &": { width: "25%", height: "25%" },
98
+ });
99
+ const stepDotBar = css({
100
+ width: "44%",
101
+ height: "10%",
102
+ minHeight: "1px",
50
103
  borderRadius: "round",
51
104
  background: "currentColor",
52
105
  });
53
106
  /** A step's status as a dot; decorative, so the text beside it says the status. */
54
107
  export function StepDot({ status, size = "medium", className, children, ...props }) {
55
- return (_jsx("span", { "aria-hidden": "true", ...props, className: cx(stepDot({ status, size }), className), "data-ciridae-ui": "", children: children ?? (_jsxs(_Fragment, { children: [status === "done" && _jsx(CheckIcon, { weight: "bold" }), status === "current" && _jsx("span", { className: stepDotCore }), status === "waiting" && _jsx(MinusIcon, { weight: "bold" }), status === "agent" && _jsx(SparkleIcon, { weight: "fill" }), status === "blocked" && _jsx(WarningCircleIcon, { weight: "bold" }), status === "locked" && _jsx(LockIcon, { weight: "bold" })] })) }));
108
+ return (_jsx("span", { "aria-hidden": "true", ...props, className: cx(stepDot({ status, size }), className), "data-ciridae-ui": "", "data-edge": status === "done" ? "gradient" : undefined, "data-status": status, children: children ?? (_jsxs(_Fragment, { children: [status === "done" && _jsx(CheckIcon, { weight: "bold" }), (status === "current" || status === "agent") && _jsx("span", { className: stepDotCenter }), status === "blocked" && _jsx("span", { className: stepDotBar })] })) }));
56
109
  }
57
110
  /*
58
- * Stage strip: one enclosed row of arrowed segments, each filled with its state, every one a
59
- * button that chooses its stage. Segments share the width; as the box narrows, dates go first,
60
- * then every stage but the current and the chosen one folds to its dot. Past `maxVisible` the
61
- * strip windows itself around the current stage: it, its neighbours and the chosen stage stay, and
62
- * the runs between them fold into one counted segment each, which lists its stages when pressed.
63
- * The window never moves with the choice, so a segment stays where it was clicked.
111
+ * Stage strip: the stages in one row, each its dot, name and meta, joined by connectors that
112
+ * stretch to fill the width, green out of a finished stage and grey ahead. The reader's stage is
113
+ * a warm pill, a blocked one a grey pill with its meta in red. Every stage is a button that
114
+ * chooses it. As the box narrows, metas go first, then every stage but the current and the chosen
115
+ * one folds to its dot. Past `maxVisible` the finished stages fold into one "N done" stage and the
116
+ * tail into a "+N" count, each listing its stages when pressed; the fold follows the current
117
+ * stage, never the choice, so a stage stays where it was clicked.
64
118
  */
65
119
  const strip = css({
66
- "--stage-arrow": "10px",
67
120
  display: "flex",
68
- alignItems: "stretch",
69
- gap: "panel.hairline",
121
+ alignItems: "center",
122
+ gap: "stage.connectorGap",
70
123
  minWidth: "0",
71
- height: "control.compact",
72
124
  margin: "none",
73
125
  padding: "none",
74
126
  listStyle: "none",
75
127
  containerType: "inline-size",
76
128
  "@container (max-width: 720px)": { "& [data-stage-meta]": { display: "none" } },
77
129
  "@container (max-width: 520px)": {
78
- "& > li > :not([data-current], [aria-pressed=true])": {
79
- flex: "0 0 auto",
80
- "& [data-stage-label]": { display: "none" },
81
- },
82
- "& > li > [data-folded]": {
83
- paddingInline: "calc(var(--stage-arrow) + token(spacing.layout.tightGap))",
84
- },
85
- },
86
- "&[data-many]": {
87
- // A folded run is a summary, so it takes only what its count needs.
88
- "& > li > [data-run]": { flex: "0 0 auto" },
89
- "@container (max-width: 1100px)": { "& [data-stage-meta]": { display: "none" } },
90
- "@container (max-width: 480px)": {
91
- "& > li > :not([data-current], [aria-pressed=true])": {
92
- flex: "0 0 auto",
93
- "& [data-stage-label]": { display: "none" },
94
- },
130
+ "& > li > :not([data-current], [aria-pressed=true], [data-selected]) [data-stage-label]": {
131
+ display: "none",
95
132
  },
96
133
  },
97
134
  });
98
135
  const stripItem = css({ display: "contents" });
136
+ const connector = css({
137
+ flex: "1 1 0",
138
+ minWidth: "token(spacing.layout.controlGap)",
139
+ height: "border.hairline",
140
+ borderRadius: "round",
141
+ background: "border.divider",
142
+ "&[data-after=done]": { background: "accent.green" },
143
+ });
99
144
  const stage = css({
100
145
  display: "flex",
101
146
  alignItems: "center",
102
- justifyContent: "center",
103
147
  gap: "control.iconGap",
104
- flex: "1 1 0",
148
+ flex: "0 1 auto",
105
149
  minWidth: "0",
106
- // Each segment tucks under the previous one's point, so the gap between reads as one chevron.
107
- marginInlineStart: "calc(var(--stage-arrow) * -1)",
108
- paddingInline: "calc(var(--stage-arrow) + token(spacing.control.block))",
109
- clipPath: "polygon(0 0, calc(100% - var(--stage-arrow)) 0, 100% 50%, calc(100% - var(--stage-arrow)) 100%, 0 100%, var(--stage-arrow) 50%)",
110
- // The grey fill, not glass: glass is near-white in light and the stages ahead vanished.
111
- background: "bg.fill",
112
- color: "fg.secondary",
150
+ paddingBlock: "panel.hairline",
151
+ paddingInline: "0",
152
+ border: "none",
153
+ borderRadius: "round",
154
+ background: "transparent",
155
+ color: "fg.primary",
156
+ font: "inherit",
113
157
  fontSize: "caption",
114
158
  fontWeight: "medium",
115
159
  letterSpacing: "compact",
160
+ lineHeight: "tight",
116
161
  whiteSpace: "nowrap",
117
- // Each segment is its item's only child, so the ends are found from the list item.
118
- "li:first-child > &": {
119
- marginInlineStart: "0",
120
- paddingInlineStart: "control.compactInline",
121
- borderStartStartRadius: "action",
122
- borderEndStartRadius: "action",
123
- clipPath: "polygon(0 0, calc(100% - var(--stage-arrow)) 0, 100% 50%, calc(100% - var(--stage-arrow)) 100%, 0 100%)",
162
+ cursor: "pointer",
163
+ "&[data-state=todo], &[data-state=locked]": { color: "fg.secondary" },
164
+ // The reader's stage and a blocked one are pills: the current stage's warm glass, and the
165
+ // selected glass in light, the red glass in dark. Their edge takes a pixel a side, as the kit's.
166
+ "&:is([data-state=current], [data-state=blocked])": {
167
+ paddingInlineStart: "control.block",
168
+ paddingInlineEnd: "control.iconInline",
169
+ border: "1px solid transparent",
124
170
  },
125
- "li:last-child > &": {
126
- paddingInlineEnd: "control.compactInline",
127
- borderStartEndRadius: "action",
128
- borderEndEndRadius: "action",
129
- clipPath: "polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--stage-arrow) 50%)",
171
+ "&[data-state=current]": {
172
+ background: "token(gradients.currentStageGlass)",
173
+ color: "accent.yellow",
174
+ "--edge": "token(gradients.border.currentStageGlass)",
130
175
  },
131
- "&[data-state=done]": { background: "bg.successSubtle", color: "accent.green" },
132
- "&[data-state=current]": { background: "bg.warningSubtle", color: "accent.yellow" },
133
- "&[data-state=blocked]": { background: "bg.errorSubtle", color: "accent.red" },
134
- "&[data-state=agent]": { background: "bg.infoSubtle", color: "accent.blue" },
135
- "&[data-state=todo], &[data-state=locked]": {
136
- color: "fg.muted",
137
- // The dot's own hairline grey vanishes on the segment's fill.
138
- "& > :first-child": { color: "fg.muted" },
176
+ "&[data-state=blocked]": {
177
+ background: "token(gradients.dangerGlass)",
178
+ _light: { background: "token(gradients.selected)" },
179
+ "--edge": "token(gradients.border.raised)",
180
+ "& [data-stage-meta]": { color: "accent.red" },
139
181
  },
140
- // Every stage can be chosen: it takes the hover wash, and the chosen one keeps it.
182
+ // A stage under the pointer, focused or chosen takes a faint pill, reaching past a plain one.
183
+ "&:is(:hover, :focus-visible, [aria-pressed=true], [data-selected]):not([data-current])": {
184
+ background: "bg.rowHover",
185
+ boxShadow: "0 0 0 token(spacing.panel.hairline) token(colors.bg.rowHover)",
186
+ },
187
+ _focusVisible: { focusRing: "outer" },
188
+ });
189
+ const stageLabel = css({ overflow: "hidden", textOverflow: "ellipsis" });
190
+ const stageMeta = css({
191
+ flexShrink: "0",
192
+ color: "fg.label",
193
+ fontSize: "code",
194
+ "[data-state=current] > &": { color: "inherit" },
195
+ });
196
+ // The tail past the fold: a quiet count that lists the stages it stands for.
197
+ const moreStages = css({
198
+ flexShrink: "0",
199
+ padding: "none",
141
200
  border: "none",
201
+ borderRadius: "control",
202
+ background: "transparent",
203
+ color: "fg.subtle",
142
204
  font: "inherit",
205
+ fontSize: "code",
206
+ fontWeight: "medium",
207
+ lineHeight: "tight",
208
+ whiteSpace: "nowrap",
143
209
  cursor: "pointer",
144
- _hover: { boxShadow: "inset 0 0 0 999px token(colors.bg.rowHover)" },
145
- _focusVisible: { outline: "none", boxShadow: "inset 0 0 0 999px token(colors.bg.rowHover)" },
146
- "&[aria-pressed=true]": { boxShadow: "inset 0 0 0 999px token(colors.bg.rowHover)" },
147
- // A chosen stage ahead has no fill of its own, so its text lifts to show the choice.
148
- "&[aria-pressed=true]:is([data-state=todo], [data-state=locked])": {
149
- color: "fg.primary",
150
- "& > :first-child": { color: "fg.primary" },
151
- },
210
+ _hover: { color: "fg.primary" },
211
+ _focusVisible: { focusRing: "outer" },
152
212
  });
153
- const stageLabel = css({ overflow: "hidden", textOverflow: "ellipsis" });
154
- const stageMeta = css({ flexShrink: "0", opacity: "0.7", fontSize: "code" });
155
- /* A folded run's dot carries its count instead of an icon. */
156
- const countDot = css({ fontSize: "icon.tiny", fontWeight: "medium", lineHeight: "1" });
157
213
  /** The stage a record is at: the first neither done nor ahead, else the last done, else the first. */
158
214
  export function currentStageIndex(stages) {
159
215
  const active = stages.findIndex((item) => item.status !== "done" && item.status !== "todo" && item.status !== "locked");
@@ -162,33 +218,21 @@ export function currentStageIndex(stages) {
162
218
  const lastDone = stages.map((item) => item.status).lastIndexOf("done");
163
219
  return lastDone >= 0 ? lastDone : 0;
164
220
  }
165
- /** The strip's segments: every stage, or past `maxVisible` the window around the current one. */
166
- function stripItems(stages, maxVisible, currentIndex, selectedIndex) {
167
- if (stages.length <= maxVisible) {
168
- return stages.map((stage, index) => ({ kind: "stage", stage, index }));
169
- }
170
- const kept = new Set([currentIndex - 1, currentIndex, currentIndex + 1, selectedIndex]);
171
- const items = [];
172
- let run = [];
173
- const closeRun = () => {
174
- if (run.length === 1)
175
- items.push({ kind: "stage", stage: stages[run[0]], index: run[0] });
176
- if (run.length > 1) {
177
- const done = run.every((index) => stages[index].status === "done");
178
- items.push({ kind: "run", indices: run, status: done ? "done" : "todo" });
179
- }
180
- run = [];
181
- };
182
- stages.forEach((stage, index) => {
183
- if (kept.has(index)) {
184
- closeRun();
185
- items.push({ kind: "stage", stage, index });
186
- }
187
- else {
188
- run.push(index);
189
- }
190
- });
191
- closeRun();
221
+ /**
222
+ * The strip's items: every stage, or past `maxVisible` the stages before the current one folded
223
+ * into one, then as many as fit, then the rest as a count.
224
+ */
225
+ function stripItems(stages, maxVisible, currentIndex) {
226
+ const all = stages.map((stage, index) => ({ kind: "stage", stage, index }));
227
+ if (stages.length <= maxVisible)
228
+ return all;
229
+ const before = stages.slice(0, currentIndex).map((_, index) => index);
230
+ const items = before.length > 1 ? [{ kind: "done", indices: before }] : all.slice(0, currentIndex);
231
+ const room = Math.max(maxVisible - items.length - 1, 1);
232
+ items.push(...all.slice(currentIndex, currentIndex + room));
233
+ const rest = stages.slice(currentIndex + room).map((_, offset) => currentIndex + room + offset);
234
+ if (rest.length > 0)
235
+ items.push({ kind: "more", indices: rest });
192
236
  return items;
193
237
  }
194
238
  export function StageStrip({ stages, value, defaultValue, onValueChange, maxVisible = 7, className, ...props }) {
@@ -197,16 +241,32 @@ export function StageStrip({ stages, value, defaultValue, onValueChange, maxVisi
197
241
  const [chosen, setChosen] = useControllableState(value, defaultValue ?? stages[currentIndex]?.id ?? "", onValueChange);
198
242
  const chosenIndex = stages.findIndex((item) => item.id === chosen);
199
243
  const selectedIndex = chosenIndex >= 0 ? chosenIndex : currentIndex;
200
- const many = stages.length > maxVisible;
201
- return (_jsx("ol", { "aria-label": labels.record.stages, ...props, className: cx(strip, className), "data-ciridae-ui": "", "data-many": many || undefined, children: stripItems(stages, maxVisible, currentIndex, selectedIndex).map((item) => item.kind === "stage" ? (_jsx("li", { className: stripItem, children: _jsxs("button", { type: "button", className: stage, "data-state": item.stage.status, "data-current": item.index === currentIndex || undefined, "data-folded": item.index !== currentIndex || undefined, title: item.stage.label, "aria-current": item.index === currentIndex ? "step" : undefined, "aria-pressed": item.index === selectedIndex, onClick: () => setChosen(item.stage.id), children: [_jsx(StepDot, { status: item.stage.status, size: "small" }), _jsx("span", { className: stageLabel, "data-stage-label": "", children: item.stage.label }), item.stage.meta && (_jsx("span", { className: stageMeta, "data-stage-meta": "", children: item.stage.meta }))] }) }, item.stage.id)) : (_jsx("li", { className: stripItem, children: _jsx(Menu, { trigger: _jsxs("button", { type: "button", className: stage, "data-state": item.status, "data-folded": "", "data-run": "", "aria-label": item.status === "done"
202
- ? labels.record.runDoneLabel(item.indices.length)
203
- : labels.record.runToGoLabel(item.indices.length), children: [_jsx(StepDot, { status: item.status, size: "small", className: countDot, children: item.indices.length }), _jsx("span", { className: stageLabel, "data-stage-label": "", children: item.status === "done" ? labels.record.runDone : labels.record.runToGo })] }), items: item.indices.map((index) => ({
204
- label: stages[index].label,
205
- icon: _jsx(StepDot, { status: stages[index].status, size: "small" }),
206
- onSelect: () => setChosen(stages[index].id),
207
- })) }) }, `run-${item.indices[0]}`))) }));
244
+ const menuItems = (indices) => indices.map((index) => ({
245
+ label: stages[index].label,
246
+ icon: _jsx(StepDot, { status: stages[index].status, size: "small" }),
247
+ checked: index === selectedIndex,
248
+ onSelect: () => setChosen(stages[index].id),
249
+ }));
250
+ const items = stripItems(stages, maxVisible, currentIndex);
251
+ return (_jsx("ol", { "aria-label": labels.record.stages, ...props, className: cx(strip, className), "data-ciridae-ui": "", children: items.map((item, position) => {
252
+ const next = items[position + 1];
253
+ const join = next !== undefined && next.kind !== "more" && (_jsx("span", { className: connector, "aria-hidden": "true", "data-after": item.kind === "done" || (item.kind === "stage" && item.stage.status === "done")
254
+ ? "done"
255
+ : undefined }));
256
+ if (item.kind === "stage")
257
+ return (_jsxs("li", { className: stripItem, children: [_jsxs("button", { type: "button", className: stage, "data-ciridae-ui": "", "data-edge": item.stage.status === "current" || item.stage.status === "blocked"
258
+ ? "gradient"
259
+ : undefined, "data-state": item.stage.status, "data-current": item.index === currentIndex || undefined, title: item.stage.label, "aria-current": item.index === currentIndex ? "step" : undefined, "aria-pressed": item.index === selectedIndex, onClick: () => setChosen(item.stage.id), children: [_jsx(StepDot, { status: item.stage.status }), _jsx("span", { className: stageLabel, "data-stage-label": "", children: item.stage.label }), item.stage.meta && (_jsx("span", { className: stageMeta, "data-stage-meta": "", children: item.stage.meta }))] }), join] }, item.stage.id));
260
+ if (item.kind === "done")
261
+ return (_jsxs("li", { className: stripItem, children: [_jsx(Menu, { trigger: _jsxs("button", { type: "button", className: stage, "data-state": "done", "aria-label": labels.record.runDoneLabel(item.indices.length), "data-selected": item.indices.includes(selectedIndex) || undefined, children: [_jsx(StepDot, { status: "done" }), _jsx("span", { className: stageLabel, "data-stage-label": "", children: labels.record.runDoneShort(item.indices.length) })] }), items: menuItems(item.indices) }), join] }, "done"));
262
+ return (_jsx("li", { className: stripItem, children: _jsx(Menu, { trigger: _jsx("button", { type: "button", className: moreStages, "aria-label": labels.record.runToGoLabel(item.indices.length), "data-selected": item.indices.includes(selectedIndex) || undefined, children: labels.common.moreCount(item.indices.length) }), items: menuItems(item.indices) }) }, "more"));
263
+ }) }));
208
264
  }
209
- /* Current step: the attention row, tinted to its tone, with the inner card when expanded. */
265
+ /*
266
+ * Current step: the attention row, with the inner card when expanded. Light paints the reader's
267
+ * move, a ready step and a stuck one in their solid tint, dark in their hue's glass. The row sets
268
+ * `--step-edge`, the hairline it and its card draw, in the tint's edge; the grey glass's otherwise.
269
+ */
210
270
  const attentionRow = cva({
211
271
  base: {
212
272
  // The step card stacks by its own width, so a narrow record column stacks it as a phone does.
@@ -214,17 +274,73 @@ const attentionRow = cva({
214
274
  display: "grid",
215
275
  gap: "layout.tightGap",
216
276
  padding: "layout.tightGap",
217
- border: "1px solid token(colors.border.panel)",
277
+ border: "1px solid transparent",
218
278
  borderRadius: "action",
219
- background: "bg.glass",
279
+ "--step-edge": "token(gradients.border.raised)",
280
+ "--edge": "var(--step-edge)",
220
281
  },
221
282
  variants: {
222
283
  tone: {
223
- warning: { borderColor: "border.glass", background: "bg.tinted" },
224
- success: { borderColor: "border.glass", background: "bg.successSubtle" },
225
- info: { borderColor: "border.glass", background: "bg.infoSubtle" },
226
- neutral: {},
227
- danger: { borderColor: "border.glass", background: "bg.errorSubtle" },
284
+ warning: {
285
+ background: "token(gradients.tinted)",
286
+ "--step-edge": "token(gradients.border.warningTint)",
287
+ _light: { background: "bg.warningTint" },
288
+ },
289
+ success: {
290
+ background: "token(gradients.successGlass)",
291
+ "--step-edge": "token(gradients.border.successTint)",
292
+ _light: { background: "bg.successTint" },
293
+ },
294
+ info: {
295
+ background: "token(gradients.infoGlass)",
296
+ "--step-edge": "token(gradients.border.infoGlass)",
297
+ },
298
+ // Waiting: the raised glass in dark; in light the grey well inside a flat divider hairline.
299
+ neutral: {
300
+ background: "token(gradients.raised)",
301
+ _light: {
302
+ background: "bg.field",
303
+ "--step-edge": "linear-gradient(token(colors.border.divider), token(colors.border.divider))",
304
+ },
305
+ },
306
+ danger: {
307
+ background: "token(gradients.dangerGlass)",
308
+ "--step-edge": "token(gradients.border.errorTint)",
309
+ _light: { background: "bg.errorTint" },
310
+ },
311
+ },
312
+ },
313
+ });
314
+ /*
315
+ * The fold toggle at the header's end: a 24px round button in its hue's glass. In dark it takes
316
+ * the row's edge; in light the yellow and red ones keep their glass's own, softer than the tint's.
317
+ */
318
+ const collapseToggle = cva({
319
+ base: {
320
+ display: "inline-grid",
321
+ placeItems: "center",
322
+ flexShrink: "0",
323
+ width: "control.shortcut",
324
+ height: "control.shortcut",
325
+ borderRadius: "round",
326
+ background: "token(gradients.raised)",
327
+ color: "fg.primary",
328
+ "--edge": "var(--step-edge)",
329
+ },
330
+ variants: {
331
+ tone: {
332
+ warning: {
333
+ background: "token(gradients.tinted)",
334
+ _light: { "--edge": "token(gradients.border.tinted)" },
335
+ },
336
+ success: { background: "token(gradients.successGlass)" },
337
+ info: { background: "token(gradients.infoGlass)" },
338
+ // The grey toggle keeps the raised glass's edge where its row draws a flat hairline.
339
+ neutral: { _light: { "--edge": "token(gradients.border.raised)" } },
340
+ danger: {
341
+ background: "token(gradients.dangerGlass)",
342
+ _light: { "--edge": "token(gradients.border.dangerGlass)" },
343
+ },
228
344
  },
229
345
  },
230
346
  });
@@ -245,21 +361,26 @@ const attentionHeader = css({
245
361
  fontSize: "code",
246
362
  fontWeight: "medium",
247
363
  letterSpacing: "compact",
364
+ lineHeight: "tight",
248
365
  "&[data-tone=warning]": { color: "accent.yellow" },
249
366
  "&[data-tone=success]": { color: "accent.green" },
250
367
  "&[data-tone=info]": { color: "accent.blue" },
251
- "&[data-tone=neutral]": { color: "fg.muted" },
252
- "&[data-tone=danger]": { color: "accent.red" },
368
+ "&[data-tone=neutral]": { color: "fg.secondary" },
369
+ // A stuck step's header is the muted red the kit gives red text on its tint.
370
+ "&[data-tone=danger]": { color: "accent.redDark" },
253
371
  });
372
+ // The card holds the step on the page's own face: the solid card in light, the panel in dark.
254
373
  const attentionCard = css({
255
374
  display: "flex",
256
375
  alignItems: "center",
257
376
  justifyContent: "space-between",
258
377
  gap: "layout.clusterGap",
259
378
  padding: "panel.default",
260
- border: "1px solid token(colors.border.glass)",
379
+ border: "1px solid transparent",
261
380
  borderRadius: "control",
262
381
  background: "bg.panel",
382
+ _light: { background: "bg.card" },
383
+ "--edge": "var(--step-edge)",
263
384
  "@container (max-width: 560px)": { flexDirection: "column", alignItems: "stretch" },
264
385
  _phone: { padding: "panel.compact" },
265
386
  });
@@ -278,6 +399,7 @@ const attentionDescription = css({
278
399
  color: "fg.secondary",
279
400
  fontSize: "code",
280
401
  fontWeight: "medium",
402
+ lineHeight: "body",
281
403
  marginTop: "layout.tightGap",
282
404
  });
283
405
  const toneStatus = {
@@ -293,31 +415,116 @@ function StepActionButton({ action, variant, }) {
293
415
  return button;
294
416
  return (_jsx(Popover, { trigger: button, open: action.popover.open, onOpenChange: action.popover.onOpenChange, children: action.popover.content }));
295
417
  }
418
+ /*
419
+ * Help menu: the ways forward a stuck step offers behind "Not sure?", on the popover's glass. A
420
+ * header says what the menu is for; each option is an icon on the raised tile, a title and a line
421
+ * on what it does.
422
+ */
423
+ const helpPopup = css({
424
+ display: "grid",
425
+ width: "overlay.helpMenu",
426
+ maxWidth: "calc(100vw - token(spacing.panel.roomy))",
427
+ padding: "menu.inset",
428
+ border: "1px solid token(colors.border.divider)",
429
+ borderRadius: "action",
430
+ background: "bg.popover",
431
+ backdropFilter: "blur(token(blurs.popover))",
432
+ boxShadow: "soft",
433
+ color: "fg.primary",
434
+ fontWeight: "medium",
435
+ letterSpacing: "compact",
436
+ outline: "none",
437
+ });
438
+ const helpHeader = css({
439
+ display: "grid",
440
+ gap: "control.trackInset",
441
+ paddingBlock: "control.block",
442
+ paddingInline: "control.compactInline",
443
+ fontSize: "caption",
444
+ lineHeight: "tight",
445
+ });
446
+ const helpDetail = css({ color: "fg.subtle", fontSize: "code", lineHeight: "body" });
447
+ const helpOption = css({
448
+ display: "flex",
449
+ alignItems: "center",
450
+ gap: "layout.controlGap",
451
+ paddingBlock: "control.block",
452
+ paddingInline: "control.compactInline",
453
+ borderRadius: "control",
454
+ cursor: "pointer",
455
+ outline: "none",
456
+ "&[data-highlighted]": { background: "token(gradients.selected)" },
457
+ });
458
+ const helpTile = css({
459
+ display: "grid",
460
+ placeItems: "center",
461
+ flexShrink: "0",
462
+ width: "control.roundSmall",
463
+ height: "control.roundSmall",
464
+ borderRadius: "action",
465
+ background: "token(gradients.raised)",
466
+ fontSize: "icon.small",
467
+ "--edge": "token(gradients.border.raised)",
468
+ });
469
+ const helpCopy = css({ display: "grid", gap: "control.trackInset", minWidth: "0" });
470
+ const helpTitle = css({ fontSize: "caption", lineHeight: "tight" });
471
+ export function HelpMenu({ trigger, title, detail, options }) {
472
+ return (_jsxs(BaseMenu.Root, { children: [_jsx(BaseMenu.Trigger, { render: trigger }), _jsx(BaseMenu.Portal, { children: _jsx(HotkeyLayer, { children: _jsx(BaseMenu.Positioner, { className: css({ zIndex: "popover" }), sideOffset: 6, align: "end", children: _jsxs(BaseMenu.Popup, { className: helpPopup, "data-ciridae-ui": "", children: [_jsxs("div", { className: helpHeader, role: "presentation", children: [title, detail !== undefined && _jsx("span", { className: helpDetail, children: detail })] }), options.map((option) => (_jsxs(BaseMenu.Item, { className: helpOption, onClick: option.onSelect, children: [option.icon && (_jsx("span", { className: helpTile, "data-ciridae-ui": "", "data-edge": "gradient", children: option.icon })), _jsxs("span", { className: helpCopy, children: [_jsx("span", { className: helpTitle, children: option.label }), option.detail && _jsx("span", { className: helpDetail, children: option.detail })] })] }, option.label)))] }) }) }) })] }));
473
+ }
296
474
  /** The step the reader acts on: a tinted header that folds the card with its title and actions. */
297
475
  export function CurrentStep({ tone, meta, title, description, primary, secondary, help, busy = false, expanded: ownExpanded, defaultExpanded = true, onExpandedChange, className, ...props }) {
298
476
  const { labels } = useCiridaeLocale();
299
477
  const [expanded, setExpanded] = useControllableState(ownExpanded, defaultExpanded, onExpandedChange);
300
- return (_jsxs("div", { ...props, className: cx(attentionRow({ tone }), className), "data-ciridae-ui": "", children: [_jsxs("button", { type: "button", className: attentionHeader, "data-tone": tone, "aria-expanded": expanded, onClick: () => setExpanded(!expanded), children: [busy ? (_jsx(Spinner, { size: "compact", "aria-hidden": "true" })) : (_jsx(StepDot, { status: toneStatus[tone], size: "small" })), _jsx("span", { className: css({ flex: "1" }), children: meta }), expanded ? (_jsx(CaretUpIcon, { className: icon({ size: "compact" }) })) : (_jsx(CaretDownIcon, { className: icon({ size: "compact" }) }))] }), expanded && (_jsxs("div", { className: attentionCard, children: [_jsxs("div", { className: css({ minWidth: "0" }), children: [_jsx("div", { className: attentionTitle, children: title }), description !== undefined && _jsx("div", { className: attentionDescription, children: description })] }), (help || secondary || primary) && (_jsxs(Cluster, { gap: "4", className: stepActions, children: [help && (_jsx(Menu, { align: "end", trigger: _jsx(Button, { variant: "ghost", children: labels.record.notSure }), items: help })), secondary && (_jsx(StepActionButton, { action: secondary, variant: tone === "warning" ? "tinted" : "secondary" })), primary && _jsx(StepActionButton, { action: primary, variant: "primary" })] }))] }))] }));
478
+ return (_jsxs("div", { ...props, className: cx(attentionRow({ tone }), className), "data-ciridae-ui": "", "data-edge": "gradient", children: [_jsxs("button", { type: "button", className: attentionHeader, "data-tone": tone, "aria-expanded": expanded, onClick: () => setExpanded(!expanded), children: [busy ? (_jsx(Spinner, { size: "compact", "aria-hidden": "true" })) : (_jsx(StepDot, { status: toneStatus[tone], size: "small" })), _jsx("span", { className: css({ flex: "1" }), children: meta }), _jsx("span", { className: collapseToggle({ tone }), "data-ciridae-ui": "", "data-edge": "gradient", children: expanded ? (_jsx(CaretUpIcon, { className: icon({ size: "compact" }) })) : (_jsx(CaretDownIcon, { className: icon({ size: "compact" }) })) })] }), expanded && (_jsxs("div", { className: attentionCard, "data-ciridae-ui": "", "data-edge": "gradient", children: [_jsxs("div", { className: css({ minWidth: "0" }), children: [_jsx("div", { className: attentionTitle, children: title }), description !== undefined && _jsx("div", { className: attentionDescription, children: description })] }), (help || secondary || primary) && (_jsxs(Cluster, { gap: "4", className: stepActions, children: [help && (_jsx(HelpMenu, { ...help, trigger: _jsx(Button, { variant: "ghost", children: labels.record.notSure }) })), secondary && (_jsx(StepActionButton, { action: secondary, variant: tone === "warning" ? "tinted" : "secondary" })), primary && _jsx(StepActionButton, { action: primary, variant: "primary" })] }))] }))] }));
301
479
  }
302
- /* Confirmation row. */
480
+ /* Confirmation row: the ready step's green, one 32px line inside a 4px inset. */
303
481
  const confirmation = css({
304
482
  display: "flex",
305
483
  alignItems: "center",
306
484
  gap: "control.iconGap",
307
- minHeight: "control.default",
485
+ minHeight: "calc(token(sizes.control.compact) + token(spacing.panel.hairline) * 2)",
486
+ padding: "panel.hairline",
308
487
  paddingInlineStart: "control.compactInline",
309
- paddingInlineEnd: "control.block",
310
- border: "1px solid token(colors.border.glass)",
488
+ border: "1px solid transparent",
311
489
  borderRadius: "action",
312
- background: "bg.successSubtle",
490
+ background: "token(gradients.successGlass)",
313
491
  color: "accent.green",
314
492
  fontSize: "code",
315
493
  fontWeight: "medium",
494
+ lineHeight: "tight",
495
+ "--edge": "token(gradients.border.successTint)",
496
+ _light: { background: "bg.successTint" },
316
497
  });
317
498
  /** A move that just landed, with when and an undo. */
318
499
  export function ConfirmationRow({ message, at, onUndo, labels: ownLabels, className, ...props }) {
319
500
  const labels = { ...useCiridaeLocale().labels.record, ...ownLabels };
320
- return (_jsxs("div", { role: "status", ...props, className: cx(confirmation, className), "data-ciridae-ui": "", children: [_jsx(StepDot, { status: "done", size: "small" }), _jsx("span", { className: css({ flex: "1" }), children: message }), _jsx(MutedText, { children: _jsx(DateTime, { value: at }) }), onUndo && (_jsx(Button, { variant: "secondary", onClick: onUndo, children: labels.undo }))] }));
501
+ return (_jsxs("div", { role: "status", ...props, className: cx(confirmation, className), "data-ciridae-ui": "", "data-edge": "gradient", children: [_jsx(StepDot, { status: "done", size: "small" }), _jsx("span", { className: css({ flex: "1" }), children: message }), _jsx(MutedText, { children: _jsx(DateTime, { value: at }) }), onUndo && (_jsx(Button, { variant: "secondary", size: "small", onClick: onUndo, children: labels.undo }))] }));
502
+ }
503
+ /*
504
+ * Requirement: one thing a stage needs, as its 12px dot, its 13px name and a muted meta. A done
505
+ * one steps back to the muted grey; a locked one fades, so what is coming stays in view; a
506
+ * dependent one indents under the step it waits on.
507
+ */
508
+ const requirement = css({
509
+ display: "inline-flex",
510
+ alignItems: "center",
511
+ gap: "control.iconGap",
512
+ minWidth: "0",
513
+ color: "fg.primary",
514
+ fontSize: "caption",
515
+ fontWeight: "medium",
516
+ letterSpacing: "compact",
517
+ lineHeight: "tight",
518
+ "&[data-status=done]": { color: "fg.subtle" },
519
+ // The row fades as a whole, so its dot does not fade a second time.
520
+ "&[data-status=locked]": { opacity: "locked.requirement", "& > [data-status]": { opacity: "1" } },
521
+ "&[data-nested]": {
522
+ paddingInlineStart: "calc(token(sizes.avatar.micro) + token(spacing.control.iconGap))",
523
+ },
524
+ });
525
+ const requirementMeta = css({ flexShrink: "0", color: "fg.subtle", fontSize: "code" });
526
+ export function Requirement({ status, meta, nested = false, children, className, ...props }) {
527
+ return (_jsxs("span", { ...props, className: cx(requirement, className), "data-ciridae-ui": "", "data-status": status, "data-nested": nested || undefined, children: [_jsx(StepDot, { status: status, size: "chip" }), _jsx("span", { className: css({ minWidth: "0" }), children: children }), meta !== undefined && _jsx("span", { className: requirementMeta, children: meta })] }));
321
528
  }
322
529
  /* Stage checklists: what each stage needs, needed or will need, as plain rows under its label. */
323
530
  const checklist = css({
@@ -340,26 +547,175 @@ const checklistHeading = css({
340
547
  fontWeight: "medium",
341
548
  });
342
549
  const checklistList = css({ display: "grid", margin: "none", padding: "none", listStyle: "none" });
343
- const checklistRow = css({
550
+ const checklistRow = css({ display: "flex", alignItems: "center", minHeight: "control.shortcut" });
551
+ /** Checklists side by side, each under its label with its meta or how many of its steps are done. */
552
+ export function StageChecklist({ groups, className, ...props }) {
553
+ const { labels } = useCiridaeLocale();
554
+ return (_jsx("div", { ...props, className: cx(checklist, className), "data-ciridae-ui": "", children: groups.map(({ id, label, meta, steps }) => {
555
+ const done = steps.filter((step) => step.status === "done").length;
556
+ return (_jsxs("section", { className: checklistGroup, children: [_jsxs("h3", { className: checklistHeading, children: [label, _jsx("span", { children: meta ?? (steps.length > 0 && labels.record.checklistProgress(done, steps.length)) })] }), _jsx("ul", { className: checklistList, children: steps.map((step, index) => (_jsx("li", { className: checklistRow, children: _jsx(Requirement, { status: step.status, meta: step.meta, nested: step.nested, children: step.label }) }, step.id ?? index))) })] }, id));
557
+ }) }));
558
+ }
559
+ const stepChip = css({
560
+ display: "inline-flex",
561
+ alignItems: "center",
562
+ gap: "control.iconGap",
563
+ flexShrink: "0",
564
+ height: "control.chip",
565
+ paddingInlineStart: "control.iconGap",
566
+ paddingInlineEnd: "control.block",
567
+ border: "1px solid transparent",
568
+ borderRadius: "pill",
569
+ background: "token(gradients.raised)",
570
+ boxShadow: "soft",
571
+ color: "fg.label",
572
+ fontSize: "code",
573
+ fontWeight: "medium",
574
+ letterSpacing: "compact",
575
+ lineHeight: "tight",
576
+ whiteSpace: "nowrap",
577
+ "--edge": "token(gradients.border.raised)",
578
+ "&[data-status=done]": { color: "accent.green" },
579
+ "&[data-status=current]": { color: "accent.yellow" },
580
+ "&[data-status=agent]": {
581
+ height: "control.shortcut",
582
+ color: "accent.blue",
583
+ "& > [data-label]": {
584
+ background: "token(gradients.chart.progress)",
585
+ backgroundClip: "text",
586
+ color: "transparent",
587
+ },
588
+ "& > svg": { fontSize: "icon.small" },
589
+ },
590
+ });
591
+ export function StepChip({ status, children, className, ...props }) {
592
+ return (_jsxs("span", { ...props, className: cx(stepChip, className), "data-ciridae-ui": "", "data-edge": "gradient", "data-status": status, children: [status === "agent" ? (_jsx(SparkleIcon, { weight: "fill", "aria-hidden": "true" })) : (_jsx(StepDot, { status: status, size: "chip" })), _jsx("span", { "data-label": "", children: children })] }));
593
+ }
594
+ /*
595
+ * Step chain: the steps that follow from the expanded step, one row a lead: a tree line from the
596
+ * step above, the lead's dot and words, then its steps as chips, each joined to the one before by
597
+ * a short link in that one's hue: green after a done step, yellow after the current, grey ahead.
598
+ */
599
+ const stepChain = css({
600
+ display: "flex",
601
+ alignItems: "flex-end",
602
+ gap: "layout.tightGap",
603
+ height: "control.compact",
604
+ paddingInline: "control.iconGap",
605
+ minWidth: "0",
606
+ });
607
+ // An L from the step above down to the lead, its corner rounded.
608
+ const chainTree = css({
609
+ flexShrink: "0",
610
+ alignSelf: "flex-start",
611
+ width: "9px",
612
+ height: "calc(100% - token(spacing.panel.default) + 1px)",
613
+ borderInlineStart: "1px solid token(colors.border.divider)",
614
+ borderBottom: "1px solid token(colors.border.divider)",
615
+ borderEndStartRadius: "keycap",
616
+ });
617
+ const chainItems = css({
344
618
  display: "flex",
345
619
  alignItems: "center",
346
620
  gap: "control.iconGap",
347
- minHeight: "control.shortcut",
348
- color: "fg.secondary",
621
+ height: "control.chip",
622
+ minWidth: "0",
623
+ overflow: "hidden",
624
+ });
625
+ const chainLead = css({
626
+ flexShrink: "0",
349
627
  fontSize: "code",
350
628
  fontWeight: "medium",
351
629
  letterSpacing: "compact",
352
- "&[data-status=done]": { color: "fg.muted" },
353
- "&[data-status=current]": { color: "fg.primary" },
354
- "&[data-status=agent]": { color: "accent.blue" },
355
- "&[data-status=blocked]": { color: "accent.red" },
356
- "&[data-status=locked], &[data-status=todo]": { color: "fg.muted" },
630
+ lineHeight: "tight",
631
+ whiteSpace: "nowrap",
632
+ color: "fg.label",
633
+ "&[data-status=done]": { color: "accent.green" },
634
+ "&[data-status=current]": { color: "accent.yellow" },
357
635
  });
358
- /** Checklists side by side, each under its label with its meta or how many of its steps are done. */
359
- export function StageChecklist({ groups, className, ...props }) {
360
- const { labels } = useCiridaeLocale();
361
- return (_jsx("div", { ...props, className: cx(checklist, className), "data-ciridae-ui": "", children: groups.map(({ id, label, meta, steps }) => {
362
- const done = steps.filter((step) => step.status === "done").length;
363
- return (_jsxs("section", { className: checklistGroup, children: [_jsxs("h3", { className: checklistHeading, children: [label, _jsx("span", { children: meta ?? (steps.length > 0 && labels.record.checklistProgress(done, steps.length)) })] }), _jsx("ul", { className: checklistList, children: steps.map((step, index) => (_jsxs("li", { className: checklistRow, "data-status": step.status, children: [_jsx(StepDot, { status: step.status, size: "chip" }), step.label] }, step.id ?? index))) })] }, id));
364
- }) }));
636
+ const chainLink = css({
637
+ flexShrink: "0",
638
+ width: "token(spacing.control.compactInline)",
639
+ height: "border.hairline",
640
+ background: "border.divider",
641
+ "&[data-after=done]": { background: "accent.green" },
642
+ "&[data-after=current]": { background: "accent.yellowIcon" },
643
+ });
644
+ export function StepChain({ lead, steps, className, ...props }) {
645
+ return (_jsxs("div", { ...props, className: cx(stepChain, className), "data-ciridae-ui": "", children: [_jsx("span", { className: chainTree, "aria-hidden": "true" }), _jsxs("span", { className: chainItems, children: [_jsx(StepDot, { status: lead.status, size: "small" }), _jsx("span", { className: chainLead, "data-status": lead.status, children: lead.label }), steps.map((step, index) => (_jsxs("span", { className: css({ display: "contents" }), children: [_jsx("span", { className: chainLink, "aria-hidden": "true", "data-after": index === 0 ? lead.status : steps[index - 1].status }), _jsx(StepChip, { status: step.status, children: step.label })] }, step.id)))] })] }));
646
+ }
647
+ /*
648
+ * Parallel chip: someone else's work under way beside the reader's, on the selected glass. A
649
+ * person shows their avatar, the agent its dashed blue dot, the system a dashed grey one; then
650
+ * who, what they are doing, and at most one action.
651
+ */
652
+ const parallelChip = css({
653
+ display: "inline-flex",
654
+ alignItems: "center",
655
+ gap: "layout.controlGap",
656
+ minWidth: "0",
657
+ paddingBlock: "control.compactBlock",
658
+ paddingInlineStart: "control.block",
659
+ paddingInlineEnd: "control.compactInline",
660
+ border: "1px solid transparent",
661
+ borderRadius: "shell",
662
+ background: "token(gradients.selected)",
663
+ fontWeight: "medium",
664
+ letterSpacing: "compact",
665
+ lineHeight: "tight",
666
+ whiteSpace: "nowrap",
667
+ "--edge": "token(gradients.border.selected)",
668
+ });
669
+ const parallelWho = css({ color: "fg.primary", fontSize: "caption" });
670
+ const parallelDoing = css({
671
+ minWidth: "0",
672
+ overflow: "hidden",
673
+ textOverflow: "ellipsis",
674
+ color: "fg.subtle",
675
+ fontSize: "code",
676
+ });
677
+ const parallelAction = css({
678
+ padding: "none",
679
+ border: "none",
680
+ borderRadius: "control",
681
+ background: "transparent",
682
+ color: "accent.blue",
683
+ font: "inherit",
684
+ fontSize: "caption",
685
+ cursor: "pointer",
686
+ _hover: { textDecoration: "underline" },
687
+ _focusVisible: { focusRing: "outer" },
688
+ });
689
+ export function ParallelChip({ actor, who, doing, action, className, ...props }) {
690
+ return (_jsxs("span", { ...props, className: cx(parallelChip, className), "data-ciridae-ui": "", "data-edge": "gradient", children: [actor.kind === "person" ? (actor.avatar) : (_jsx(StepDot, { status: actor.kind === "agent" ? "agent" : "waiting" })), _jsx("span", { className: parallelWho, children: who }), _jsx("span", { className: parallelDoing, children: doing }), action && (_jsx("button", { type: "button", className: parallelAction, onClick: action.onClick, children: action.label }))] }));
691
+ }
692
+ const partsRollup = css({
693
+ display: "flex",
694
+ alignItems: "center",
695
+ flexWrap: "wrap",
696
+ gap: "layout.contentGap",
697
+ minHeight: "partsRollup.height",
698
+ paddingBlock: "panel.hairline",
699
+ fontWeight: "medium",
700
+ letterSpacing: "compact",
701
+ lineHeight: "tight",
702
+ });
703
+ const partsLabel = css({ color: "fg.primary", fontSize: "caption" });
704
+ const partsBar = css({
705
+ display: "flex",
706
+ gap: "layout.tightGap",
707
+ width: "320px",
708
+ maxWidth: "100%",
709
+ height: "progress.track",
710
+ });
711
+ const partsSegment = css({
712
+ flex: "1 1 0",
713
+ borderRadius: "round",
714
+ background: "chart.notStarted",
715
+ "&[data-state=done]": { background: "accent.green" },
716
+ "&[data-state=needsYou]": { background: "token(gradients.chart.atRisk)" },
717
+ });
718
+ const partsLegend = css({ color: "fg.muted", fontSize: "code", _light: { color: "fg.subtle" } });
719
+ export function PartsRollup({ label, parts, legend, className, ...props }) {
720
+ return (_jsxs("div", { ...props, className: cx(partsRollup, className), "data-ciridae-ui": "", children: [_jsx("span", { className: partsLabel, children: label }), _jsx("span", { className: partsBar, "aria-hidden": "true", children: parts.map((state, index) => (_jsx("span", { className: partsSegment, "data-state": state }, index))) }), _jsx("span", { className: partsLegend, children: legend })] }));
365
721
  }