@cosxai/ui 0.25.0 → 1.0.0-alpha.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 (133) hide show
  1. package/CHANGELOG.md +577 -0
  2. package/NOTICE +11 -0
  3. package/README.md +52 -0
  4. package/package.json +33 -7
  5. package/src/fonts.css +20 -0
  6. package/src/index.ts +8 -25
  7. package/src/lib/cn.ts +23 -6
  8. package/src/styles.css +17 -0
  9. package/src/theme.css +132 -0
  10. package/src/tokens/colors.css +112 -0
  11. package/src/tokens/compat.css +36 -0
  12. package/src/tokens/elevation.css +14 -0
  13. package/src/tokens/motion.css +42 -0
  14. package/src/tokens/spacing.css +55 -0
  15. package/src/tokens/typography-cjk.css +42 -0
  16. package/src/tokens/typography.css +81 -0
  17. package/src/actionbar/ActionBar.tsx +0 -837
  18. package/src/actionbar/ActionBarButton.tsx +0 -111
  19. package/src/actionbar/ActionBarMenuGroup.tsx +0 -116
  20. package/src/actionbar/ActionBarModeHandle.tsx +0 -171
  21. package/src/actionbar/ActionBarProvider.tsx +0 -91
  22. package/src/actionbar/actionbar-context.ts +0 -45
  23. package/src/actionbar/index.ts +0 -28
  24. package/src/actionbar/types.ts +0 -147
  25. package/src/actionbar/useActionBarItems.ts +0 -60
  26. package/src/actionbar/useActionBarMode.ts +0 -144
  27. package/src/actionbar/useActionBarPanel.ts +0 -56
  28. package/src/actionbar/useActionBarStatusDot.ts +0 -49
  29. package/src/actionbar/useActionBarToast.ts +0 -33
  30. package/src/ambient/AmbientBackdrop.tsx +0 -74
  31. package/src/ambient/CommandInput.tsx +0 -107
  32. package/src/ambient/SuperbarStrip.tsx +0 -36
  33. package/src/ambient/index.ts +0 -6
  34. package/src/bento/BentoCell.tsx +0 -66
  35. package/src/bento/BentoGrid.tsx +0 -42
  36. package/src/bento/index.ts +0 -2
  37. package/src/command/CommandPalette.tsx +0 -329
  38. package/src/command/CommandProvider.tsx +0 -57
  39. package/src/command/command-context.ts +0 -12
  40. package/src/command/index.ts +0 -6
  41. package/src/command/rank.ts +0 -50
  42. package/src/command/types.ts +0 -37
  43. package/src/command/useCommandSource.ts +0 -37
  44. package/src/dialogs/DialogsProvider.tsx +0 -216
  45. package/src/dialogs/Modal.tsx +0 -260
  46. package/src/dialogs/Toast.tsx +0 -85
  47. package/src/dialogs/dialogs-context.ts +0 -6
  48. package/src/dialogs/index.ts +0 -10
  49. package/src/dialogs/types.ts +0 -37
  50. package/src/dialogs/useDialogs.ts +0 -8
  51. package/src/editorial/EditorialSpotlight.tsx +0 -63
  52. package/src/editorial/Folio.tsx +0 -52
  53. package/src/editorial/PlateMarker.tsx +0 -33
  54. package/src/editorial/RomanSection.tsx +0 -65
  55. package/src/editorial/RunningMarginalia.tsx +0 -65
  56. package/src/editorial/index.ts +0 -10
  57. package/src/hooks/index.ts +0 -5
  58. package/src/hooks/useKeyboardHotkey.ts +0 -93
  59. package/src/hooks/useReducedMotion.ts +0 -20
  60. package/src/hooks/useViewport.ts +0 -61
  61. package/src/layout/Breadcrumb.tsx +0 -74
  62. package/src/layout/LeftNavRail.tsx +0 -139
  63. package/src/layout/MobileTabBar.tsx +0 -101
  64. package/src/layout/NavItem.tsx +0 -128
  65. package/src/layout/NavSearchTrigger.tsx +0 -88
  66. package/src/layout/NavSection.tsx +0 -40
  67. package/src/layout/RightSidebarPanel.tsx +0 -111
  68. package/src/layout/Shell.tsx +0 -91
  69. package/src/layout/SidePanel.tsx +0 -316
  70. package/src/layout/StickyBanner.tsx +0 -83
  71. package/src/layout/Topbar.tsx +0 -68
  72. package/src/layout/index.ts +0 -24
  73. package/src/layout/useNavRailState.ts +0 -69
  74. package/src/lib/time-utils.ts +0 -44
  75. package/src/neobrutalism/Marquee.tsx +0 -81
  76. package/src/neobrutalism/Sticker.tsx +0 -71
  77. package/src/neobrutalism/index.ts +0 -4
  78. package/src/primitives/Avatar.tsx +0 -53
  79. package/src/primitives/Button.tsx +0 -67
  80. package/src/primitives/Card.tsx +0 -41
  81. package/src/primitives/Checkbox.tsx +0 -87
  82. package/src/primitives/Chip.tsx +0 -191
  83. package/src/primitives/Combobox.tsx +0 -507
  84. package/src/primitives/CopyField.tsx +0 -139
  85. package/src/primitives/CountBadge.tsx +0 -50
  86. package/src/primitives/Input.tsx +0 -204
  87. package/src/primitives/Kbd.tsx +0 -45
  88. package/src/primitives/MentionCombobox.tsx +0 -612
  89. package/src/primitives/MultiCombobox.tsx +0 -327
  90. package/src/primitives/PageHeader.tsx +0 -77
  91. package/src/primitives/Radio.tsx +0 -131
  92. package/src/primitives/SegmentedControl.tsx +0 -135
  93. package/src/primitives/Select.tsx +0 -779
  94. package/src/primitives/SignaturePad.tsx +0 -461
  95. package/src/primitives/Tag.tsx +0 -56
  96. package/src/primitives/Textarea.tsx +0 -62
  97. package/src/primitives/ToggleSwitch.tsx +0 -79
  98. package/src/primitives/Tooltip.tsx +0 -213
  99. package/src/primitives/index.ts +0 -53
  100. package/src/pwa/InstallPromptBanner.tsx +0 -132
  101. package/src/pwa/index.ts +0 -4
  102. package/src/pwa/manifest.template.json +0 -20
  103. package/src/pwa/registerSW.ts +0 -55
  104. package/src/riso/Halftone.tsx +0 -85
  105. package/src/riso/Misregister.tsx +0 -63
  106. package/src/riso/RisoStamp.tsx +0 -76
  107. package/src/riso/index.ts +0 -3
  108. package/src/sketch/HandUnderline.tsx +0 -53
  109. package/src/sketch/RoughArrow.tsx +0 -91
  110. package/src/sketch/RoughBox.tsx +0 -73
  111. package/src/sketch/StickyNote.tsx +0 -56
  112. package/src/sketch/index.ts +0 -4
  113. package/src/styles/base.css +0 -186
  114. package/src/styles/chrome-ambient.css +0 -235
  115. package/src/styles/chrome-bento.css +0 -192
  116. package/src/styles/chrome-editorial.css +0 -168
  117. package/src/styles/chrome-neobrutalism.css +0 -326
  118. package/src/styles/chrome-riso.css +0 -339
  119. package/src/styles/chrome-sketch.css +0 -362
  120. package/src/styles/chrome-swiss.css +0 -265
  121. package/src/styles/chrome-terminal.css +0 -272
  122. package/src/styles/fonts.css +0 -1034
  123. package/src/styles/index.css +0 -343
  124. package/src/styles/tokens.css +0 -941
  125. package/src/terminal/AsciiBox.tsx +0 -65
  126. package/src/terminal/BrailleSpinner.tsx +0 -46
  127. package/src/terminal/index.ts +0 -4
  128. package/src/theme/ThemeProvider.tsx +0 -99
  129. package/src/theme/index.ts +0 -6
  130. package/src/theme/inline-script.ts +0 -40
  131. package/src/theme/theme-context.ts +0 -7
  132. package/src/theme/types.ts +0 -39
  133. package/src/theme/useTheme.ts +0 -8
