@danxbot/ui 2.7.1 → 2.9.0

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.
@@ -55,6 +55,57 @@ export interface DragDropItemProps {
55
55
  children?: ReactNode;
56
56
  }
57
57
  export declare function DragDropItem({ id, index, label, className, children, }: DragDropItemProps): import("react").JSX.Element;
58
+ export interface DragDropSurfaceProps {
59
+ /**
60
+ * Accessible name, used ONLY in the case where the surface has to become
61
+ * focusable itself — see the banner. Defaults to the item's label.
62
+ */
63
+ label?: string;
64
+ className?: string;
65
+ children?: ReactNode;
66
+ }
67
+ /**
68
+ * THE WHOLE ROW IS THE DRAG SOURCE. The alternative to `DragDropHandle`, and
69
+ * the right one wherever every row is draggable by definition.
70
+ *
71
+ * A GRIP IS A CHOICE ABOUT WHICH ROWS MOVE. On a list where some rows are fixed
72
+ * and some are not, the grip is the affordance that says which — and it is also
73
+ * the only way to keep a row full of buttons from starting a drag from every
74
+ * one of them. On a kanban board neither is true: every card moves, and the
75
+ * card's own job is to open a record. Reported, in the user's words: "I should
76
+ * be able to drag anywhere on the card and it should drag the card. Get rid of
77
+ * the drag handle entirely. Cards are just draggable. Click opens them, drag
78
+ * drags them."
79
+ *
80
+ * THE HARD PART IS NOT THE DRAG, IT IS THE CLICK. A card that is both a drag
81
+ * source and a link has to tell a press-that-moved from a press-that-did-not,
82
+ * and it has to do it without a threshold invented here — the engine already
83
+ * has one, tuned against real devices (`PointerActivation`: 5px for a mouse, a
84
+ * 250ms dwell for a finger). A press under that stays a click and opens the
85
+ * record; a press over it becomes a drag AND has its trailing `click`
86
+ * swallowed, in `sensors/gesture.ts`, because the browser fires one either way.
87
+ *
88
+ * THE KEYBOARD IS THE HALF THAT IS EASY TO LOSE, and losing it is not
89
+ * acceptable — a board that can only be reordered with a mouse is a board some
90
+ * people cannot reorder at all. Two things keep it:
91
+ *
92
+ * THE KEYS ARE SPLIT. Space picks the card up, Enter is left to whatever
93
+ * control has focus inside it. The keyboard sensor reads that off the event's
94
+ * target rather than from a flag here, so a grip (where the source IS the
95
+ * focused control) keeps both keys and a card gets the split, with nothing to
96
+ * configure and nothing to forget.
97
+ *
98
+ * THE INSTRUCTIONS FOLLOW THE FOCUS. A grip carried `aria-describedby`
99
+ * itself. A card's focus lands on the consumer's own control — a
100
+ * `RecordCard`'s button — which this component did not render and cannot pass
101
+ * props to, so the description is attached to that element directly and
102
+ * removed again on unmount. A card with NO focusable control of its own has
103
+ * nothing to describe and no way into a drag, so the surface becomes the
104
+ * focusable control instead. Both branches are measured by
105
+ * `probe-kanban.mjs`; without the second one, a board of plain cards would be
106
+ * keyboard-inaccessible with nothing to see and nothing to fail.
107
+ */
108
+ export declare function DragDropSurface({ label, className, children }: DragDropSurfaceProps): import("react").JSX.Element;
58
109
  export interface DragDropHandleProps {
59
110
  /** Accessible name. Defaults to `Reorder <the item's label>`. */
60
111
  label?: string;
@@ -1,4 +1,4 @@
1
- import type { ReactNode, Ref } from "react";
1
+ import { type ReactNode, type Ref } from "react";
2
2
  import { type VariantProps } from "tailwind-variants";
3
3
  declare const bar: import("tailwind-variants").TVReturnType<{
4
4
  size: {
@@ -151,8 +151,28 @@ declare const ring: import("tailwind-variants").TVReturnType<{
151
151
  lg: "size-24";
152
152
  };
153
153
  }, undefined>>;
154
- export interface ProgressRingProps extends VariantProps<typeof ring> {
155
- value: number | null;
154
+ /** The tones a bar, meter or ring arc can be drawn in. */
155
+ export type ProgressTone = NonNullable<VariantProps<typeof bar>["tone"]>;
156
+ /** One arc of a segmented ring: how much, in what tone, called what. */
157
+ export interface ProgressRingSegment {
158
+ /**
159
+ * How much of `total` this arc covers, in the caller's own units — tokens,
160
+ * megabytes, jobs. Never a percentage of its own accord; the denominator is
161
+ * `total` and nothing else.
162
+ */
163
+ value: number;
164
+ tone: ProgressTone;
165
+ /**
166
+ * What this arc IS, in words.
167
+ *
168
+ * Not decorative and not optional: it is read out as part of the ring's
169
+ * description, and it is the only thing a legend beside the ring has to name
170
+ * a colour by. A segmented ring whose arcs had no names would encode its
171
+ * entire breakdown in hue alone.
172
+ */
173
+ label: string;
174
+ }
175
+ interface ProgressRingBase extends VariantProps<typeof ring> {
156
176
  /**
157
177
  * REQUIRED, and a plain string.
158
178
  *
@@ -163,16 +183,108 @@ export interface ProgressRingProps extends VariantProps<typeof ring> {
163
183
  * of this component existing.
164
184
  */
165
185
  label: string;
166
- tone?: VariantProps<typeof bar>["tone"];
186
+ /**
187
+ * Draw the number in the middle. On by default, which is the behaviour this
188
+ * component has always had.
189
+ *
190
+ * Worth turning off on a `sm` ring used as an affordance rather than a
191
+ * readout — two digits inside a 40px circle is a number nobody reads and a
192
+ * ring nobody can see past.
193
+ */
194
+ showValue?: boolean;
167
195
  className?: string;
168
196
  }
197
+ /** A ring showing ONE number. `null` is "working, length unknown". */
198
+ export interface ProgressRingValueProps extends ProgressRingBase {
199
+ value: number | null;
200
+ tone?: ProgressTone;
201
+ segments?: never;
202
+ total?: never;
203
+ remainderLabel?: never;
204
+ }
205
+ /**
206
+ * A ring showing a BREAKDOWN: contiguous arcs around one circle.
207
+ *
208
+ * The two shapes are a union rather than two optional props, so passing both
209
+ * `value` and `segments` does not type-check. There is no precedence rule to
210
+ * learn and no silent winner.
211
+ */
212
+ export interface ProgressRingSegmentsProps extends ProgressRingBase {
213
+ /** Drawn in order, starting at twelve o'clock and running clockwise. */
214
+ segments: ProgressRingSegment[];
215
+ /**
216
+ * The whole the segments are parts OF, in the same units as their values.
217
+ *
218
+ * REQUIRED, with no default. A default of 100 would read as "the values are
219
+ * percentages" and quietly turn a ring of token counts into a ring that
220
+ * overflows on its first render — and the denominator is the one fact that
221
+ * makes a remainder arc mean anything.
222
+ *
223
+ * SEGMENTS SUMMING TO MORE THAN THIS ARE **CLAMPED, NOT NORMALISED** — see
224
+ * the note above `layoutRingSegments`.
225
+ */
226
+ total: number;
227
+ /**
228
+ * What the unfilled part of the ring IS, for the description: "Remaining",
229
+ * "Free", "Unallocated".
230
+ *
231
+ * The arc itself needs no drawing — it is the track showing through — but it
232
+ * still needs a name, because "72% used" and "28% free" are the same fact
233
+ * and readers act on different halves of it.
234
+ */
235
+ remainderLabel?: string;
236
+ value?: never;
237
+ tone?: never;
238
+ }
239
+ export type ProgressRingProps = ProgressRingValueProps | ProgressRingSegmentsProps;
240
+ /** One arc, resolved to the two fractions an SVG dash actually needs. */
241
+ export interface ProgressRingArc {
242
+ label: string;
243
+ tone: ProgressTone;
244
+ /** The segment's own magnitude, in caller units, before any clamping. */
245
+ value: number;
246
+ /** Where the arc begins, as a fraction of the circumference. */
247
+ start: number;
248
+ /** How far it is drawn for, as a fraction, with its gap already taken off. */
249
+ length: number;
250
+ }
251
+ export interface ProgressRingLayout {
252
+ arcs: ProgressRingArc[];
253
+ /** Every segment added up, in caller units. NOT clamped — this is the fact. */
254
+ used: number;
255
+ /** How far `used` overshoots `total`. Zero when the segments fit. */
256
+ overflow: number;
257
+ }
258
+ export declare function layoutRingSegments(segments: ProgressRingSegment[], total: number): ProgressRingLayout;
169
259
  /**
170
260
  * Circular progress, for when the number belongs INSIDE the indicator —
171
261
  * a tile, a card corner, a stat.
172
262
  *
263
+ * Two shapes, chosen by which prop is passed:
264
+ *
265
+ * ```tsx
266
+ * <ProgressRing value={62} label="Context used" />
267
+ * <ProgressRing
268
+ * label="Context used"
269
+ * total={200_000}
270
+ * segments={[
271
+ * { label: "System prompt", value: 12_000, tone: "brand" },
272
+ * { label: "Conversation", value: 84_000, tone: "accent" },
273
+ * ]}
274
+ * remainderLabel="Free"
275
+ * />
276
+ * ```
277
+ *
173
278
  * Uses `pathLength="1"` so the dash numbers are fractions rather than
174
279
  * circumference maths: the same two values work at any radius, and it dodges
175
280
  * the Safari bug where a zoomed SVG rescales absolute dash lengths.
281
+ *
282
+ * THE ROLE IS `progressbar` IN BOTH SHAPES, and stays that way even though a
283
+ * breakdown of a budget is arguably a gauge. The role is a property of the
284
+ * component a caller reached for, not of the data they handed it — a component
285
+ * that announced itself as a different thing depending on the shape of its
286
+ * props would be two components sharing one name. A caller whose fact is a
287
+ * gauge has `Meter`.
176
288
  */
177
- export declare function ProgressRing({ value, label, size, tone, className, }: ProgressRingProps): import("react").JSX.Element;
289
+ export declare function ProgressRing(props: ProgressRingProps): import("react").JSX.Element;
178
290
  export {};
@@ -1,4 +1,4 @@
1
- import type { ReactNode, Ref } from "react";
1
+ import { type ReactNode, type Ref } from "react";
2
2
  import { Select as BaseSelect } from "@base-ui/react/select";
3
3
  import { type VariantProps } from "tailwind-variants";
4
4
  declare const trigger: import("tailwind-variants").TVReturnType<{
@@ -28,7 +28,7 @@ export interface SelectProps<Value> extends Omit<BaseSelect.Root.Props<Value, bo
28
28
  children: ReactNode;
29
29
  ref?: Ref<HTMLButtonElement>;
30
30
  }
31
- export declare function Select<Value>({ size, placeholder, className, children, ref, "aria-label": ariaLabel, ...props }: SelectProps<Value>): import("react").JSX.Element;
31
+ export declare function Select<Value>({ size, placeholder, className, children, ref, items, "aria-label": ariaLabel, ...props }: SelectProps<Value>): import("react").JSX.Element;
32
32
  export interface SelectItemProps extends Omit<BaseSelect.Item.Props, "className"> {
33
33
  className?: string;
34
34
  ref?: Ref<HTMLDivElement>;
@@ -2,6 +2,11 @@ import type { ReactNode, Ref } from "react";
2
2
  import { type VariantProps } from "tailwind-variants";
3
3
  declare const stat: import("tailwind-variants").TVReturnType<{
4
4
  size: {
5
+ xs: {
6
+ label: string;
7
+ hint: string;
8
+ value: string;
9
+ };
5
10
  sm: {
6
11
  value: string;
7
12
  };
@@ -21,6 +26,11 @@ declare const stat: import("tailwind-variants").TVReturnType<{
21
26
  hint: string;
22
27
  }, undefined, {
23
28
  size: {
29
+ xs: {
30
+ label: string;
31
+ hint: string;
32
+ value: string;
33
+ };
24
34
  sm: {
25
35
  value: string;
26
36
  };
@@ -40,6 +50,11 @@ declare const stat: import("tailwind-variants").TVReturnType<{
40
50
  hint: string;
41
51
  }, import("tailwind-variants").TVReturnTypeLike<{
42
52
  size: {
53
+ xs: {
54
+ label: string;
55
+ hint: string;
56
+ value: string;
57
+ };
43
58
  sm: {
44
59
  value: string;
45
60
  };
@@ -1,4 +1,4 @@
1
- import type { ReactNode, Ref } from "react";
1
+ import { type ReactNode, type Ref } from "react";
2
2
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
3
3
  import { type VariantProps } from "tailwind-variants";
4
4
  declare const tabs: import("tailwind-variants").TVReturnType<{
@@ -6,6 +6,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
6
6
  marker: string;
7
7
  landing: string;
8
8
  handle: string;
9
+ surface: string;
9
10
  };
10
11
  x: {
11
12
  container: string;
@@ -13,6 +14,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
13
14
  marker: string;
14
15
  landing: string;
15
16
  handle: string;
17
+ surface: string;
16
18
  };
17
19
  };
18
20
  moving: {
@@ -36,6 +38,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
36
38
  shift: string;
37
39
  marker: string[];
38
40
  landing: string;
41
+ surface: string[];
39
42
  handle: string[];
40
43
  overlay: string[];
41
44
  }, undefined, {
@@ -46,6 +49,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
46
49
  marker: string;
47
50
  landing: string;
48
51
  handle: string;
52
+ surface: string;
49
53
  };
50
54
  x: {
51
55
  container: string;
@@ -53,6 +57,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
53
57
  marker: string;
54
58
  landing: string;
55
59
  handle: string;
60
+ surface: string;
56
61
  };
57
62
  };
58
63
  moving: {
@@ -76,6 +81,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
76
81
  shift: string;
77
82
  marker: string[];
78
83
  landing: string;
84
+ surface: string[];
79
85
  handle: string[];
80
86
  overlay: string[];
81
87
  }, import("tailwind-variants").TVReturnTypeLike<{
@@ -86,6 +92,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
86
92
  marker: string;
87
93
  landing: string;
88
94
  handle: string;
95
+ surface: string;
89
96
  };
90
97
  x: {
91
98
  container: string;
@@ -93,6 +100,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
93
100
  marker: string;
94
101
  landing: string;
95
102
  handle: string;
103
+ surface: string;
96
104
  };
97
105
  };
98
106
  moving: {
@@ -116,6 +124,7 @@ export declare const dnd: import("tailwind-variants").TVReturnType<{
116
124
  shift: string;
117
125
  marker: string[];
118
126
  landing: string;
127
+ surface: string[];
119
128
  handle: string[];
120
129
  overlay: string[];
121
130
  }>>;
@@ -12,8 +12,7 @@ export declare const kanban: import("tailwind-variants").TVReturnType<{
12
12
  laneLabel: string;
13
13
  laneHint: string;
14
14
  laneItems: string;
15
- card: string[];
16
- cardHandle: string;
15
+ card: string;
17
16
  laneEmpty: string;
18
17
  laneRefusal: string;
19
18
  }, undefined, {
@@ -30,8 +29,7 @@ export declare const kanban: import("tailwind-variants").TVReturnType<{
30
29
  laneLabel: string;
31
30
  laneHint: string;
32
31
  laneItems: string;
33
- card: string[];
34
- cardHandle: string;
32
+ card: string;
35
33
  laneEmpty: string;
36
34
  laneRefusal: string;
37
35
  }, import("tailwind-variants").TVReturnTypeLike<{
@@ -48,8 +46,7 @@ export declare const kanban: import("tailwind-variants").TVReturnType<{
48
46
  laneLabel: string;
49
47
  laneHint: string;
50
48
  laneItems: string;
51
- card: string[];
52
- cardHandle: string;
49
+ card: string;
53
50
  laneEmpty: string;
54
51
  laneRefusal: string;
55
52
  }>>;
@@ -48,7 +48,7 @@ export { Toggle, ToggleGroup, Toolbar, ToolbarSeparator, type ToggleProps, type
48
48
  export { Separator, ScrollArea, Collapsible, EmptyState, Timeline, type SeparatorProps, type ScrollAreaProps, type CollapsibleProps, type EmptyStateProps, type TimelineProps, type TimelineItem, } from "./components/Surface";
49
49
  export { PanelGroup, Panel, PanelResizer, type PanelGroupProps, type PanelProps, type PanelResizerProps, type PanelDirection, type PanelNarrowMode, } from "./components/Panel";
50
50
  export { Avatar, AvatarGroup, type AvatarProps, type AvatarGroupProps, type AvatarStatus, } from "./components/Avatar";
51
- export { Progress, Meter, ProgressRing, type ProgressProps, type MeterProps, type ProgressRingProps, } from "./components/Progress";
51
+ export { Progress, Meter, ProgressRing, type ProgressProps, type MeterProps, type ProgressRingProps, type ProgressRingValueProps, type ProgressRingSegmentsProps, type ProgressRingSegment, type ProgressTone, } from "./components/Progress";
52
52
  export { Stat, Sparkline, type StatProps, type SparklineProps, type StatTrend, type StatSentiment, } from "./components/Stat";
53
53
  export { DataTable, TableEmptyState, CellStack, type DataTableProps, type TableDensity, } from "./components/Table";
54
54
  export { CodeEditor, CodeEditorToolbar, CodeEditorCaption, CodeEditorCopyButton, CodeEditorWrapToggle, CodeEditorViewSwitch, type CodeEditorProps, type CodeEditorToolbarProps, type CodeEditorView, } from "./components/CodeEditor";
@@ -69,7 +69,7 @@ export type { Command, Edit } from "./lib/markdown/commands";
69
69
  export { toggleBold, toggleItalic, toggleCode, toggleStrike, toggleInline, setHeading, adjustHeading, headingLevel, toggleList, toggleTask, indentLines, outdentLines, toggleQuote, toggleCodeBlock, fenceAround, insertRule, insertImage, insertLink, insertTable, insertRow, insertColumn, deleteRow, deleteColumn, deleteTable, alignColumn, tableAround, cellsOf, cellAt, tableTab, smartEnter, smartTab, smartShiftTab, } from "./lib/markdown/commands";
70
70
  export { tokenize, listLanguages, resolveLanguage, defineGrammar, registerGrammar, checkCoverage, type Token, type TokenType, type Grammar, type Rule, } from "./lib/syntax";
71
71
  export { parseMarkdown, MarkdownNodes, sanitizeHref, type MdNode, type MdInline, type MdCodeRenderer, } from "./lib/syntax/markdown";
72
- export { DragDrop, DragDropContainer, DragDropItem, DragDropHandle, type DragDropProps, type DragDropContainerProps, type DragDropItemProps, type DragDropHandleProps, } from "./components/DragDrop";
72
+ export { DragDrop, DragDropContainer, DragDropItem, DragDropHandle, DragDropSurface, type DragDropProps, type DragDropContainerProps, type DragDropItemProps, type DragDropHandleProps, type DragDropSurfaceProps, } from "./components/DragDrop";
73
73
  export { KanbanBoard, type KanbanBoardProps, type KanbanLane, type KanbanMove, type KanbanRefusal, } from "./components/kanban/KanbanBoard";
74
74
  export { DEFAULT_DRAG_STRINGS, DRAG_INSTRUCTIONS_ID, DragInstructions, DragLiveRegion, DragSession, DragSessionProvider, applyMove, attachAutoScroll, attachKeyboardSensor, attachPointerSensor, attachSessionWindowListeners, isNoOp, resolveTarget, sameTarget, scrollVelocity, useCreateDragSession, useDragAnnouncements, useDragSession, useDragState, useDraggable, useDropContainer, type ContainerId, type DragAxis, type DragCancelEvent, type DragEndEvent, type DragId, type DragMode, type DragPhase, type DragSessionCallbacks, type DragStartEvent, type DragState, type AutoScrollOptions, type DragAnnouncementContext, type DragAnnouncementOptions, type DragAnnouncements, type DragInstructionsProps, type DragLiveRegionProps, type DragStrings, type DropTarget, type KeyboardSensorOptions, type PointerActivation, } from "./lib/dnd";
75
75
  export type { MessageRole, MessagePart, MessagePartType, TextPart, CodePart, ReasoningPart, ToolPart, ToolStatus, FilePart, ComponentPart, ChatFile, ChatMessage, RenderContext, PartRenderer, PartRenderers, ChatComponentRenderer, ChatComponents, } from "./components/chat/types";
@@ -13,7 +13,11 @@ import type { DragAxis, DragId } from "../types";
13
13
  *
14
14
  * THE KEY MAP IS FIXED BY THE PROJECT CONTRACT, not by this file:
15
15
  *
16
- * Space / Enter pick up on a focused handle; drop when already carrying
16
+ * Space pick up; drop when already carrying
17
+ * Enter the same, but ONLY when the drag source is itself the
18
+ * focused control. On a whole-card source the key arrives by
19
+ * bubbling from the card's own button, and Enter is that
20
+ * button's — see the note at the check.
17
21
  * Escape cancel — handled by `attachSessionWindowListeners`, NOT here
18
22
  * Along the axis step the insertion point inside the current container
19
23
  * Across it move to the previous / next container
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The one reduced-motion answer, read from `--motion-scale`.
3
+ *
4
+ * WHY THE TOKEN AND NOT `matchMedia` DIRECTLY. `--motion-scale` is the single
5
+ * place where the OS media query and the app-level `data-motion` override are
6
+ * reconciled (see `styles/motion.css`). Reading the media query alone would
7
+ * ignore the in-app preference control, so a user who set "reduced" in the app
8
+ * on a machine with no OS setting would get full-speed motion — the exact case
9
+ * that control exists for. It is also why a raw `motion-reduce:` utility is the
10
+ * wrong tool for anything this system builds (see the note on `Choice`'s
11
+ * thumb): that variant reads the OS query alone and fires even for someone who
12
+ * has explicitly chosen `data-motion="full"`.
13
+ *
14
+ * ONLY THE COMPARISON IS EVER USED, never the value. Multiplying a SPEED by the
15
+ * scale would be backwards — a slower auto-scroll ramp holds the user at the
16
+ * edge longer and produces MORE motion overall — and a scroll has no "half"
17
+ * anyway: it is smooth or it is a jump.
18
+ *
19
+ * ITS OWN MODULE because two unrelated layers need it. The drag engine's edge
20
+ * auto-scroll had the only copy, and the tab strip's reveal-the-active-tab
21
+ * scroll needs exactly the same question answered exactly the same way. A
22
+ * second copy is a second place for the token name to go stale.
23
+ */
24
+ export declare function reducedMotion(): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danxbot/ui",
3
- "version": "2.7.1",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "Danxbot — a domain-agnostic React design system with motion as a first-class primitive.",
6
6
  "license": "MIT",
@@ -69,7 +69,7 @@
69
69
  "gates": "npm run shoot && npm run a11y && node scripts/shoot-overlays.mjs && npm run probe",
70
70
  "a11y": "node scripts/a11y-scan.mjs",
71
71
  "shoot": "node scripts/shoot.mjs",
72
- "probe": "node scripts/probe-scale.mjs && node scripts/probe-switch.mjs && node scripts/probe-slider.mjs && node scripts/probe-panel.mjs && node scripts/probe-popover-width.mjs && node scripts/probe-api.mjs && node scripts/probe-api-live.mjs",
72
+ "probe": "node scripts/probe-scale.mjs && node scripts/probe-switch.mjs && node scripts/probe-slider.mjs && node scripts/probe-panel.mjs && node scripts/probe-popover-width.mjs && node scripts/probe-api.mjs && node scripts/probe-api-live.mjs && node scripts/probe-tabs-overflow.mjs && node scripts/probe-stat-row.mjs",
73
73
  "sync": "node scripts/sync.mjs",
74
74
  "visual": "node scripts/visual-docker.mjs",
75
75
  "visual:update": "node scripts/visual-docker.mjs --update-snapshots",