@duro-app/cli 5.6.0 → 5.7.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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/registry.json +406 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/cli",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Machine-queryable docs for the Duro design system: components, props, recipes, tokens and rules as CLI output, JSON, or an MCP server.",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@modelcontextprotocol/sdk": ">=1.0.0",
48
- "@duro-app/ui": "^5.6.0"
48
+ "@duro-app/ui": "^5.7.0"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@duro-app/ui": {
@@ -60,9 +60,9 @@
60
60
  "@types/node": "^24.0.0",
61
61
  "ts-morph": "^26.0.0",
62
62
  "typescript": "^5.7.0",
63
- "@duro-app/eslint-plugin": "^5.6.0",
64
- "@duro-app/tokens": "^5.6.0",
65
- "@duro-app/ui": "^5.6.0"
63
+ "@duro-app/tokens": "^5.7.0",
64
+ "@duro-app/eslint-plugin": "^5.7.0",
65
+ "@duro-app/ui": "^5.7.0"
66
66
  },
67
67
  "scripts": {
68
68
  "registry": "node scripts/build-registry.mjs",
package/registry.json CHANGED
@@ -1947,14 +1947,17 @@
1947
1947
  "kind": "compound",
1948
1948
  "meta": {
1949
1949
  "anatomy": {
1950
+ "optional": [
1951
+ "Handle"
1952
+ ],
1950
1953
  "required": [
1951
1954
  "Root",
1952
1955
  "Zone",
1953
1956
  "Item"
1954
1957
  ]
1955
1958
  },
1956
- "description": "Move items between zones with one pointer — mouse, pen and touch through the same pointer-event path (touch holds briefly, then drags; a moving touch stays a scroll). A ghost follows the pointer, the zone under it lights up, and the Root announces picks and drops to screen readers. Dragging is pointer-only by design: keep a button or remove affordance that does the same thing.",
1957
- "example": "<DragDrop.Root onDrop={({item, target}) => move(item.id, target.zone, target.index)}>\n <DragDrop.Zone id=\"roster\" label=\"Roster\">\n <Cluster gap=\"sm\">\n {people.map((p) => (\n <DragDrop.Item key={p.id} id={p.id} zone=\"roster\" label={p.name} data={p}>\n <Button variant=\"secondary\" size=\"small\" onClick={() => slot(p)}>{p.name}</Button>\n </DragDrop.Item>\n ))}\n </Cluster>\n </DragDrop.Zone>\n <DragDrop.Zone id=\"gates\" label=\"Gates\" orientation=\"horizontal\">\n <Inline gap=\"sm\">\n {gates.map((g) => (\n <DragDrop.Item key={g.id} id={g.id} zone=\"gates\" label={g.name} data={g}>\n <Tag removable onRemove={() => unslot(g)}>{g.name}</Tag>\n </DragDrop.Item>\n ))}\n </Inline>\n </DragDrop.Zone>\n</DragDrop.Root>",
1959
+ "description": "Move items between zones by pointer (mouse, pen and touch through one pointer-event path; touch holds briefly, then drags) or by keyboard (a Handle per item: Space picks up, arrows move, Space drops, Escape cancels). The innermost accepting zone wins, so a zone nested in an Item (a card in a column) takes its own drops; a zone can refuse with a reason that is shown and announced. Placeholders, edge auto-scroll and list-style announcements (through `announce`, for catalog strings) come with it.",
1960
+ "example": "<DragDrop.Root\n onDrop={({item, target}) => move(item.id, target.zone, target.index)}\n renderPlaceholder={({label}) => <Text variant=\"caption\">→ {label}</Text>}\n announce={(e) => t(`board.drag.${e.kind}`, e)}\n>\n {columns.map((col) => (\n <DragDrop.Zone key={col.id} id={col.id} label={col.name} orientation=\"vertical\" list\n accepts={(item) => item.data.kind === 'task' || {ok: false, reason: t('board.attachOnCard')}}>\n {col.cards.map((card) => (\n <DragDrop.Item key={card.id} id={card.id} zone={col.id} label={card.title} data={card}>\n <DragDrop.Handle>\n <DragDrop.Zone id={`attach:${card.id}`} label={card.title}\n accepts={(item) => item.data.kind === 'activity'}>\n <Card>{card.title}</Card>\n </DragDrop.Zone>\n </DragDrop.Handle>\n </DragDrop.Item>\n ))}\n </DragDrop.Zone>\n ))}\n</DragDrop.Root>",
1958
1961
  "relatedTo": [
1959
1962
  {
1960
1963
  "component": "Tag",
@@ -1968,18 +1971,36 @@
1968
1971
  }
1969
1972
  ],
1970
1973
  "whenNotToUse": [
1971
- "As the ONLY way to do something — every drag needs a tap/click equivalent (WCAG 2.5.7); this component does not provide one",
1972
- "Long lists that scroll while dragging — the ghost follows the pointer but zones do not auto-scroll",
1974
+ "As the ONLY way to do something — WCAG 2.5.7 asks for a single-pointer alternative to dragging (a button or menu that moves the item); a Handle adds the keyboard path, not a click one",
1973
1975
  "Sorting tabular data — sort the Table instead",
1974
1976
  "React Native — Item renders its children without drag behaviour there"
1975
1977
  ],
1976
1978
  "whenToUse": [
1977
1979
  "Slotting things into places — people into approval gates, cards into columns, files into folders",
1978
- "Reordering a short row or list when the order is visible and meaningful"
1980
+ "Reordering a short row or list when the order is visible and meaningful",
1981
+ "A board: columns as labelled lists (`list`), cards with a Handle, an attach zone nested in each card"
1979
1982
  ]
1980
1983
  },
1981
1984
  "name": "DragDrop",
1982
1985
  "parts": {
1986
+ "Handle": {
1987
+ "name": "Handle",
1988
+ "props": [
1989
+ {
1990
+ "description": "Defaults to the Item's label.",
1991
+ "name": "aria-label",
1992
+ "required": false,
1993
+ "type": "string"
1994
+ },
1995
+ {
1996
+ "description": "What the handle shows: a grip, or the item's whole face. Keep other controls out of it — a handle is one widget.",
1997
+ "name": "children",
1998
+ "required": true,
1999
+ "type": "ReactNode"
2000
+ }
2001
+ ],
2002
+ "sourcePath": "packages/ui/src/components/DragDrop/DragDrop.tsx"
2003
+ },
1983
2004
  "Item": {
1984
2005
  "name": "Item",
1985
2006
  "props": [
@@ -1995,7 +2016,7 @@
1995
2016
  "type": "string"
1996
2017
  },
1997
2018
  {
1998
- "description": "Read to screen readers when picked up or dropped.",
2019
+ "description": "Read to screen readers when picked up or dropped; a Handle's default accessible name.",
1999
2020
  "name": "label",
2000
2021
  "required": true,
2001
2022
  "type": "string"
@@ -2030,16 +2051,35 @@
2030
2051
  "name": "Root",
2031
2052
  "props": [
2032
2053
  {
2033
- "description": "Called when an item is released over a zone that accepts it. Reordering inside the item's own zone arrives here too, with the new index.",
2054
+ "description": "Called when an item is released over a zone that accepts it (or dropped with Space/Enter). Reordering inside the item's own zone arrives here too, with the new index.",
2034
2055
  "name": "onDrop",
2035
2056
  "required": true,
2036
2057
  "type": "(event: DragDropEvent<T>) => void"
2037
2058
  },
2038
2059
  {
2039
- "description": "Overrides the default screen-reader announcements.",
2060
+ "description": "Called whenever the target under the drag changes — a new zone or index, nothing, or a refusal.",
2061
+ "name": "onDragOver",
2062
+ "required": false,
2063
+ "type": "(event: DragDropOverEvent<T>) => void"
2064
+ },
2065
+ {
2066
+ "description": "Supplies every live-region string (pick, move, drop, cancel, refuse), e.g. from the app's message catalog. Defaults to English.",
2040
2067
  "name": "announce",
2041
2068
  "required": false,
2042
- "type": "(event: {kind: 'pick' | 'drop' | 'cancel'; item: string; zone?: string}) => string"
2069
+ "type": "(event: DragDropAnnouncement) => string"
2070
+ },
2071
+ {
2072
+ "description": "What marks the insertion point in a zone while dragging; a Zone's own renderPlaceholder wins. Without one, only the zone lights up.",
2073
+ "name": "renderPlaceholder",
2074
+ "required": false,
2075
+ "type": "DragDropRenderPlaceholder"
2076
+ },
2077
+ {
2078
+ "default": "DEFAULT_INSTRUCTIONS",
2079
+ "description": "Read by screen readers on a focused Handle.",
2080
+ "name": "instructions",
2081
+ "required": false,
2082
+ "type": "string"
2043
2083
  },
2044
2084
  {
2045
2085
  "name": "children",
@@ -2058,7 +2098,7 @@
2058
2098
  "type": "string"
2059
2099
  },
2060
2100
  {
2061
- "description": "Read to screen readers when an item is dropped here.",
2101
+ "description": "Read to screen readers when an item moves or drops here; the list's accessible name with `list`.",
2062
2102
  "name": "label",
2063
2103
  "required": true,
2064
2104
  "type": "string"
@@ -2071,10 +2111,53 @@
2071
2111
  "type": "'horizontal' | 'vertical'"
2072
2112
  },
2073
2113
  {
2074
- "description": "Refuse an item; the zone then never lights up for it.",
2114
+ "description": "Take the item (`true`), let it pass to an outer zone (`false`), or refuse it with a reason that is shown and announced (`{ok: false, reason}`). Omitted: every item is taken.",
2075
2115
  "name": "accepts",
2076
2116
  "required": false,
2077
- "type": "(item: DragDropItemData<T>) => boolean"
2117
+ "type": "(item: DragDropItemData<T>) => DragDropVerdict"
2118
+ },
2119
+ {
2120
+ "description": "Keyboard order across zones (Left/Right); zones without one follow, in DOM order.",
2121
+ "name": "order",
2122
+ "required": false,
2123
+ "type": "number"
2124
+ },
2125
+ {
2126
+ "description": "Marks the insertion point in this zone; overrides the Root's.",
2127
+ "name": "renderPlaceholder",
2128
+ "required": false,
2129
+ "type": "DragDropRenderPlaceholder"
2130
+ },
2131
+ {
2132
+ "default": "false",
2133
+ "description": "Render the zone as a labelled list (`role=\"list\"`) laid out along `orientation`, its Items as list items. Put the Items directly in it.",
2134
+ "name": "list",
2135
+ "required": false,
2136
+ "type": "boolean"
2137
+ },
2138
+ {
2139
+ "default": "'sm'",
2140
+ "description": "Gap between the Items of a `list` zone.",
2141
+ "name": "gap",
2142
+ "required": false,
2143
+ "type": "SpacingToken",
2144
+ "union": [
2145
+ "xs",
2146
+ "sm",
2147
+ "ms",
2148
+ "md",
2149
+ "lg",
2150
+ "xl",
2151
+ "xxl",
2152
+ "xxxl"
2153
+ ]
2154
+ },
2155
+ {
2156
+ "default": "false",
2157
+ "description": "Stretch to fill the parent (a Timeline bar, a card) instead of sizing to the content.",
2158
+ "name": "fill",
2159
+ "required": false,
2160
+ "type": "boolean"
2078
2161
  },
2079
2162
  {
2080
2163
  "name": "children",
@@ -3899,6 +3982,78 @@
3899
3982
  "props": null,
3900
3983
  "sourcePath": "packages/ui/src/components/Menu/Menu.tsx"
3901
3984
  },
3985
+ "Meter": {
3986
+ "importPath": "@duro-app/ui",
3987
+ "kind": "component",
3988
+ "meta": {
3989
+ "description": "A scalar inside a known range — WIP 2 of 3, a milestone 40% done — as role=\"meter\" with a value text, drawn as a thin bar (the meterH token). The label is the accessible name; valueText says the value in words.",
3990
+ "example": "<Meter label=\"In progress WIP\" value={2} max={3} valueText=\"2 of 3\" />\n<Meter label=\"Milestone done\" value={0.4} tone=\"success\" />",
3991
+ "relatedTo": [
3992
+ {
3993
+ "component": "Badge",
3994
+ "kind": "contrast",
3995
+ "relationship": "Badge states a status or a count; Meter shows a level within a range"
3996
+ }
3997
+ ],
3998
+ "whenNotToUse": [
3999
+ "Progress of a task that is still running — use Spinner (or a determinate progress pattern)",
4000
+ "Picking a value — use an input; a meter has no thumb",
4001
+ "A status with no range (ok/failing) — use Badge or StatusIcon"
4002
+ ],
4003
+ "whenToUse": [
4004
+ "A level against a limit or a total: WIP against its limit, tasks done in a milestone, quota used",
4005
+ "Beside a count, when the proportion matters more than the number"
4006
+ ]
4007
+ },
4008
+ "name": "Meter",
4009
+ "props": [
4010
+ {
4011
+ "name": "value",
4012
+ "required": true,
4013
+ "type": "number"
4014
+ },
4015
+ {
4016
+ "default": "0",
4017
+ "description": "Defaults to 0.",
4018
+ "name": "min",
4019
+ "required": false,
4020
+ "type": "number"
4021
+ },
4022
+ {
4023
+ "default": "1",
4024
+ "description": "Defaults to 1, so a fraction needs nothing else.",
4025
+ "name": "max",
4026
+ "required": false,
4027
+ "type": "number"
4028
+ },
4029
+ {
4030
+ "description": "The accessible name, e.g. \"In progress WIP\".",
4031
+ "name": "label",
4032
+ "required": true,
4033
+ "type": "string"
4034
+ },
4035
+ {
4036
+ "description": "What screen readers read for the value, e.g. \"4 of 3, over the limit\". Defaults to the percentage.",
4037
+ "name": "valueText",
4038
+ "required": false,
4039
+ "type": "string"
4040
+ },
4041
+ {
4042
+ "default": "'accent'",
4043
+ "description": "The fill's colour; defaults to accent.",
4044
+ "name": "tone",
4045
+ "required": false,
4046
+ "type": "MeterTone",
4047
+ "union": [
4048
+ "accent",
4049
+ "success",
4050
+ "warning",
4051
+ "error"
4052
+ ]
4053
+ }
4054
+ ],
4055
+ "sourcePath": "packages/ui/src/components/Meter/Meter.tsx"
4056
+ },
3902
4057
  "Node": {
3903
4058
  "importPath": "@duro-app/diagrams",
3904
4059
  "kind": "component",
@@ -6617,11 +6772,216 @@
6617
6772
  ],
6618
6773
  "sourcePath": "packages/ui/src/components/ThemeProvider/ThemeProvider.tsx"
6619
6774
  },
6775
+ "Timeline": {
6776
+ "importPath": "@duro-app/ui",
6777
+ "kind": "compound",
6778
+ "meta": {
6779
+ "anatomy": {
6780
+ "required": [
6781
+ "Root",
6782
+ "Row",
6783
+ "Bar"
6784
+ ]
6785
+ },
6786
+ "description": "Rows of date bars against a date axis (milestones, releases, sprints), with a progress fill per bar and a today marker. Dates are calendar days (YYYY-MM-DD, UTC). A bar is a toggle button (selected/onSelect), can be a DragDrop.Zone target (dropZone), and its edges are keyboard sliders whose value text is a date (onStartChange/onEndChange).",
6787
+ "example": "<Timeline.Root aria-label=\"Milestones\" start=\"2026-09-21\" end=\"2026-11-08\" today=\"2026-10-08\">\n {milestones.map((m) => (\n <Timeline.Row key={m.id} label={m.title} description={`${m.done} of ${m.total} done`}>\n <Timeline.Bar\n label={m.title}\n start={m.start}\n end={m.end}\n progress={m.done / m.total}\n selected={selected === m.id}\n onSelect={() => select(m.id)}\n onEndChange={(end) => reschedule(m.id, {end})}\n dropZone={{id: `ms:${m.id}`, label: m.title}}\n >\n {m.done}/{m.total}\n </Timeline.Bar>\n </Timeline.Row>\n ))}\n</Timeline.Root>",
6788
+ "relatedTo": [
6789
+ {
6790
+ "component": "DragDrop",
6791
+ "kind": "composition",
6792
+ "relationship": "Pass dropZone to make a bar a DragDrop.Zone inside a DragDrop.Root"
6793
+ },
6794
+ {
6795
+ "component": "Meter",
6796
+ "kind": "contrast",
6797
+ "relationship": "Meter shows one level; Timeline places spans of time"
6798
+ }
6799
+ ],
6800
+ "whenNotToUse": [
6801
+ "A schedule by hour — a calendar grid fits (timeGutterW, dayHeaderH)",
6802
+ "A dependency graph or a full Gantt chart with links — this draws bars, not edges",
6803
+ "Moving dates with the pointer — edges are keyboard sliders; give a date field for the rest"
6804
+ ],
6805
+ "whenToUse": [
6806
+ "Milestones or releases over weeks, where the overlap and today matter",
6807
+ "Dropping work onto a milestone (bars as DragDrop zones)"
6808
+ ]
6809
+ },
6810
+ "name": "Timeline",
6811
+ "parts": {
6812
+ "Bar": {
6813
+ "name": "Bar",
6814
+ "props": [
6815
+ {
6816
+ "name": "start",
6817
+ "required": true,
6818
+ "type": "TimelineDate"
6819
+ },
6820
+ {
6821
+ "name": "end",
6822
+ "required": true,
6823
+ "type": "TimelineDate"
6824
+ },
6825
+ {
6826
+ "description": "The bar's accessible name.",
6827
+ "name": "label",
6828
+ "required": true,
6829
+ "type": "string"
6830
+ },
6831
+ {
6832
+ "description": "Done fraction, 0–1, drawn as a fill from the start edge.",
6833
+ "name": "progress",
6834
+ "required": false,
6835
+ "type": "number"
6836
+ },
6837
+ {
6838
+ "default": "'default'",
6839
+ "description": "`planned` draws a dashed outline; `done` the success tone.",
6840
+ "name": "tone",
6841
+ "required": false,
6842
+ "type": "'default' | 'planned' | 'done'"
6843
+ },
6844
+ {
6845
+ "description": "Makes the bar a toggle button (aria-pressed).",
6846
+ "name": "selected",
6847
+ "required": false,
6848
+ "type": "boolean"
6849
+ },
6850
+ {
6851
+ "name": "onSelect",
6852
+ "required": false,
6853
+ "type": "() => void"
6854
+ },
6855
+ {
6856
+ "description": "Adds the start-edge slider.",
6857
+ "name": "onStartChange",
6858
+ "required": false,
6859
+ "type": "(date: TimelineDate) => void"
6860
+ },
6861
+ {
6862
+ "description": "Adds the end-edge slider.",
6863
+ "name": "onEndChange",
6864
+ "required": false,
6865
+ "type": "(date: TimelineDate) => void"
6866
+ },
6867
+ {
6868
+ "description": "Names of the edge sliders; default \"<label>, start\" / \"<label>, end\".",
6869
+ "name": "startLabel",
6870
+ "required": false,
6871
+ "type": "string"
6872
+ },
6873
+ {
6874
+ "name": "endLabel",
6875
+ "required": false,
6876
+ "type": "string"
6877
+ },
6878
+ {
6879
+ "description": "Makes the bar a DragDrop.Zone (needs a DragDrop.Root above).",
6880
+ "name": "dropZone",
6881
+ "required": false,
6882
+ "type": "TimelineBarDropZone<T>"
6883
+ },
6884
+ {
6885
+ "description": "Text inside the bar, e.g. \"4/7\".",
6886
+ "name": "children",
6887
+ "required": false,
6888
+ "type": "ReactNode"
6889
+ }
6890
+ ],
6891
+ "sourcePath": "packages/ui/src/components/Timeline/Timeline.tsx"
6892
+ },
6893
+ "Root": {
6894
+ "name": "Root",
6895
+ "props": [
6896
+ {
6897
+ "description": "First day on the axis.",
6898
+ "name": "start",
6899
+ "required": true,
6900
+ "type": "TimelineDate"
6901
+ },
6902
+ {
6903
+ "description": "Last day on the axis (included).",
6904
+ "name": "end",
6905
+ "required": true,
6906
+ "type": "TimelineDate"
6907
+ },
6908
+ {
6909
+ "description": "Draws the today marker when inside the range.",
6910
+ "name": "today",
6911
+ "required": false,
6912
+ "type": "TimelineDate"
6913
+ },
6914
+ {
6915
+ "description": "The timeline's accessible name.",
6916
+ "name": "aria-label",
6917
+ "required": true,
6918
+ "type": "string"
6919
+ },
6920
+ {
6921
+ "description": "Text above the row labels, e.g. \"Milestones\".",
6922
+ "name": "heading",
6923
+ "required": false,
6924
+ "type": "ReactNode"
6925
+ },
6926
+ {
6927
+ "default": "'Today'",
6928
+ "description": "The today marker's text.",
6929
+ "name": "todayLabel",
6930
+ "required": false,
6931
+ "type": "string"
6932
+ },
6933
+ {
6934
+ "description": "BCP 47 locale for the default date format.",
6935
+ "name": "locale",
6936
+ "required": false,
6937
+ "type": "string"
6938
+ },
6939
+ {
6940
+ "description": "Formats a day for the axis and the edge sliders' value text.",
6941
+ "name": "formatDate",
6942
+ "required": false,
6943
+ "type": "(date: TimelineDate) => string"
6944
+ },
6945
+ {
6946
+ "name": "children",
6947
+ "required": true,
6948
+ "type": "ReactNode"
6949
+ }
6950
+ ],
6951
+ "sourcePath": "packages/ui/src/components/Timeline/Timeline.tsx"
6952
+ },
6953
+ "Row": {
6954
+ "name": "Row",
6955
+ "props": [
6956
+ {
6957
+ "description": "The row's name, shown in the label column.",
6958
+ "name": "label",
6959
+ "required": true,
6960
+ "type": "string"
6961
+ },
6962
+ {
6963
+ "description": "A second line under the label, e.g. \"4 of 7 done\".",
6964
+ "name": "description",
6965
+ "required": false,
6966
+ "type": "ReactNode"
6967
+ },
6968
+ {
6969
+ "name": "children",
6970
+ "required": false,
6971
+ "type": "ReactNode"
6972
+ }
6973
+ ],
6974
+ "sourcePath": "packages/ui/src/components/Timeline/Timeline.tsx"
6975
+ }
6976
+ },
6977
+ "props": null,
6978
+ "sourcePath": "packages/ui/src/components/Timeline/Timeline.tsx"
6979
+ },
6620
6980
  "ToastProvider": {
6621
6981
  "importPath": "@duro-app/ui",
6622
6982
  "kind": "provider",
6623
6983
  "meta": {
6624
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs.",
6984
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
6625
6985
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
6626
6986
  "relatedTo": [
6627
6987
  {
@@ -6643,11 +7003,19 @@
6643
7003
  "whenToUse": [
6644
7004
  "Confirming an action succeeded (e.g. \"Grant revoked\")",
6645
7005
  "Reporting an action failed, with the reason",
6646
- "Reversible actions that benefit from an inline Undo"
7006
+ "Reversible actions that benefit from an inline Undo",
7007
+ "A soft limit crossed but allowed (variant warning), e.g. a column over its WIP limit"
6647
7008
  ]
6648
7009
  },
6649
7010
  "name": "ToastProvider",
6650
7011
  "props": [
7012
+ {
7013
+ "default": "'Dismiss'",
7014
+ "description": "Accessible name of each toast's close button, e.g. from the app's message catalog.",
7015
+ "name": "dismissLabel",
7016
+ "required": false,
7017
+ "type": "string"
7018
+ },
6651
7019
  {
6652
7020
  "name": "children",
6653
7021
  "required": true,
@@ -7419,14 +7787,17 @@
7419
7787
  "kind": "hook",
7420
7788
  "meta": {
7421
7789
  "anatomy": {
7790
+ "optional": [
7791
+ "Handle"
7792
+ ],
7422
7793
  "required": [
7423
7794
  "Root",
7424
7795
  "Zone",
7425
7796
  "Item"
7426
7797
  ]
7427
7798
  },
7428
- "description": "Move items between zones with one pointer — mouse, pen and touch through the same pointer-event path (touch holds briefly, then drags; a moving touch stays a scroll). A ghost follows the pointer, the zone under it lights up, and the Root announces picks and drops to screen readers. Dragging is pointer-only by design: keep a button or remove affordance that does the same thing.",
7429
- "example": "<DragDrop.Root onDrop={({item, target}) => move(item.id, target.zone, target.index)}>\n <DragDrop.Zone id=\"roster\" label=\"Roster\">\n <Cluster gap=\"sm\">\n {people.map((p) => (\n <DragDrop.Item key={p.id} id={p.id} zone=\"roster\" label={p.name} data={p}>\n <Button variant=\"secondary\" size=\"small\" onClick={() => slot(p)}>{p.name}</Button>\n </DragDrop.Item>\n ))}\n </Cluster>\n </DragDrop.Zone>\n <DragDrop.Zone id=\"gates\" label=\"Gates\" orientation=\"horizontal\">\n <Inline gap=\"sm\">\n {gates.map((g) => (\n <DragDrop.Item key={g.id} id={g.id} zone=\"gates\" label={g.name} data={g}>\n <Tag removable onRemove={() => unslot(g)}>{g.name}</Tag>\n </DragDrop.Item>\n ))}\n </Inline>\n </DragDrop.Zone>\n</DragDrop.Root>",
7799
+ "description": "Move items between zones by pointer (mouse, pen and touch through one pointer-event path; touch holds briefly, then drags) or by keyboard (a Handle per item: Space picks up, arrows move, Space drops, Escape cancels). The innermost accepting zone wins, so a zone nested in an Item (a card in a column) takes its own drops; a zone can refuse with a reason that is shown and announced. Placeholders, edge auto-scroll and list-style announcements (through `announce`, for catalog strings) come with it.",
7800
+ "example": "<DragDrop.Root\n onDrop={({item, target}) => move(item.id, target.zone, target.index)}\n renderPlaceholder={({label}) => <Text variant=\"caption\">→ {label}</Text>}\n announce={(e) => t(`board.drag.${e.kind}`, e)}\n>\n {columns.map((col) => (\n <DragDrop.Zone key={col.id} id={col.id} label={col.name} orientation=\"vertical\" list\n accepts={(item) => item.data.kind === 'task' || {ok: false, reason: t('board.attachOnCard')}}>\n {col.cards.map((card) => (\n <DragDrop.Item key={card.id} id={card.id} zone={col.id} label={card.title} data={card}>\n <DragDrop.Handle>\n <DragDrop.Zone id={`attach:${card.id}`} label={card.title}\n accepts={(item) => item.data.kind === 'activity'}>\n <Card>{card.title}</Card>\n </DragDrop.Zone>\n </DragDrop.Handle>\n </DragDrop.Item>\n ))}\n </DragDrop.Zone>\n ))}\n</DragDrop.Root>",
7430
7801
  "relatedTo": [
7431
7802
  {
7432
7803
  "component": "Tag",
@@ -7440,14 +7811,14 @@
7440
7811
  }
7441
7812
  ],
7442
7813
  "whenNotToUse": [
7443
- "As the ONLY way to do something — every drag needs a tap/click equivalent (WCAG 2.5.7); this component does not provide one",
7444
- "Long lists that scroll while dragging — the ghost follows the pointer but zones do not auto-scroll",
7814
+ "As the ONLY way to do something — WCAG 2.5.7 asks for a single-pointer alternative to dragging (a button or menu that moves the item); a Handle adds the keyboard path, not a click one",
7445
7815
  "Sorting tabular data — sort the Table instead",
7446
7816
  "React Native — Item renders its children without drag behaviour there"
7447
7817
  ],
7448
7818
  "whenToUse": [
7449
7819
  "Slotting things into places — people into approval gates, cards into columns, files into folders",
7450
- "Reordering a short row or list when the order is visible and meaningful"
7820
+ "Reordering a short row or list when the order is visible and meaningful",
7821
+ "A board: columns as labelled lists (`list`), cards with a Handle, an attach zone nested in each card"
7451
7822
  ]
7452
7823
  },
7453
7824
  "name": "useDragDrop",
@@ -7484,7 +7855,7 @@
7484
7855
  "importPath": "@duro-app/ui",
7485
7856
  "kind": "hook",
7486
7857
  "meta": {
7487
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs.",
7858
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
7488
7859
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
7489
7860
  "relatedTo": [
7490
7861
  {
@@ -7506,7 +7877,8 @@
7506
7877
  "whenToUse": [
7507
7878
  "Confirming an action succeeded (e.g. \"Grant revoked\")",
7508
7879
  "Reporting an action failed, with the reason",
7509
- "Reversible actions that benefit from an inline Undo"
7880
+ "Reversible actions that benefit from an inline Undo",
7881
+ "A soft limit crossed but allowed (variant warning), e.g. a column over its WIP limit"
7510
7882
  ]
7511
7883
  },
7512
7884
  "name": "useToast",
@@ -9003,6 +9375,10 @@
9003
9375
  {
9004
9376
  "key": "dayHeaderH",
9005
9377
  "value": "46px"
9378
+ },
9379
+ {
9380
+ "key": "timelineLabelW",
9381
+ "value": "180px"
9006
9382
  }
9007
9383
  ],
9008
9384
  "exportName": "sizes",
@@ -9153,7 +9529,7 @@
9153
9529
  "importPath": "@duro-app/tokens/tokens/typography.css"
9154
9530
  }
9155
9531
  },
9156
- "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
9532
+ "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-timeline-label-w: 180px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
9157
9533
  },
9158
9534
  "unions": {
9159
9535
  "AlertVariant": [
@@ -9340,6 +9716,12 @@
9340
9716
  "listMaxH",
9341
9717
  "listMaxHSm"
9342
9718
  ],
9719
+ "MeterTone": [
9720
+ "accent",
9721
+ "success",
9722
+ "warning",
9723
+ "error"
9724
+ ],
9343
9725
  "PageShellMaxWidth": [
9344
9726
  "sm",
9345
9727
  "md",
@@ -9474,7 +9856,8 @@
9474
9856
  "ToastVariant": [
9475
9857
  "success",
9476
9858
  "error",
9477
- "info"
9859
+ "info",
9860
+ "warning"
9478
9861
  ],
9479
9862
  "ToggleSize": [
9480
9863
  "default",