package/CHANGELOG.md ADDED
@@ -0,0 +1,577 @@
1
+ ## 1.0.0-alpha.0 (2026-09-29)
2
+
3
+ **A new system — breaking for every 0.x consumer.** 0.x continues on the
4
+ `ui-0.x` branch (dist-tag `v0`); stay on `^0.25` until you move.
5
+
6
+ - COSX Design System 3.0 foundations: the 3.0 token files unchanged
7
+ (`styles.css`), Geist + Noto Sans SC self-hosted (Fontsource, OFL-1.1),
8
+ ink mode via `data-mode="ink"` / `.ink-mode`.
9
+ - Tailwind v4 theme (`theme.css`): 3.0 colours, radii, type scale,
10
+ tracking and easings as utilities; Tailwind's own palette, shadows and
11
+ easings removed.
12
+ - `cn()` — clsx + tailwind-merge taught the kit's size names.
13
+ - Removed: every 0.x component, the `--ck-*` variables and the design
14
+ presets (editorial, neobrutalism, riso, sketch, terminal, ambient,
15
+ bento, pwa). Components return in the next alphas on Radix.
16
+
17
+ ## 0.25.0 (2026-09-08)
18
+
19
+ - **fix(primitives)**: `Chip` no longer steals focus on mousedown — the
20
+ label button and the × button call `preventDefault()` in
21
+ `onMouseDown` (the action stays on `onClick`). A host with an inline
22
+ editor open for another chip no longer sees the editor blur (commit +
23
+ unmount) swallow the first click.
24
+ - **feat(primitives)**: `MultiCombobox` gains `layout?: "stacked" |
25
+ "inline"` (default `stacked` — 0.24.0 behaviour unchanged). `inline`
26
+ is a mail-client "To" field: one Input-like frame
27
+ (`.ck-multi-combobox-field`, `:focus-within` ring, `:has([aria-invalid])`
28
+ critical border + revealed `invalidHint` line, `:has(:disabled)` dim)
29
+ whose `<ul role="list">` is the flex-wrap row — chips as `<li>`s, the
30
+ bare search input in a last presentational `<li>` — so typing
31
+ continues after the last chip and wraps with it; the label renders
32
+ above as an eyebrow, the dropdown spans the frame, the invalid hint is
33
+ `aria-describedby`-linked to the input, editor + hint stay below.
34
+ Clicking the frame's empty area focuses the input. Testids unchanged
35
+ (`-entries` is the `<ul>` inside the frame); new `${testid}-field` on
36
+ the frame.
37
+ - **feat(primitives)**: `Combobox` gains `bare?: boolean` (input without
38
+ its own field chrome, root `position: static` + `flex: 1 1 140px` so a
39
+ host-drawn `position: relative` frame anchors the dropdown; no label /
40
+ error text rendered), `inputId?: string` (for a host `<label
41
+ htmlFor>`) and `inputDescribedBy?: string` (merged into the input's
42
+ `aria-describedby`). `Input` gains `bare?: boolean` (only the `<input
43
+ class="ck-input ck-input--bare">`, no wrapper / label / helper / error
44
+ / chrome; a boosted-specificity rule in `styles/index.css` strips the
45
+ chrome presets' `!important` input styling). `Input` now spreads
46
+ `...rest` before its `error`-derived `aria-invalid` / `aria-describedby`
47
+ (so `error` stays authoritative; a consumer `aria-describedby` is
48
+ merged with the helper/error id rather than replaced).
49
+ - **fix(combobox)**: the 150 ms blur timer no longer closes the list /
50
+ auto-commits when the input has been re-focused within the delay
51
+ (e.g. a host frame click handing focus straight back).
52
+
53
+ ## 0.24.0 (2026-09-08)
54
+
55
+ - **feat(combobox)**: `clearOnCommit?: boolean` — after `onCommit`
56
+ fires, the input resets to "" and the dropdown closes instead of
57
+ echoing the committed text (multi-value mode; default false, the
58
+ single-value behaviour is unchanged). `Combobox` is now
59
+ `forwardRef<ComboboxHandle>` with `ComboboxHandle = { focus(); clear() }`
60
+ (`clear` for parents rejecting a duplicate commit).
61
+ - **feat(primitives)**: New `<Chip>` — pill for one selected value
62
+ (recipient, filter, token): `--ck-bg-muted` slab, 13px sans, tones
63
+ `neutral` / `accent` / `warning` / `critical`, `selected`, `disabled`,
64
+ optional `onClick` (label becomes a button) and `onRemove` (× button,
65
+ `removeLabel` aria-label, default "Remove"). Root
66
+ `<span class="ck-chip" data-ck-chip data-tone data-selected data-disabled>`
67
+ is the styling hook for chrome presets — no per-chrome CSS in this
68
+ release. Deliberately NOT `ck-tag` (the uppercase mono status label).
69
+ - **feat(primitives)**: New `<MultiCombobox>` — `Combobox` in
70
+ `clearOnCommit` mode + a `<ul role="list">` of `Chip`s + an optional
71
+ inline editor slot (`renderEntryEditor`, mounts under the row for the
72
+ entry with `editing: true`) + a `hint` line. Backspace in the EMPTY
73
+ search input removes the last entry (`backspaceRemovesLast`, default
74
+ true; scoped to the embedded input so editors are unaffected). Value is
75
+ parent-owned via `entries` / `onCommit` / `onRemoveEntry`; paste /
76
+ comma splitting is the consumer's job inside `onCommit`. Forwards
77
+ `MultiComboboxHandle` (= `ComboboxHandle`). Testids:
78
+ `${testid}-combobox-input` / `-dropdown` / `-free-entry`,
79
+ `${testid}-entries`, `${testid}-entry-${key}`, `${testid}-editor`.
80
+ - **feat(layout)**: `LeftNavRail` gains `footerTop?: ReactNode` — a
81
+ block pinned between the scrolling nav body and the footer rule,
82
+ always visible above the divider. Graduated from product-meta's local
83
+ pnpm patch (its agent drawer trigger), which meta can now drop.
84
+
85
+ ## 0.23.2 (2026-08-19)
86
+
87
+ - **fix(hooks)**: `useKeyboardHotkey` calls `e.preventDefault()` once
88
+ every guard passes — a hotkey that opens a dialog with an
89
+ autofocused input no longer sees its own keystroke inserted as text.
90
+
91
+ ## 0.21.0 (2026-08-10)
92
+ - **feat(dialogs)**: `Modal` gains `phonePresentation?: "center" | "page" | "sheet"` — how the modal presents on PHONE viewports (no effect elsewhere). `"page"` renders full-screen and slides in from the right like a pushed native page (for content dialogs the user enters to work in); `"sheet"` rises from the bottom edge (light pickers / short forms); `"center"` (default) keeps today's card. New full-distance motion primitives `ck-anim-page-push` / `ck-anim-sheet-up` back it, both killed under `prefers-reduced-motion`. Backward compatible: omit the prop and nothing changes. Graduated from product-meta's matter split dialogs (fact detail / evidence ruling), whose in-dialog "document takes over + Back" phone flow nests naturally inside a pushed page.
93
+
94
+ ## 0.23.1 (2026-08-19)
95
+
96
+ - **fix(select)**: `fit="auto"` triggers size to the WIDEST option
97
+ (invisible same-cell sizer) instead of the current selection — the
98
+ popover matches trigger width, so long options no longer truncate
99
+ after picking a short one.
100
+ ## 0.23.0 (2026-08-19)
101
+
102
+ - **feat(combobox)**: `searchOnFocus?: boolean` — run `search("")` on
103
+ focus so clicking the field presents the option list before any
104
+ typing (select-with-search for small known sets). Off by default.
105
+ ## 0.22.0 (2026-08-19)
106
+
107
+ - SegmentedControl: unfilled track — hairline border delimits the
108
+ group, the raised selected segment carries the contrast. The
109
+ muted-filled track read as a heavy block on warm canvases.
110
+ ## 0.20.0 (2026-07-28)
111
+ - **feat(command)**: `CommandItem` gains an optional `disabled?: boolean`. A disabled row still renders (dimmed) but is skipped by arrow-key nav, never auto-selected (the default highlight lands on the first enabled row), ignores hover/click, and `run` never fires on it. For "coming soon" teasers — distinct from `secret`, which hides the row entirely. Backward compatible: omit it and rows behave exactly as before.
112
+
113
+ ## 0.19.0 (2026-07-28)
114
+ - **feat(command)**: `<CommandPalette>` gains an optional `onQueryChange?: (query: string) => void` prop — fires on every keystroke and on the reset-to-empty when the palette opens. Lets a consumer drive an ASYNC command source (debounce → fetch → register results via `useCommandSource`) that the built-in client-side filter can't provide. Backward compatible: omit it and the palette behaves exactly as before. Unblocks product-meta's global document search in the ⌘K palette.
115
+
116
+ ## 0.18.1 (2026-07-23)
117
+ - fix(hooks): useKeyboardHotkey honors `data-hotkey-passthrough="true"` containers — hotkeys stay live when focus sits in an editable the user never types into (canvas spreadsheet focus traps)
118
+
119
+ # Changelog
120
+
121
+ ## 0.13.0 (2026-07-17)
122
+
123
+ - **feat(primitives)**: New `<CopyField>` — read-only value field with an embedded Copy button inside the frame (mirrors `Input`'s suffix-addon chrome: 36px height, muted slab, hairline divider). Value renders mono + ellipsized; Copy flips to "Copied ✓" for a beat (`copiedForMs`, default 1500ms) and writes via the Clipboard API with a silent select-the-text fallback for non-secure origins. For share links, signing URLs, tokens, API keys — anywhere the next action is overwhelmingly "copy this". Graduated from product-meta's Signing tab where the URL box + separate COPY button read as two disconnected controls.
124
+
125
+ ## 0.11.0 (2026-07-14)
126
+
127
+ - **feat(layout)**: New `<SidePanel>` — right-docked, backdrop-less panel that slides in from the right edge and pushes main content left via `--ck-sidepanel-width` (stamped on `:root` while open). For editors, activity feeds, share managers, and other admin surfaces where the reader should keep interacting with the main content while the panel is open (Notion / Linear / Slack right-panel UX). Distinct from `<RightSidebarPanel>` (scoped floating card at edge offset): SidePanel is full-height and reshapes layout via a CSS var; RightSidebarPanel is a smaller ephemeral card. Portal-mounted to `document.body` (escapes transform ancestors), `role="complementary"`, ESC-to-close, focus lands on the close button (no focus trap — main surface stays interactive). Prototyped in product-meta's block_doc editor; graduated after Ben validated the API against the Edit document surface. Layout consumers opt into the push-left by summing `var(--ck-sidepanel-width, 0px)` into their right inset.
128
+
129
+ ## 0.10.4 (2026-07-09)
130
+
131
+ - **fix(actionbar)**: Admin mode is now session-only (was persisted to `localStorage` under `<storageKey>:admin`). Two problems the persistence caused:
132
+ - Page reload → session came back in elevated state without an explicit user opt-in.
133
+ - Cross-doc navigation → user turned admin on for Doc A, opened Doc B, and landed in admin mode carrying nothing but a red-tinted bar (bug Ben spotted on the block_doc viewer).
134
+ - **fix(actionbar)**: Auto-reset admin mode when the set of `adminOnly` item keys changes. Consumers that call `useActionBarItems` with different items on a new route reliably clear the elevated state — no per-page "reset on unmount" wiring needed. Same-page updates (item labels change, hint keys change) don't reset because the *key* set stays constant.
135
+
136
+ ## 0.10.3 (2026-07-09)
137
+
138
+ - **feat(actionbar)**: New `hiddenInAdmin?: boolean` on `ActionBarItem`. Symmetric counterpart to `adminOnly` — pages that want an exclusive "different toolset" UX (block_doc viewer's Manage share + Activity + History replacing Share) mark their regular items `hiddenInAdmin: true` so they clear when the shield toggle flips on. Pages that want the additive layering shape just leave it unset. Per-item flag so different consumers on the same page can pick independently.
139
+
140
+ ## 0.10.2 (2026-07-09)
141
+
142
+ - **fix(actionbar)**: Restore shield glyph on the admin-mode toggle (better semantic weight than the sliders variant tried in 0.10.1). Still borderless / transparent bg — no outer ring, just the icon.
143
+ - **feat(actionbar)**: Softer motion when entering / exiting admin mode. Bar background + border tint now transition on a spring curve (260 ms, `cubic-bezier(0.34, 1.56, 0.64, 1)`), the shield icon picks up a 1.06× scale bump alongside its stroke-colour shift, and admin items animate in with a translate + scale spring on reveal. Toggle-off is instant (React unmount) — future release may add a matching exit transition if needed.
144
+
145
+ ## 0.10.1 (2026-07-09)
146
+
147
+ - **fix(actionbar)**: Admin-mode toggle drops its bordered-circle chrome so the button reads with the same visual weight as neighbouring items (Theme icon, Share icon, etc.). Now: transparent bg, no border, muted stroke by default, accent-coloured icon when active. Glyph swapped from shield → sliders (two tracks with knobs) so the affordance reads as "reveal more controls" instead of "security/protected" — matches what admin mode actually does.
148
+
149
+ ## 0.10.0 (2026-07-09)
150
+
151
+ - **feat(actionbar)**: New `adminOnly?: boolean` on `ActionBarItem`. Marking an item admin-only hides it behind an auto-appearing shield toggle button (rendered between the drag grip and the first item). When active, admin items reveal + the bar picks up a subtle accent-tinted background so the "elevated privileges" state reads at a glance. State persists per `storageKey`. Toggle only renders when at least one registered item has adminOnly=true — a bar with no admin items looks exactly as it always did. Reusable across viewers (block_doc, PDF, etc.) so cross-kind admin surfaces stay consistent.
152
+ - **feat(actionbar)**: Bar background + border animate on admin-mode transition (180 ms ease-out). Accent-mixed 8% opacity in oklab space so the tint reads on both light and dark themes without a full palette override.
153
+
154
+ ## 0.9.0 (2026-07-08)
155
+
156
+ - **feat(actionbar)**: New `ActionBarModeHandle` component + `useActionBarMode` hook for the block_doc viewer's admin peek-out affordance. The handle sits ~6px above the ActionBar's top edge at rest (a slim 44×6 accent-coloured pill) and grows to 68×24 on hover / focus while revealing a label — click swaps the ActionBar's item set between named modes ("viewer" / "manage" / "draft"). `useActionBarMode({ modes, defaultMode, storageKey })` manages the state, persists to localStorage, and registers the active mode's items into the ActionBar registry atomically (previous set unregistered on mode change). Gate the handle behind a capability bit with `visible={can.manage}` — non-privileged principals never see the affordance. Lands with product-mesh M4.5 Phase F; consumer wiring in product-meta comes with Phase G-I.
157
+
158
+ ## 0.7.1 (2026-07-01)
159
+
160
+ - **fix(MentionCombobox)**: Removed `padding: 0 4px` + `font-weight: 500` from the highlight chip. Both added glyph advance to the overlay that the underlying textarea didn't have, so every character after `@Ben Zhang` drifted right of its true position (visible mis-alignment reported at ~8px, matching the chip's horizontal padding). The chip now conveys "highlighted" purely via background + colour + a tight border-radius; overlay and textarea characters occupy identical widths so the caret stays where the user typed it.
161
+
162
+ ## 0.7.0 (2026-07-01)
163
+
164
+ - **feat(MentionCombobox)**: New `mentionNames?: readonly string[]` prop. When provided and non-empty, the primitive draws a mirrored overlay behind the textarea that highlights each `@Name` (matched longest-first, whitespace-bounded) as an accent-tinted chip while the user is still composing. Textarea's own glyphs are hidden with `color: transparent` + `-webkit-text-fill-color: transparent`; `caret-color` keeps the cursor visible. Scroll syncs via a `onScroll` handler so long comments stay aligned. Omit or pass empty to opt out — the primitive renders exactly as before. Consumers typically derive the list from their captured pick-list (product-meta CommentComposer stores `PickedMention[]` for its bracket-form wire serializer and passes `picks.map(p => p.name)` here).
165
+ - **feat(MentionCombobox)**: Export `splitByMentionNames` helper used internally by the overlay — useful for consumers that render the same body outside the composer (e.g. a preview panel).
166
+
167
+ ## 0.6.0 (2026-06-28)
168
+
169
+ - **feat(MentionCombobox)**: New headless generic primitive. Same @-trigger + debounced-search + keyboard-nav behaviour previously in product-meta, hoisted up so mesh + future consumers can share it. Consumers plug in `loadCandidates`, `getItemKey`, `getInsertionText`, `renderItem`. Ref exposes `focus()` for mount-on-open flows.
170
+
171
+ ## 0.5.0 (2026-06-26)
172
+
173
+ - **feat(Button)**: New `loading?: boolean` prop. When true the
174
+ button renders a leading CSS ring spinner (`.ck-btn-spinner`
175
+ inherits `currentColor` so it reads on every variant), is set
176
+ natively `disabled`, and exposes `aria-busy="true"`. Distinct
177
+ from `disabled` — `disabled` means "you can't take this action
178
+ right now", `loading` means "we're already taking this action".
179
+ Wire both together for async submits behind form validation:
180
+ `<Button disabled={!name.trim()} loading={submitting}>`.
181
+ - **refactor(Button)**: `forwardRef` to the underlying `<button>`
182
+ so consumers can wire focus management (e.g. autoFocus on dialog
183
+ mount). Was a Phase 0 stub; aligns with the
184
+ `.claude/rules/code-style.md` `forwardRef-for-all-interactive-
185
+ elements` rule.
186
+
187
+ ## 0.4.11 (2026-06-25)
188
+
189
+ - **fix(ActionBar)**: The keyboard-shortcut `hint` badge stayed
190
+ visible on phone-width viewports even though the matching label
191
+ had already collapsed via `@media (max-width: 767px)`. Result:
192
+ on a phone, the bar read as `[icon] C` instead of just `[icon]`
193
+ — a dangling badge advertising a shortcut nobody can press
194
+ without a keyboard. Tag the hint span with `ck-actionbar-hint`
195
+ and add it to the same `display: none` media rule.
196
+
197
+ ## 0.4.10 (2026-06-24)
198
+
199
+ - **feat(tokens)**: New `--ck-shadow-overlay` token for floating
200
+ surfaces that need to read as "lifted off the page" rather than
201
+ "card on a page". Default light value ~2× the punch of shadow-3
202
+ (32-px blur on the soft layer + 8-px blur on the tight layer);
203
+ per-chrome overrides for dark, editorial light, and dark-editorial
204
+ so the shadow stays legible on charcoal surfaces.
205
+ - **fix(Modal)**: Bump card to `--ck-radius-lg` (12 px) + the new
206
+ `--ck-shadow-overlay`, and stretch slot paddings to a unified
207
+ 24-px gutter (`20px 24px` header, `20px 24px 24px` body,
208
+ `16px 24px` footer). Consumers reported the previous chrome read
209
+ as "another tier of card" — these changes give the modal an
210
+ unambiguous hierarchy step above the page.
211
+
212
+ ## 0.4.9 (2026-06-24)
213
+
214
+ - **fix(Modal)**: Bump backdrop blur from 2 px to 8 px so the page
215
+ surface visibly drops out of focus when a modal opens. The earlier
216
+ value read as "barely there" — consumers cross-referencing
217
+ agent-dataroom (4 px `backdrop-blur-sm`) asked for distinctly
218
+ more separation; 8 px lands on the "the world stopped" side of
219
+ the curve while still letting the surface tint show through.
220
+ Also adds the `-webkit-` prefix so Safari < 18 (and any WebKit
221
+ embed) gets the same effect instead of falling through to no
222
+ blur at all.
223
+
224
+ ## 0.4.8 (2026-06-24)
225
+
226
+ - **fix(chrome-editorial)**: NavItem rows in the LeftNavRail now get a
227
+ hover background like every other chrome. Editorial was the only one
228
+ missing the `[data-ck-navitem]:not([data-active="true"]):hover` rule
229
+ — rail items read as static even though they're navigable, which
230
+ product-meta consumers noticed against the cards / actionbar items
231
+ that DO tint on hover. Sketch / ambient / riso / neobrutalism /
232
+ terminal already had the equivalent rule; this brings editorial up
233
+ to parity using the shared `--ck-bg-muted` token.
234
+ - **fix(Modal)**: ModalHeader's close `×` button picks up a
235
+ `--ck-bg-muted` background on hover + a subtle border-radius. The
236
+ previous styles only set color + cursor, so the corner control
237
+ looked like static decoration. Same hover token + transition as the
238
+ rest of the kit's interactive surfaces so the affordance reads
239
+ consistently across light / dark / chrome variants.
240
+
241
+ ## 0.4.7 (2026-06-24)
242
+
243
+ - **fix(actionbar)**: `useActionBarItems` no longer freezes the
244
+ registered items on first mount when the array length + item
245
+ `key`s are stable. Previously the hook wrapped the caller's
246
+ array in `useMemo(() => items, [items.length, keys.join('|')])`
247
+ to throttle re-registration; that gate suppressed identity
248
+ updates whenever length + keys matched, which is exactly the
249
+ case for a selection-mode toolbar with fixed buttons whose
250
+ `onClick`s close over changing state (e.g. the current selection
251
+ set). The registered items kept their first render's closures,
252
+ so every click after the first shipped stale state to the
253
+ consumer — observed in product-meta as Trash bulk-restore +
254
+ bulk-purge sending only the first-selected id even when the
255
+ user had picked many. The gate is gone; the provider's existing
256
+ shallow item-identity dedup in `register()` is now the single
257
+ source of truth. Consumers MUST `useMemo` their items array
258
+ (already in the JSDoc) — without it, every render allocates new
259
+ item objects and the effect loops `register → setState →
260
+ re-render → register`. The hook docstring now spells out the
261
+ trade-off explicitly.
262
+
263
+ ## 0.4.6 (2026-06-20)
264
+
265
+ - **fix(actionbar)**: ActionBarMenuGroup child-button hover bumps
266
+ from 18% accent blend to 30%. The 18% overlay introduced in 0.4.5
267
+ layered on top of the wrapper's `--ck-accent-muted` (already
268
+ 8–14% accent per chrome) composited to roughly 30% effective
269
+ saturation — at that low saturation a translucent orange / coral /
270
+ blue all wash out to salmon, so the hover lost its hue identity
271
+ next to the solid `--ck-accent` text it sits beside. 30% pushes
272
+ the overlay past the "dilution reads as pink" point so the
273
+ workspace's actual brand colour is recognisable on hover.
274
+
275
+ ## 0.4.5 (2026-06-20)
276
+
277
+ - **fix(actionbar)**: child-button hover state inside an open
278
+ disclosure group now uses an accent-blended overlay instead of
279
+ neutral `--ck-bg-muted` gray. The default `.ck-actionbar-btn:hover`
280
+ rule paints gray; that's correct for buttons sitting on the app
281
+ background, but the disclosure wrapper paints itself
282
+ `--ck-accent-muted` (a coral / indigo pill in editorial / base
283
+ chromes) while open, so child hover was stacking gray on top of
284
+ the brand-tinted pill — visually muddy and the hover signal lost
285
+ its hue. `ActionBarMenuGroup` now stamps a
286
+ `ck-actionbar-group--open` class on its wrapper while expanded;
287
+ a scoped rule overrides the child hover to
288
+ `color-mix(in oklab, var(--ck-accent) 18%, transparent)` so the
289
+ hover stays in the accent family. Closed groups + standalone
290
+ buttons are untouched. Consumers don't need to change anything —
291
+ bump the dep version and the fix lands.
292
+
293
+ ## 0.4.4 (2026-06-15)
294
+
295
+ - **fix(chrome)**: primary-button text colour now reads from
296
+ `var(--ck-accent-fg, <chrome default>)` in editorial / ambient /
297
+ sketch / riso / neobrutalism chromes. Previously every chrome
298
+ hardcoded its text color to pair with its OWN signature accent
299
+ (editorial near-black on coral; ambient white on saturated blue;
300
+ sketch paper-white on sketch-blue; riso near-black on pink;
301
+ neobrutalism black on pastel). When a consumer stamped a runtime
302
+ brand override via `--ck-accent-light-override`, the accent
303
+ background flipped to the brand colour but the text stayed on
304
+ the chrome default — a dark brand colour against a chrome's
305
+ near-black text yielded illegible buttons (e.g. editorial coral
306
+ → `#0F0F0F` text was fine, but brand `#000000` → `#0F0F0F` text
307
+ was invisible). Consumer apps can now compute a contrast-aware
308
+ foreground colour from the chosen accent (e.g. via WCAG relative
309
+ luminance) and stamp `--ck-accent-fg` alongside the override knob,
310
+ and every chrome respects it. Without an override the chrome's
311
+ documented default fg still applies, so no visual change for
312
+ in-the-box usage. Swiss chrome already used `var(--ck-bg-canvas)`
313
+ for primary text (not tied to accent), so it's untouched.
314
+
315
+ ## 0.4.3 (2026-06-15)
316
+
317
+ - **fix(tokens)**: respect the documented `--ck-accent-light-override` /
318
+ `--ck-accent-dark-override` brand-override knob in every chrome that
319
+ hardcoded accent shades. Previously `editorial`, `riso`, and `sketch`
320
+ set `--ck-accent` to a literal hex (and `--ck-accent-hover` / `-active`
321
+ to hand-tuned shades), which silently bypassed the override chain —
322
+ consumer apps stamping their brand colour via the documented mechanism
323
+ saw chrome stay on the platform palette. Each chrome now sets its
324
+ signature colour as the `var(--ck-accent-light-override, <chrome
325
+ default>)` fallback and derives hover/active via `color-mix(in oklab,
326
+ var(--ck-accent), black 10% / 18%)` (matching the default `:root`
327
+ formula). Sketch additionally swaps two `rgba(... blue-literal ...)`
328
+ muted/border values for `color-mix(var(--ck-accent) NN%, transparent)`
329
+ so they track the brand. Net: stamping `--ck-accent-light-override` on
330
+ `documentElement.style` now cascades to the whole accent family across
331
+ every chrome, single source of truth restored. Consumer impact:
332
+ product-meta's `BrandProvider` can drop its accent-family mirror
333
+ workaround (a98324f) and go back to stamping just the override knob.
334
+
335
+ ## 0.4.2 (2026-06-06)
336
+
337
+ - **fix(input)**: add `minWidth: 0` to the `.ck-input-field` wrapper so it
338
+ can shrink past its inner `.ck-input-addon-wrap`'s nowrap suffix when
339
+ hosted inside a flex/grid parent. Long suffixes (e.g. `.meta.test.cosx.dev`)
340
+ previously set a min-content floor that pushed the field past mobile
341
+ viewports — product-meta's onboarding (`CreateWorkspace`, `ActivatePersonal`)
342
+ and the workspace-name form on Landing all overflowed on iPhone Pro
343
+ widths. Per-page CSS workarounds in consumer apps only covered one
344
+ shell scope; fixing it at the primitive catches every page.
345
+ - **fix(input)**: real disabled visual on `.ck-input` / `.ck-textarea` —
346
+ `opacity: 0.55`, `cursor: not-allowed`, muted background. Mirrors the
347
+ existing `.ck-btn:disabled` treatment. Disabled inputs on a cream /
348
+ dark canvas were previously almost indistinguishable from editable
349
+ ones (the QA report flagged this for product-meta's Profile Email
350
+ field which is read-only pending the email-change verification flow).
351
+ The `.ck-input-addon-wrap:has(:disabled)` selector dims the suffix /
352
+ prefix along with the input so a disabled `slug + suffix` stack reads
353
+ as one disabled unit. `:has()` is supported across all evergreen
354
+ browsers since 2023.
355
+
356
+ ## 0.4.1 (2026-06-01)
357
+
358
+ - **feat(fonts)**: add Noto Serif SC to the editorial-chrome `--ck-font-serif`
359
+ fallback stack so CJK names render in a serif matching Playfair Display
360
+ rather than the system *sans-serif* CJK fallback (PingFang SC / SimSun) the
361
+ browser would otherwise pick. Names like "本杰明 Zoë" now read as one
362
+ coherent typographic line cross-platform without depending on the user
363
+ having Songti SC / Source Han Serif SC installed locally.
364
+
365
+ Bandwidth shape: Noto Serif SC ships from Google Fonts as ~100
366
+ unicode-range subsets. Pure-Latin pages pay only the CSS file (~5-10KB
367
+ gzip) — no .woff2 binaries fetch. Pages with Chinese names pay an
368
+ additional ~200-400KB of CJK subset binaries (only the ranges they use).
369
+ Sans + mono slots intentionally stay system-only — PingFang SC /
370
+ Microsoft YaHei is what users expect for UI body copy.
371
+
372
+ ## 0.4.0 (2026-06-01)
373
+
374
+ - **feat(fonts)**: load Geist + Geist Mono + Playfair Display + Caveat from
375
+ Google Fonts CDN instead of the prior self-hosted `@font-face` blocks.
376
+ Closes a long-standing "Failed to decode downloaded font: …/fonts/Geist-Regular.otf"
377
+ warning that fired on every page load in consumer SPAs that didn't ship
378
+ the OTF files under their `/fonts/` route (the Cloudflare SPA fallback
379
+ was returning index.html with `Content-Type: text/html` and the browser's
380
+ font decoder was rejecting it). Consumers no longer need to provision
381
+ `public/fonts/` themselves.
382
+ - **feat(fonts)**: append CJK system-font fallbacks to `--ck-font-sans` /
383
+ `--ck-font-mono` / `--ck-font-serif`. Names containing 中文 / 日本語 /
384
+ 한국어 (e.g. "本杰明 Zoë") now render in the matching serif/sans
385
+ system font (PingFang SC on macOS, Microsoft YaHei on Windows, etc.)
386
+ instead of dropping into the browser's last-resort glyph. No new web
387
+ font is loaded — every modern OS already ships at least one of the
388
+ listed CJK families.
389
+
390
+ ## 0.3.4 (2026-05-31)
391
+
392
+ - **fix(actionbar)**: bar now has symmetric horizontal padding
393
+ (`0 6px` instead of `0 6px 0 0`). Previously the right-only padding
394
+ pushed the leading items a few pixels left of the bar's true
395
+ centre — the grip touched the left curve while the rightmost
396
+ element (now the status dot) had visible breathing room. With
397
+ 0.3.3's leading spacer the imbalance was small but visible.
398
+ - **fix(actionbar)**: leading + trailing spacers now both use
399
+ `minWidth: 0` instead of `0` and `12` respectively. Cosmetic
400
+ alignment — `0` is the right neutral value for a spacer that's
401
+ expected to grow into available room rather than enforce a
402
+ minimum gap.
403
+
404
+ ## 0.3.3 (2026-05-31)
405
+
406
+ - **fix(actionbar)**: leading items centre between the grip and the
407
+ status dot when no trailing items are present. Previously a solo
408
+ leading item (e.g. "Theme · Light") visually packed next to the
409
+ grip leaving an unbalanced gap before the status dot. Now a
410
+ balancing flex spacer is inserted to the LEFT of the leading
411
+ group whenever it's the only content + the right side holds only
412
+ a status dot. When trailing items ARE present, they retain their
413
+ right-anchor role and leading goes back to natural left packing.
414
+
415
+ ## 0.3.2 (2026-05-31)
416
+
417
+ - **fix(actionbar)**: bar now renders when the only consumer is
418
+ `useActionBarStatusDot` (no items registered). The empty-state
419
+ guard previously checked `items.length === 0` only, so a
420
+ status-dot-only surface would render nothing.
421
+
422
+ ## 0.3.1 (2026-05-31)
423
+
424
+ - **feat(actionbar)**: bar-intrinsic `statusDot` slot at the right
425
+ edge, mirroring the left-edge drag grip. Registered via
426
+ `useActionBarStatusDot({color, title?, onClick?, pulse?} | null)`.
427
+ Unlike `useActionBarItems`, the status dot is system chrome — not
428
+ page-level content — so the API is a single hook with last-call-
429
+ wins semantics (no source-key fan-out). Driven by `product-meta`
430
+ needing a fixed sync indicator that visually anchors to the bar
431
+ rather than registering as a registry item (the trailing slot
432
+ worked but conflated system status with page actions). Exports
433
+ `ActionBarStatusDot` type + `useActionBarStatusDot` hook.
434
+
435
+ ## 0.3.0 (2026-05-31)
436
+
437
+ - **feat(actionbar)**: `ActionBarItem` gains a `slot?: 'leading' |
438
+ 'trailing'` field. Trailing items render after a flex spacer so
439
+ they pin to the right edge of the bar regardless of registration
440
+ order — system status indicators (sync, identity, connection)
441
+ belong here, where page items registering later can't shuffle
442
+ them. Default `'leading'` preserves existing behavior; this is a
443
+ purely additive change. Exports `ActionBarItemSlot` from the
444
+ bucket index. Driven by product-meta needing a stable home for
445
+ the SWR sync-status indicator on dash AND inside workspace SPAs.
446
+
447
+ ## 0.2.10 (2026-05-30)
448
+
449
+ - **fix**: Add `text-decoration: none` to `.ck-btn` so `<a class="ck-btn">`
450
+ CTAs render flat instead of inheriting the browser's default
451
+ anchor underline. Consumers using Tailwind preflight had this
452
+ masked because Tailwind resets `<a>` decoration globally; mesh's
453
+ embedded auth pages (no Tailwind) surfaced the underline on the
454
+ verify-email success page's "Continue to [Product] →" anchor.
455
+
456
+ ## 0.2.9 (2026-05-30)
457
+
458
+ - **fix**: Add a global `*, *::before, *::after { box-sizing: border-box }`
459
+ reset to `base.css`. Every modern CSS reset ships this; without it,
460
+ `min-height: 100vh` + padding extends elements beyond the viewport
461
+ (default `content-box` stacks padding on top of the declared min-height)
462
+ and creates a sneaky scroll on any consumer page that combines those
463
+ two properties. Caught in mesh's `body.mesh-auth-page` where a green
464
+ flash alert pushed a reset-password page slightly past 100vh and the
465
+ whole page became scrollable. Consumers using Tailwind preflight
466
+ already had this rule via Tailwind; consumers without it (like mesh's
467
+ embedded auth pages) now pick it up here.
468
+
469
+ ## 0.2.8 (2026-05-29)
470
+
471
+ - **feat**: `ActionBarButton` wraps the `icon` prop in a
472
+ `.ck-actionbar-icon` span. Lifts unicode glyphs (◐ ☀ ☾ ◇) to
473
+ 16 px and applies a 1 px optical-centre nudge so they read on
474
+ par with the heavier label text. Callers can drop any local
475
+ wrappers and pass icons as bare strings / SVG nodes.
476
+
477
+ ## 0.2.7 (2026-05-28)
478
+
479
+ - **fix**: `Select` popover used to close on ANY scroll event
480
+ (including the option list's own internal scroll), so a user
481
+ trying to scroll through a long list would see the popover
482
+ vanish under their cursor. Scrolls that originate inside the
483
+ popover are now filtered out; outer / page scrolls reposition
484
+ the popover against the trigger instead of closing it.
485
+
486
+ ## 0.2.6 (2026-05-28)
487
+
488
+ - **fix**: `Select` popover was clipped by ancestors with
489
+ `overflow: hidden` (Card, Drawer, Dialog). Popover now renders
490
+ via `createPortal` to `document.body` with `position: fixed`
491
+ computed against the trigger's bounding rect — escapes any
492
+ parent's clip box and stacks above sibling content. Closes on
493
+ page scroll to avoid drifting off the trigger.
494
+ - **feat**: `Select` gains `searchable` + `searchPlaceholder`.
495
+ When `searchable={true}` the popover renders a search input
496
+ pinned at the top that filters options by case-insensitive
497
+ label substring. Keyboard model on the input matches Radix /
498
+ shadcn Combobox: Arrows navigate filtered list, Enter commits
499
+ highlighted, Esc closes, Tab advances focus.
500
+
501
+ ## 0.2.5 (2026-05-28)
502
+
503
+ - **feat**: new `Select` primitive. Custom listbox (NOT native
504
+ `<select>`) so the popup styling actually responds to chrome
505
+ overrides — native `<select>` popups are browser-locked on every
506
+ OS, which used to punch through the design system with macOS
507
+ blue on terminal/editorial dark mode.
508
+ - Trigger renders the same shape as `Input`; chromes that restyle
509
+ `.ck-input` automatically pick up `.ck-select-trigger` siblings.
510
+ - ARIA combobox / listbox roles; full keyboard support
511
+ (Space/Enter open, Arrows + Home/End navigate, Enter commits,
512
+ Esc closes restoring previous value, Tab advances, A-Z/0-9
513
+ typeahead with 500 ms reset).
514
+ - Optional `name` prop emits a hidden `<input>` so plain `<form>`
515
+ submits still carry the value.
516
+ - Terminal chrome override included; other chromes inherit
517
+ sensible defaults via token consumption (extend at the chrome
518
+ file as their look diverges).
519
+
520
+ ## 0.2.4 (2026-05-28)
521
+
522
+ - **fix**: bare `<a>` elements now default to `--ck-accent` (with
523
+ a clean 1 px underline + `var(--ck-accent-hover)` on hover) and
524
+ hold accent through the `:visited` state. Without this rule the
525
+ browser's default visited-purple bled through every chrome —
526
+ visible on terminal (green-on-black landed indigo-on-black) and
527
+ editorial (coral landed purple). Components that style their own
528
+ anchors (`NavItem`, `TopBar` nav, breadcrumbs) keep winning via
529
+ more-specific selectors; this is a strictly-additive base rule.
530
+
531
+ ## 0.2.3 (2026-05-28)
532
+
533
+ - **fix**: `Input` with `prefix` / `suffix` rendered with no left
534
+ padding under the `swiss` chrome — swiss strips `padding-left`
535
+ from `.ck-input` for the underline-only standalone look, but
536
+ that made the input text collide with the addon slab. Restored
537
+ padding for the `.ck-input--with-addon` variant; the swiss
538
+ underline is now drawn on the OUTER wrap so the whole field
539
+ (addons + input) reads as one underlined bar.
540
+
541
+ ## 0.2.2 (2026-05-28)
542
+
543
+ > Note: `ui-v0.2.1` exists as a git tag but was never published to
544
+ > npm (publish workflow waiting for OTP at the time the fixes below
545
+ > were folded in). Consumers should ignore 0.2.1 — use 0.2.2.
546
+
547
+ - **fix**: `Input` addon (`prefix` / `suffix`) visual contrast.
548
+ Previously the addon shared the input's `--ck-bg-surface`
549
+ background and relied on a hard `1px` divider for separation,
550
+ which read as a vertical cut across an otherwise homogeneous
551
+ field. Switched to `--ck-bg-muted` (the canonical "recessed
552
+ slab" token) and removed the divider — the bg shift carries the
553
+ separation across every chrome and dark variant.
554
+ - **fix**: `Input` default height 34 → 36 px to line up with the
555
+ default `Button` height (also 36 px under editorial; matches the
556
+ shadcn/ui + Mantine convention). The previous 2 px short-fall
557
+ made the field read as a size smaller than buttons next to it.
558
+
559
+ ## 0.2.0 (2026-05-28)
560
+
561
+ - **breaking**: remove `frutiger` chrome. The preset's tokens, CSS,
562
+ and components (`SkyBackdrop`, `GlossyOrb`) are deleted; the
563
+ `Chrome` type union no longer includes `"frutiger"`. Consumers
564
+ using it should switch to `ambient` (closest spiritual match).
565
+ - **fix**: ThemeProvider now persists every built-in chrome through
566
+ reloads, not just `classic` / `seamless` (cosxai/product-design#2).
567
+ `BUILTIN_CHROMES` is the new source of truth — exported from
568
+ `@cosxai/ui` so consumers can use it for chrome pickers.
569
+ - **feat**: `Input` gains optional `prefix` + `suffix` props for
570
+ inline addons inside the bordered field (e.g. `acme.cosx.dev`
571
+ workspace pickers, currency symbols, search icons). The native
572
+ HTML `prefix` RDFa attribute is now Omitted from the `InputProps`
573
+ surface — consumers needing it can drop to a raw `<input>`.
574
+
575
+ ## 0.1.0 (2026-05-26)
576
+
577
+ - Initial public release on npm.
package/NOTICE ADDED
@@ -0,0 +1,11 @@
1
+ @cosxai/ui
2
+ © 2026 COSINE X LTD. All rights reserved. Proprietary — see package.json
3
+ ("UNLICENSED"): no licence is granted to use, copy or modify this package.
4
+
5
+ The COSX Design System 3.0 tokens in src/tokens/ are COSX's own.
6
+
7
+ Fonts are dependencies, not part of this package, each under its own licence:
8
+ Geist (Vercel) — SIL Open Font License 1.1, via @fontsource-variable/geist
9
+ Noto Sans SC (Google) — SIL Open Font License 1.1, via @fontsource-variable/noto-sans-sc
10
+
11
+ Code adapted from third-party projects will be listed here with its licence.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @cosxai/ui
2
+
3
+ The COSX Design System 3.0 in React: tokens, a Tailwind v4 theme and
4
+ (from the 1.0 alphas on) components. The design lives at
5
+ [design.cosx.co](https://design.cosx.co); this package is its
6
+ implementation.
7
+
8
+ > **1.x is a new system.** 0.x (the `--ck-*` kit with its presets) is
9
+ > maintained on the `ui-0.x` branch and published under the `v0` dist-tag;
10
+ > `^0.x` ranges keep getting its fixes. 1.0 alphas are published under
11
+ > `next` — install with `@cosxai/ui@next`.
12
+
13
+ ## Use
14
+
15
+ ```css
16
+ /* app.css — Tailwind v4 app */
17
+ @import "tailwindcss";
18
+ @import "@cosxai/ui/theme.css";
19
+ @import "@cosxai/ui/styles.css";
20
+ @source "../node_modules/@cosxai/ui/src";
21
+ ```
22
+
23
+ Without Tailwind, `@cosxai/ui/styles.css` alone gives the tokens and fonts
24
+ as CSS variables (`var(--bg-page)`, `var(--radius-md)` …).
25
+
26
+ **Ink (dark) mode:** `data-mode="ink"` on `<html>`, or `.ink-mode` on a
27
+ subtree. The semantic utilities (`bg-page`, `text-fg`, `border-rule` …)
28
+ switch with it — no `dark:` variants.
29
+
30
+ ## What the theme gives you
31
+
32
+ | Kind | Utilities | Notes |
33
+ |---|---|---|
34
+ | Surfaces | `bg-page` `bg-sunk` `bg-well` `bg-chrome` `bg-field` | switch with ink mode |
35
+ | Type colour | `text-fg` `text-fg-secondary` `text-fg-label` | |
36
+ | Materials | `bg-paper` `bg-linen` `bg-ink` `bg-yellow` `bg-yellow-accent` … | fixed |
37
+ | Status | `attention` `error` (+ `-wash`, `error-text`) | yellow, ink and one red only |
38
+ | Radius | `rounded-xs` 4 · `sm` 6 · `md` 8 · `lg` 16 · `xl` 24 · `pill` | |
39
+ | Size | `text-meta` 12 · `small` 13.5 · `ui` 14 · `body` 15 · `h3` 18 · `lede` 20 · `title` 22 · `figure` 32 · `h2` `h1` `display` | |
40
+ | Family | `font-sans`, `font-sans-cjk` | Geist + Noto Sans SC, self-hosted |
41
+ | Motion | `ease-standard` `ease-out` `ease-in` | |
42
+
43
+ Tailwind's own palette, radii, shadows and easings are removed on purpose:
44
+ 3.0 has no shadows and one yellow.
45
+
46
+ ## Develop
47
+
48
+ `pnpm test` (theme ↔ tokens agreement, `cn`), `pnpm check:css` (a real
49
+ Tailwind build of the theme), `pnpm typecheck`.
50
+
51
+ `src/tokens/` are copied unchanged from the "COSX Design System 3.0"
52
+ Claude Design project — change them there, copy again.