@sableui/react 0.9.0 → 0.10.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 (221) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +124 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/DESIGN.md +55 -16
  5. package/README.md +12 -0
  6. package/dist/{_contract-C2M1SjBf.d.ts → _contract-CkqXDVww.d.ts} +25 -2
  7. package/dist/blocks/danger-zone-block.js +3 -3
  8. package/dist/blocks/danger-zone-block.meta.js +1 -1
  9. package/dist/blocks/faq-block.js +3 -3
  10. package/dist/blocks/goal-progress-block.js +1 -1
  11. package/dist/blocks/notification-list-block.js +1 -1
  12. package/dist/blocks/pricing-table-block.js +12 -12
  13. package/dist/blocks/product-card-block.js +3 -3
  14. package/dist/blocks/product-card-block.meta.js +3 -3
  15. package/dist/blocks/profile-card-block.js +1 -1
  16. package/dist/blocks/profile-form-block.js +3 -3
  17. package/dist/blocks/revenue-breakdown-block.js +5 -5
  18. package/dist/blocks/stat-row-block.js +3 -3
  19. package/dist/blocks/task-list-block.js +2 -2
  20. package/dist/blocks/team-members-block.js +1 -1
  21. package/dist/blocks/wallet-block.js +4 -4
  22. package/dist/blocks/wallet-block.meta.js +3 -3
  23. package/dist/components/accordion/accordion.meta.js +2 -2
  24. package/dist/components/app-header/app-header.meta.js +1 -1
  25. package/dist/components/app-shell/app-shell.meta.js +2 -2
  26. package/dist/components/area-chart/area-chart.meta.js +3 -3
  27. package/dist/components/badge/badge.js +13 -4
  28. package/dist/components/badge/badge.meta.js +24 -13
  29. package/dist/components/badge/badge.variants.js +37 -23
  30. package/dist/components/banner/banner.meta.js +8 -8
  31. package/dist/components/banner/banner.variants.js +3 -3
  32. package/dist/components/bar-chart/bar-chart.meta.js +4 -4
  33. package/dist/components/breadcrumbs/breadcrumbs.meta.js +1 -1
  34. package/dist/components/button/button.js +13 -2
  35. package/dist/components/button/button.meta.js +3 -3
  36. package/dist/components/card/card.js +16 -7
  37. package/dist/components/card/card.meta.js +4 -4
  38. package/dist/components/checkbox-card/checkbox-card.meta.js +10 -10
  39. package/dist/components/checkbox-card/checkbox-card.variants.js +15 -15
  40. package/dist/components/code-block/code-block.meta.js +1 -1
  41. package/dist/components/command-menu/command-menu.js +2 -0
  42. package/dist/components/command-menu/command-menu.meta.js +1 -1
  43. package/dist/components/context-card/context-card.meta.js +2 -2
  44. package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +1 -1
  45. package/dist/components/dialog/dialog.meta.js +2 -2
  46. package/dist/components/disclosure-picker/disclosure-picker.js +48 -2
  47. package/dist/components/disclosure-picker/disclosure-picker.meta.js +3 -2
  48. package/dist/components/empty-state/empty-state.meta.js +4 -4
  49. package/dist/components/form/form.js +10 -5
  50. package/dist/components/gradient-art/gradient-art.meta.js +3 -3
  51. package/dist/components/label/label.meta.js +1 -1
  52. package/dist/components/line-chart/line-chart.meta.js +3 -3
  53. package/dist/components/meter/meter.js +1 -1
  54. package/dist/components/meter/meter.meta.js +2 -2
  55. package/dist/components/meter/meter.variants.js +1 -1
  56. package/dist/components/middle-truncate/middle-truncate.meta.js +4 -4
  57. package/dist/components/note/note.meta.js +3 -3
  58. package/dist/components/page-header/page-header.js +1 -1
  59. package/dist/components/pagination/pagination.meta.js +2 -2
  60. package/dist/components/phone/phone.meta.js +1 -1
  61. package/dist/components/pie-chart/pie-chart.meta.js +3 -3
  62. package/dist/components/pill/pill.js +4 -2
  63. package/dist/components/pill/pill.meta.js +19 -2
  64. package/dist/components/pill/pill.variants.js +23 -2
  65. package/dist/components/popover/popover.meta.js +2 -2
  66. package/dist/components/radar-chart/radar-chart.meta.js +3 -3
  67. package/dist/components/radial-chart/radial-chart.meta.js +3 -3
  68. package/dist/components/radio/radio.meta.js +2 -2
  69. package/dist/components/radio-card/radio-card.meta.js +6 -6
  70. package/dist/components/radio-card/radio-card.variants.js +15 -15
  71. package/dist/components/scroller/scroller.meta.js +1 -1
  72. package/dist/components/search-input/search-input.meta.js +1 -1
  73. package/dist/components/section-header/section-header.js +1 -1
  74. package/dist/components/segmented-bar/segmented-bar.js +1 -1
  75. package/dist/components/segmented-bar/segmented-bar.meta.js +6 -6
  76. package/dist/components/segmented-bar/segmented-bar.variants.js +4 -4
  77. package/dist/components/select/select.meta.js +2 -2
  78. package/dist/components/sheet/sheet.js +15 -1
  79. package/dist/components/sheet/sheet.meta.js +1 -1
  80. package/dist/components/side-nav/side-nav.meta.js +1 -1
  81. package/dist/components/sidebar/sidebar.meta.js +1 -1
  82. package/dist/components/sparkline/sparkline.meta.js +4 -4
  83. package/dist/components/spinner/spinner.meta.js +1 -1
  84. package/dist/components/status-dot/status-dot.js +4 -1
  85. package/dist/components/status-dot/status-dot.meta.js +8 -16
  86. package/dist/components/status-dot/status-dot.variants.js +13 -4
  87. package/dist/components/stepper/stepper.js +11 -2
  88. package/dist/components/table/table.meta.js +4 -4
  89. package/dist/components/tabs/tabs.meta.js +1 -1
  90. package/dist/components/textarea/textarea.meta.js +2 -2
  91. package/dist/components/toast/toast.js +1 -0
  92. package/dist/components/toast/toast.meta.js +1 -1
  93. package/dist/components/tooltip/tooltip.meta.js +2 -2
  94. package/dist/file-hashes.json +127 -124
  95. package/dist/foundations/spacing.js +8 -8
  96. package/dist/index.d.ts +138 -73
  97. package/dist/index.js +12 -1
  98. package/dist/lib/chart/types.js +3 -3
  99. package/dist/lib/is-promise.js +5 -0
  100. package/dist/lib/palette.js +103 -0
  101. package/dist/lib/selection-card.js +4 -4
  102. package/dist/palette.css +669 -0
  103. package/dist/patterns/auth-page.js +6 -6
  104. package/dist/patterns/auth-page.meta.js +3 -3
  105. package/dist/patterns/brand-landing-page.js +2 -2
  106. package/dist/patterns/chat-panel.js +2 -2
  107. package/dist/patterns/dashboard-page.js +71 -71
  108. package/dist/patterns/dashboard-page.meta.js +5 -5
  109. package/dist/patterns/feed-page.js +25 -25
  110. package/dist/patterns/feed-page.meta.js +3 -3
  111. package/dist/patterns/landing-page.js +1 -1
  112. package/dist/patterns/list-detail-page.js +17 -17
  113. package/dist/patterns/settings-page.js +17 -17
  114. package/dist/presets/index.d.ts +1 -1
  115. package/dist/presets/index.js +17 -2
  116. package/dist/styles.css +163 -55
  117. package/dist/{theme-provider-S0NVJs2Y.d.ts → theme-provider-CdSicAad.d.ts} +1 -1
  118. package/dist/themes/_contract.js +22 -8
  119. package/dist/themes/atelier/atelier.css +13 -13
  120. package/dist/themes/atelier/index.d.ts +1 -1
  121. package/dist/themes/ember/ember.css +13 -13
  122. package/dist/themes/ember/index.d.ts +1 -1
  123. package/dist/themes/index.d.ts +2 -2
  124. package/dist/themes/meridian/index.d.ts +1 -1
  125. package/dist/themes/meridian/meridian.css +13 -13
  126. package/dist/themes/poppy/index.d.ts +1 -1
  127. package/dist/themes/poppy/poppy.css +19 -16
  128. package/dist/themes/prism/index.d.ts +1 -1
  129. package/dist/themes/prism/prism.css +18 -15
  130. package/dist/themes/sable/index.d.ts +1 -1
  131. package/dist/themes/sable/sable.css +18 -22
  132. package/dist/themes/verdant/index.d.ts +1 -1
  133. package/dist/themes/verdant/verdant.css +15 -15
  134. package/llms/blocks/danger-zone-block.md +4 -4
  135. package/llms/blocks/faq-block.md +3 -3
  136. package/llms/blocks/goal-progress-block.md +1 -1
  137. package/llms/blocks/notification-list-block.md +1 -1
  138. package/llms/blocks/pricing-table-block.md +12 -12
  139. package/llms/blocks/product-card-block.md +6 -6
  140. package/llms/blocks/profile-card-block.md +1 -1
  141. package/llms/blocks/profile-form-block.md +3 -3
  142. package/llms/blocks/revenue-breakdown-block.md +5 -5
  143. package/llms/blocks/stat-row-block.md +3 -3
  144. package/llms/blocks/task-list-block.md +2 -2
  145. package/llms/blocks/team-members-block.md +1 -1
  146. package/llms/blocks/wallet-block.md +7 -7
  147. package/llms/components/accordion.md +2 -2
  148. package/llms/components/app-header.md +1 -1
  149. package/llms/components/app-shell.md +2 -2
  150. package/llms/components/area-chart.md +1 -1
  151. package/llms/components/badge.md +15 -7
  152. package/llms/components/banner.md +6 -6
  153. package/llms/components/bar-chart.md +2 -2
  154. package/llms/components/breadcrumbs.md +1 -1
  155. package/llms/components/button.md +3 -3
  156. package/llms/components/card.md +4 -4
  157. package/llms/components/checkbox-card.md +5 -5
  158. package/llms/components/code-block.md +1 -1
  159. package/llms/components/command-menu.md +1 -1
  160. package/llms/components/context-card.md +2 -2
  161. package/llms/components/destructive-action-modal.md +1 -1
  162. package/llms/components/dialog.md +2 -2
  163. package/llms/components/disclosure-picker.md +2 -1
  164. package/llms/components/empty-state.md +4 -4
  165. package/llms/components/gradient-art.md +3 -3
  166. package/llms/components/label.md +1 -1
  167. package/llms/components/line-chart.md +1 -1
  168. package/llms/components/meter.md +2 -2
  169. package/llms/components/middle-truncate.md +5 -5
  170. package/llms/components/note.md +3 -3
  171. package/llms/components/pagination.md +2 -2
  172. package/llms/components/phone.md +1 -1
  173. package/llms/components/pie-chart.md +1 -1
  174. package/llms/components/pill.md +12 -2
  175. package/llms/components/popover.md +2 -2
  176. package/llms/components/radar-chart.md +1 -1
  177. package/llms/components/radial-chart.md +1 -1
  178. package/llms/components/radio-card.md +4 -4
  179. package/llms/components/radio.md +2 -2
  180. package/llms/components/scroller.md +1 -1
  181. package/llms/components/search-input.md +1 -1
  182. package/llms/components/segmented-bar.md +7 -7
  183. package/llms/components/select.md +2 -2
  184. package/llms/components/sheet.md +1 -1
  185. package/llms/components/side-nav.md +1 -1
  186. package/llms/components/sidebar.md +1 -1
  187. package/llms/components/sparkline.md +2 -2
  188. package/llms/components/spinner.md +1 -1
  189. package/llms/components/status-dot.md +4 -4
  190. package/llms/components/table.md +8 -8
  191. package/llms/components/tabs.md +1 -1
  192. package/llms/components/textarea.md +2 -2
  193. package/llms/components/toast.md +1 -1
  194. package/llms/components/tooltip.md +2 -2
  195. package/llms/foundations/spacing.md +7 -7
  196. package/llms/layout/grid.md +1 -1
  197. package/llms/layout/page.md +1 -1
  198. package/llms/layout/region.md +1 -1
  199. package/llms/layout/split-pane.md +1 -1
  200. package/llms/patterns/auth-page.md +11 -11
  201. package/llms/patterns/brand-landing-page.md +2 -2
  202. package/llms/patterns/chat-panel.md +2 -2
  203. package/llms/patterns/dashboard-page.md +78 -78
  204. package/llms/patterns/feed-page.md +31 -31
  205. package/llms/patterns/landing-page.md +1 -1
  206. package/llms/patterns/list-detail-page.md +18 -18
  207. package/llms/patterns/settings-page.md +18 -18
  208. package/llms/reference/token-intent.md +25 -11
  209. package/llms/reference/utilities.md +1 -1
  210. package/llms/tasks/adding-a-component.md +4 -1
  211. package/llms/tasks/building-a-page.md +40 -0
  212. package/llms/tasks/building-with-sable.md +4 -0
  213. package/llms/tasks/converting-an-app.md +3 -1
  214. package/llms/tasks/using-presets.md +4 -0
  215. package/llms-full.txt +433 -347
  216. package/llms.txt +7 -7
  217. package/package.json +4 -4
  218. package/registry.json +369 -233
  219. package/scripts/lint-rules.mjs +26 -5
  220. package/scripts/sources.mjs +4 -1
  221. package/skills/sable/SKILL.md +14 -3
@@ -73,7 +73,7 @@ Use Tooltip for brief nonessential hints tied to a specific control or piece of
73
73
  ```tsx
74
74
  import { Button, Icon, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@sableui/react"
75
75
 
76
- <TooltipProvider><Tooltip><TooltipTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button>} /><TooltipContent>Stored permanently on Arweave.</TooltipContent></Tooltip></TooltipProvider>
76
+ <TooltipProvider><Tooltip><TooltipTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button>} /><TooltipContent>Saved to your workspace.</TooltipContent></Tooltip></TooltipProvider>
77
77
  ```
78
78
 
79
79
  ### Sides
@@ -89,5 +89,5 @@ import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@sableui/react"
89
89
  ```tsx
90
90
  import { Button, Icon, SimpleTooltip } from "@sableui/react"
91
91
 
92
- <SimpleTooltip content="Stored permanently on Arweave."><Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button></SimpleTooltip>
92
+ <SimpleTooltip content="Saved to your workspace."><Button variant="ghost" size="icon-sm" aria-label="Info"><Icon name="info" /></Button></SimpleTooltip>
93
93
  ```
@@ -1,8 +1,8 @@
1
1
  # Spacing & sizing
2
2
 
3
- One 4px scale governs every gap, inset and size, and space is assigned by role. Controls read a height ladder that a density scope can retune all at once.
3
+ One scale, derived from the type: the baseline is one line of body text, split into steps every gap, inset and line height lands on. Space is assigned by role. Controls read a height ladder that a density scope can retune all at once.
4
4
 
5
- **4px base · 8px rhythm · 3 roles · 1 control ladder**
5
+ **Baseline from the type · 8px steps · 3 roles · 1 control ladder**
6
6
 
7
7
  ## Spacing by role
8
8
 
@@ -11,8 +11,8 @@ Steps below 2 (8px) are optical correction only.
11
11
  | Name | Value | Use |
12
12
  |---|---|---|
13
13
  | `Inset p-4` | 16px | Padding inside a surface. p-3 for a dense card, px-3 py-2 for a row. |
14
- | `Stack, within a group gap-1.5 – gap-2` | 6–8px | Label to field, title to description. |
15
- | `Stack, between groups gap-4 – gap-6` | 16–24px | Field to field, card to card. |
14
+ | `Stack, within a group gap-2` | 1 step · 8px | Label to field, title to description. |
15
+ | `Stack, between groups gap-4 – gap-6` | 2–3 steps · 16–24px | Field to field, card to card. |
16
16
  | `Stack, between sections gap-12+` | 48px+ | Major regions of a page. |
17
17
  | `Inline gap-2 – gap-3` | 8–12px | Icon to label, control to control. |
18
18
 
@@ -38,8 +38,8 @@ Comfortable values; data-density="compact" (or <Density>) redefines every step f
38
38
 
39
39
  ## Rules
40
40
 
41
- 1. **Every margin, padding and gap is a step on the 4px scale.** Off-scale values break the rhythm that lets adjacent components line up without coordination.
42
- 2. **Padding, gaps and insets in a component sit on the spacing scale: a step (p-4, gap-2) or --spacing(n) inside an arbitrary value (p-[var(--card-p,--spacing(6))]).** The density (Character) control works by retuning --spacing, so anything on the scale moves with it. A literal p-[1.5rem] or padding: "12px" stays put while everything around it tightens. Keep fixed rem/px for what must not scale: a 1px hairline, a ring width. *Enforced by design-lint: fixed-spacing (library source).*
41
+ 1. **Every margin, padding and gap is a step on the scale, and vertical space between blocks is whole baseline steps: an even Tailwind step (2, 4, 6, 8, 12) or a Stack/Row/Grid gap number.** The baseline is one line of body text split into steps; line heights round to it. An odd step (3, 5) is half a baseline step. It is a rhythm to keep, not a grid to force: components stay as tall as their content.
42
+ 2. **Padding, gaps and insets in a component sit on the spacing scale: a step (p-4, gap-2) or --spacing(n) inside an arbitrary value (p-[var(--card-p,--spacing(6))]).** --spacing derives from the baseline step and the density (Character) control moves the leading it comes from, so anything on the scale moves with it. A literal p-[1.5rem] or padding: "12px" stays put while everything around it tightens. Keep fixed rem/px for what must not scale: a 1px hairline, a ring width. *Enforced by design-lint: fixed-spacing (library source).*
43
43
  3. **Related things sit closer than unrelated things.** Proximity groups content without a divider line. Use space first; reach for a border only when space is not enough.
44
44
  4. **Space lives on the container (gap), not on children (margins).** gap survives reordering, conditional children and RTL; per-child margins do not.
45
45
  5. **Control heights read the --control-* ladder.** A density scope retunes every control at once. A hardcoded height has to be found and overridden by hand. *Enforced by sable-lint: raw-control-height.*
@@ -77,6 +77,6 @@ Every control, menu and popup inside retunes, including portaled overlays.
77
77
  <Stack gap={3}> between groups (24px), <Stack gap={1}> inside a group (8px)
78
78
  ```
79
79
 
80
- Two spacing steps do the work of every divider, with less ink. Stack's numeric gap counts --rhythm-unit — two spacing steps — so it follows the density control like every other spacing value.
80
+ Spacing steps do the work of every divider, with less ink. Stack's numeric gap counts --rhythm-unit — one baseline step — so it follows the type and the density control like every other spacing value.
81
81
 
82
82
  **Related:** `sable docs shape` · `sable docs typography` · `sable docs building-a-tool` · `sable token layout`
@@ -64,5 +64,5 @@ import { Card, CardDescription, CardHeader, Grid } from "@sableui/react"
64
64
  ```tsx
65
65
  import { Badge, Grid } from "@sableui/react"
66
66
 
67
- <Grid cols={{ base: 1, md: 2, lg: 4 }}><Badge appearance="plain" tone="standard" dot /><Badge appearance="plain" tone="warning" dot /><Badge appearance="plain" tone="neutral" dot /><Badge appearance="plain" tone="destructive" dot /></Grid>
67
+ <Grid cols={{ base: 1, md: 2, lg: 4 }}><Badge appearance="plain" tone="3" dot /><Badge appearance="plain" tone="warning" dot /><Badge appearance="plain" tone="neutral" dot /><Badge appearance="plain" tone="destructive" dot /></Grid>
68
68
  ```
@@ -35,5 +35,5 @@ Use Page as a route content shell that combines Region sizing with vertical rhyt
35
35
  ```tsx
36
36
  import { Card, CardContent, Page, PageHeader } from "@sableui/react"
37
37
 
38
- <Page as="main" max="feed"><PageHeader title="Home" description="Latest posts from artists you follow." /><Card><CardContent><p className="text-body-md">First post.</p></CardContent></Card></Page>
38
+ <Page as="main" max="feed"><PageHeader title="Home" description="Latest posts from people you follow." /><Card><CardContent><p className="text-body-md">First post.</p></CardContent></Card></Page>
39
39
  ```
@@ -50,7 +50,7 @@ import { PageHeader, Region } from "@sableui/react"
50
50
  ```tsx
51
51
  import { Card, CardHeader, CardTitle, Grid, Region } from "@sableui/react"
52
52
 
53
- <Region max="wide"><Grid minItemWidth="16rem"><Card><CardHeader><CardTitle>Revenue</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Collects</CardTitle></CardHeader></Card></Grid></Region>
53
+ <Region max="wide"><Grid minItemWidth="16rem"><Card><CardHeader><CardTitle>Revenue</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Orders</CardTitle></CardHeader></Card></Grid></Region>
54
54
  ```
55
55
 
56
56
  ### Fluid workspace: the queue's table takes the extra width
@@ -37,5 +37,5 @@ Use SplitPane for responsive two-pane layouts where a primary pane and supportin
37
37
  ```tsx
38
38
  import { AspectBox, Button, SplitPane, Stack } from "@sableui/react"
39
39
 
40
- <SplitPane ratio="2/1" primary={<AspectBox ratio={1}><img src={post.imageUrl} alt={post.title} className="size-full object-cover" /></AspectBox>} secondary={<Stack gap={2}><h1 className="text-heading-1">{post.title}</h1><p className="text-body-md text-muted-foreground">{post.description}</p><Button>Collect</Button></Stack>} />
40
+ <SplitPane ratio="2/1" primary={<AspectBox ratio={1}><img src={post.imageUrl} alt={post.title} className="size-full object-cover" /></AspectBox>} secondary={<Stack gap={2}><h1 className="text-heading-1">{post.title}</h1><p className="text-body-md text-muted-foreground">{post.description}</p><Button>Add to cart</Button></Stack>} />
41
41
  ```
@@ -1,6 +1,6 @@
1
1
  # AuthPage
2
2
 
3
- Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and wallet alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a featured release card. Below lg the panel drops away and the form is the whole screen.
3
+ Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and email-link alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a customer story card. Below lg the panel drops away and the form is the whole screen.
4
4
 
5
5
  **Intent:** Reach for this for sign-in, sign-up, forgot-password, and any single-purpose one-column form route (invite accept, unsubscribe). Not for multi-section forms (use SettingsPage).
6
6
 
@@ -11,14 +11,14 @@ Split sign-in page: a form column (brand mark, heading, email + password, primar
11
11
  - The heading is `text-heading-1` in an h1 — it is the page outline, so it takes a heading tier, not a card title.
12
12
  - Brand mark, form and terms are a three-part column: mark pinned to the top, form vertically centred in the remaining space, terms pinned to the bottom.
13
13
  - Forgot password sits under the password field, right-aligned, before the submit button — so it comes right after the field in tab order. Field has no label-row action slot yet.
14
- - One primary action. Passkey and wallet are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.
14
+ - One primary action. Passkey and email link are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.
15
15
  - Validation uses Field `validate` for product copy instead of the browser's generic required message. A rejected sign-in comes back through Form `errors` on the password field; the submit Button shows `loading` while the request is in flight.
16
16
  - The brand panel artwork is a mesh of the theme's decorative tones over `--primary`, so it re-paints for every theme and mode. The relative-colour `oklch(from var(--tone-*) …)` lift needs an `inline-color` disable with a reason.
17
17
  - The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it.
18
18
 
19
19
  ## Accessibility
20
20
 
21
- - The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Featured release".
21
+ - The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Customer story".
22
22
  - Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.
23
23
  - Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.
24
24
  - The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.
@@ -49,7 +49,7 @@ export function AuthPage() {
49
49
  <Stack gap={1.5}>
50
50
  <h1 className="text-heading-1">Welcome back</h1>
51
51
  <p className="text-body-lg text-muted-foreground">
52
- Sign in to collect new releases and manage your editions.
52
+ Sign in to pick up where you and your team left off.
53
53
  </p>
54
54
  </Stack>
55
55
 
@@ -87,8 +87,8 @@ export function AuthPage() {
87
87
  <Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="key" />}>
88
88
  Sign in with a passkey
89
89
  </Button>
90
- <Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="wallet" />}>
91
- Connect a wallet
90
+ <Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="mail" />}>
91
+ Email me a sign-in link
92
92
  </Button>
93
93
  </Stack>
94
94
 
@@ -115,23 +115,23 @@ export function AuthPage() {
115
115
  </Region>
116
116
 
117
117
  {/* The brand panel is decoration: on a phone the form is the whole screen. */}
118
- <aside aria-label="Featured release" className="hidden p-3 lg:flex">
118
+ <aside aria-label="Customer story" className="hidden p-3 lg:flex">
119
119
  {/* Art painted from the theme's tones over its brand, so it follows every look. */}
120
120
  <GradientArt seed="Untitled (gold rush)" className="flex flex-1 flex-col justify-end rounded-xl p-8">
121
121
  <Card className="max-w-md">
122
122
  <CardContent>
123
123
  <Stack gap={3}>
124
- <Badge tone="edition" className="self-start">
125
- Edition of 50
124
+ <Badge tone="1" className="self-start">
125
+ Customer story
126
126
  </Badge>
127
127
  <p className="text-title-lg">
128
- “Every edition is a conversation with whoever holds it next.”
128
+ “We moved three teams over in a week, and nobody asked for the old tool back.”
129
129
  </p>
130
130
  <Entity
131
131
  className="p-0"
132
132
  leading={<Avatar variant="art" fallback="MO" />}
133
133
  title="Mara Okafor"
134
- subtitle="Untitled (gold rush), 2026"
134
+ subtitle="Head of Design, Northwind"
135
135
  />
136
136
  </Stack>
137
137
  </CardContent>
@@ -45,7 +45,7 @@ const spaces = [
45
45
 
46
46
  const routes = [
47
47
  { label: "Intake", state: "Open", tone: "info" as const },
48
- { label: "Review", state: "Live", tone: "edition" as const },
48
+ { label: "Review", state: "Live", tone: "1" as const },
49
49
  { label: "Archive", state: "Safe", tone: "success" as const },
50
50
  ]
51
51
 
@@ -223,7 +223,7 @@ export function BrandLandingPage() {
223
223
  <div className="flex items-center justify-between border-b border-border px-4 py-3">
224
224
  <Row gap={1}>
225
225
  <StatusDot tone="warning" />
226
- <StatusDot tone="edition" />
226
+ <StatusDot tone="1" />
227
227
  <StatusDot tone="success" />
228
228
  </Row>
229
229
  <span className="text-label-sm text-muted-foreground">Live workspace</span>
@@ -95,7 +95,7 @@ export function ChatPanel() {
95
95
  render={<section aria-labelledby="chat-title" />}
96
96
  className="flex h-[calc(100dvh_-_--spacing(12))] flex-col overflow-hidden"
97
97
  >
98
- <CardHeader className="border-b border-border pb-(--card-p)">
98
+ <CardHeader className="pb-(--card-p) shadow-[inset_0_-1px_0_var(--border)]">
99
99
  {/* CardTitle carries the type; the h1 inside it is the page heading. */}
100
100
  <CardTitle>
101
101
  <h1 id="chat-title">Scroll behaviour</h1>
@@ -234,7 +234,7 @@ export function ChatPanel() {
234
234
  </Message>
235
235
  </MessageScroller>
236
236
 
237
- <CardFooter className="border-t border-border pt-(--card-p)">
237
+ <CardFooter className="pt-(--card-p) shadow-[inset_0_1px_0_var(--border)]">
238
238
  <ChatComposer
239
239
  className="flex-1"
240
240
  placeholder="Ask a follow-up…"
@@ -1,17 +1,17 @@
1
1
  # DashboardPage
2
2
 
3
- App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge editions table with sell-through Meters, and supporting payout, collector, release and activity cards — the default shape for an operator or analytics home route.
3
+ App-shell admin dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge accounts table with seat-usage Meters, and supporting payout, top-customer, release and activity cards — the default shape for an operator or analytics home route.
4
4
 
5
- **Intent:** Reach for this when a route's job is monitoring or managing many records (sales, orders, editions, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).
5
+ **Intent:** Reach for this when a route's job is monitoring or managing many records (sales, orders, accounts, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).
6
6
 
7
7
  ## Notes
8
8
 
9
9
  - Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.
10
- - Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top collectors), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.
10
+ - Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top customers), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.
11
11
  - Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` — the metric mono tier, tabular figures — never a heading tier. The trend Badge sits with its comparison note in CardContent, so the value keeps the full card width when the row folds to 2×2 on a phone.
12
12
  - Stat cards use an explicit cols map (2 → 4) — a 4-stat row must fold to 2×2, never strand an orphan the way auto-fit can.
13
13
  - The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density — never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.
14
- - Sold / supply is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance="plain" dot`, with the app's own states mapped to tones once at the top of the file.
14
+ - Seats used / seats is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance="plain" dot`, with the app's own states mapped to tones once at the top of the file.
15
15
  - Lists are Entity rows in CardList, inset to the card padding and divided by hairlines — never CardContent with hand-tuned px. A card that shares a grid row with a taller one becomes `flex flex-col` and pins its CardFooter with `mt-auto`.
16
16
  - Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with an OverflowMenu. Sidebar rebinds the page ink tokens to its own, so the Entity and OverflowMenu read correctly even when a theme paints the rail dark on a light page.
17
17
  - The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.
@@ -23,7 +23,7 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
23
23
  - AppShell provides the skip link and <main id="main-content"> landmark.
24
24
  - The active SidebarItem and BottomNavItem carry aria-current via `active`.
25
25
  - DataTable headers are real <th>; sortable columns are announced.
26
- - Each sell-through Meter has an aria-label naming the edition and aria-valuetext reading "184 of 250 sold".
26
+ - Each seat-usage Meter has an aria-label naming the account and aria-valuetext reading "184 of 250 seats used".
27
27
  - Avatars beside a visible name use an empty alt so the name is not read twice.
28
28
 
29
29
  ## Source
@@ -31,11 +31,11 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
31
31
  ```tsx
32
32
  import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, DataTable, Entity, Grid, Icon, MenuItem, MenuSeparator, Meter, NotificationBadge, OverflowMenu, Page, PageHeader, RelativeTime, Row, Sidebar, SidebarBrand, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, Stack } from "@sableui/react"
33
33
 
34
- // Edition status → Badge tone. Sable has no status vocabulary of its own;
34
+ // Account status → Badge tone. Sable has no status vocabulary of its own;
35
35
  // an app maps ITS states onto tones once, here, and every badge follows.
36
- const STATUS_TONE = { live: "success", scheduled: "info", "sold-out": "edition", closed: "neutral" } as const
37
- const STATUS_LABEL = { live: "Live", scheduled: "Scheduled", "sold-out": "Sold out", closed: "Closed" } as const
38
- type EditionStatus = keyof typeof STATUS_TONE
36
+ const STATUS_TONE = { active: "success", onboarding: "info", full: "1", cancelled: "neutral" } as const
37
+ const STATUS_LABEL = { active: "Active", onboarding: "Onboarding", full: "Seats full", cancelled: "Cancelled" } as const
38
+ type AccountStatus = keyof typeof STATUS_TONE
39
39
 
40
40
  const MIN = 60 * 1000
41
41
  const HOUR = 60 * MIN
@@ -48,54 +48,54 @@ const shortDate = (offset: number) =>
48
48
 
49
49
  const stats = [
50
50
  { label: "Revenue", value: "$18,240", delta: "+8.2%", up: true, note: "vs. $16,857" },
51
- { label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 first-time" },
52
- { label: "Collectors", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
53
- { label: "Sell-through", value: "64%", delta: "−1.8%", up: false, note: "6 open editions" },
51
+ { label: "Active seats", value: "1,284", delta: "+3.1%", up: true, note: "182 added" },
52
+ { label: "Accounts", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
53
+ { label: "Seat usage", value: "64%", delta: "−1.8%", up: false, note: "6 near their limit" },
54
54
  ]
55
55
 
56
- const editions = [
57
- { id: "ed_01", title: "Gold Rush", status: "live" as const, sold: 184, supply: 250, price: 48, released: -6 * DAY },
58
- { id: "ed_02", title: "Quiet Harbor", status: "sold-out" as const, sold: 100, supply: 100, price: 120, released: -19 * DAY },
59
- { id: "ed_03", title: "Dusk, no. 4", status: "live" as const, sold: 61, supply: 150, price: 64, released: -2 * DAY },
60
- { id: "ed_04", title: "Tidewater", status: "live" as const, sold: 212, supply: 300, price: 36, released: -27 * DAY },
61
- { id: "ed_05", title: "Salt Study", status: "closed" as const, sold: 38, supply: 75, price: 90, released: -44 * DAY },
62
- { id: "ed_06", title: "Paper Moon", status: "sold-out" as const, sold: 50, supply: 50, price: 220, released: -58 * DAY },
63
- { id: "ed_07", title: "Night Signal", status: "scheduled" as const, sold: 0, supply: 200, price: 56, released: 3 * DAY },
64
- { id: "ed_08", title: "Low Tide, Blue", status: "closed" as const, sold: 72, supply: 120, price: 42, released: -71 * DAY },
65
- { id: "ed_09", title: "Ember Field", status: "sold-out" as const, sold: 80, supply: 80, price: 140, released: -86 * DAY },
56
+ const accounts = [
57
+ { id: "ac_01", title: "Northwind", status: "active" as const, used: 184, seats: 250, price: 16, started: -6 * DAY },
58
+ { id: "ac_02", title: "Harbor Labs", status: "full" as const, used: 100, seats: 100, price: 20, started: -19 * DAY },
59
+ { id: "ac_03", title: "Brightline", status: "active" as const, used: 61, seats: 150, price: 16, started: -2 * DAY },
60
+ { id: "ac_04", title: "Tidewater Co.", status: "active" as const, used: 212, seats: 300, price: 12, started: -27 * DAY },
61
+ { id: "ac_05", title: "Saltworks", status: "cancelled" as const, used: 38, seats: 75, price: 20, started: -44 * DAY },
62
+ { id: "ac_06", title: "Paper Moon", status: "full" as const, used: 50, seats: 50, price: 24, started: -58 * DAY },
63
+ { id: "ac_07", title: "Signal Studio", status: "onboarding" as const, used: 0, seats: 200, price: 16, started: 3 * DAY },
64
+ { id: "ac_08", title: "Bluewater", status: "cancelled" as const, used: 72, seats: 120, price: 12, started: -71 * DAY },
65
+ { id: "ac_09", title: "Ember & Field", status: "full" as const, used: 80, seats: 80, price: 20, started: -86 * DAY },
66
66
  ]
67
- const editionCount = 24
67
+ const accountCount = 312
68
68
 
69
69
  const payout = {
70
- amount: 8214.6,
70
+ amount: 8862.64,
71
71
  date: 5 * DAY,
72
72
  lines: [
73
- { label: "Sales", value: 9127.33 },
74
- { label: "Platform fee (10%)", value: -912.73 },
73
+ { label: "Subscriptions", value: 9127.33 },
74
+ { label: "Processing (2.9%)", value: -264.69 },
75
75
  { label: "Deposits to", value: "Checking ••4821" },
76
76
  ],
77
77
  }
78
78
 
79
- const collectors = [
80
- { id: "co_01", name: "Nina Simone", handle: "@nina", initials: "NS", pieces: 14, spent: 1460 },
81
- { id: "co_02", name: "Grace Hopper", handle: "@grace", initials: "GH", pieces: 11, spent: 1128 },
82
- { id: "co_03", name: "Frida Kahlo", handle: "@frida", initials: "FK", pieces: 9, spent: 942 },
83
- { id: "co_04", name: "Mary Shelley", handle: "@mary", initials: "MS", pieces: 6, spent: 588 },
79
+ const customers = [
80
+ { id: "cu_01", name: "Northwind", handle: "northwind.io", initials: "NW", seats: 184, spent: 2944 },
81
+ { id: "cu_02", name: "Tidewater Co.", handle: "tidewater.co", initials: "TC", seats: 212, spent: 2544 },
82
+ { id: "cu_03", name: "Harbor Labs", handle: "harborlabs.com", initials: "HL", seats: 100, spent: 2000 },
83
+ { id: "cu_04", name: "Ember & Field", handle: "emberfield.com", initials: "EF", seats: 80, spent: 1600 },
84
84
  ]
85
85
 
86
86
  const upcoming = [
87
- { id: "up_01", title: "Night Signal", detail: "Edition of 200 · $56", at: 3 * DAY },
88
- { id: "up_02", title: "Tidewater, second printing", detail: "Edition of 100 · $40", at: 12 * DAY },
89
- { id: "up_03", title: "Dusk, no. 5", detail: "Edition of 150 · $64", at: 26 * DAY },
90
- { id: "up_04", title: "Quiet Harbor, artist proofs", detail: "Edition of 10 · $480", at: 34 * DAY },
87
+ { id: "up_01", title: "Version 2.4", detail: "Shared reports · All plans", at: 3 * DAY },
88
+ { id: "up_02", title: "Audit log export", detail: "Business plan · Beta", at: 12 * DAY },
89
+ { id: "up_03", title: "Version 2.5", detail: "Offline mode · All plans", at: 26 * DAY },
90
+ { id: "up_04", title: "Usage-based billing", detail: "Studio and Business · Beta", at: 34 * DAY },
91
91
  ]
92
92
 
93
93
  const activity = [
94
- { id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush · 185 of 250", when: 2 * MIN },
95
- { id: "ac_02", who: "Grace Hopper", initials: "GH", what: "collected Dusk, no. 4 · 61 of 150", when: 26 * MIN },
96
- { id: "ac_03", who: "Frida Kahlo", initials: "FK", what: "tipped $20 on Tidewater", when: 1 * HOUR },
97
- { id: "ac_04", who: "Mary Shelley", initials: "MS", what: "started following you", when: 3 * HOUR },
98
- { id: "ac_05", who: "Nina Simone", initials: "NS", what: "set a reminder for Night Signal", when: 6 * HOUR },
94
+ { id: "ev_01", who: "Nina Simone", initials: "NS", what: "added 12 seats to Northwind · 184 of 250", when: 2 * MIN },
95
+ { id: "ev_02", who: "Grace Hopper", initials: "GH", what: "upgraded Brightline to Studio", when: 26 * MIN },
96
+ { id: "ev_03", who: "Frida Kahlo", initials: "FK", what: "paid invoice INV-0042 for Tidewater Co.", when: 1 * HOUR },
97
+ { id: "ev_04", who: "Mary Shelley", initials: "MS", what: "joined the Harbor Labs workspace", when: 3 * HOUR },
98
+ { id: "ev_05", who: "Nina Simone", initials: "NS", what: "booked onboarding for Signal Studio", when: 6 * HOUR },
99
99
  ]
100
100
 
101
101
  export function DashboardPage() {
@@ -117,8 +117,8 @@ export function DashboardPage() {
117
117
  bottomNav={
118
118
  <BottomNav aria-label="Primary">
119
119
  <BottomNavItem icon={<Icon name="grid" />} label="Overview" showLabel active render={<a href="/" />} />
120
- <BottomNavItem icon={<Icon name="image-stack" />} label="Editions" showLabel render={<a href="/editions" />} />
121
- <BottomNavItem icon={<Icon name="user" />} label="Collectors" showLabel render={<a href="/collectors" />} />
120
+ <BottomNavItem icon={<Icon name="folder" />} label="Accounts" showLabel render={<a href="/accounts" />} />
121
+ <BottomNavItem icon={<Icon name="user" />} label="Customers" showLabel render={<a href="/customers" />} />
122
122
  <BottomNavItem icon={<Icon name="bell" />} label="Activity" showLabel render={<a href="/activity" />} />
123
123
  <BottomNavItem icon={<Icon name="gear" />} label="Settings" showLabel render={<a href="/settings" />} />
124
124
  </BottomNav>
@@ -130,8 +130,8 @@ export function DashboardPage() {
130
130
  </SidebarHeader>
131
131
  <SidebarNav>
132
132
  <SidebarItem icon={<Icon name="grid" />} label="Overview" active render={<a href="/" />} />
133
- <SidebarItem icon={<Icon name="image-stack" />} label="Editions" render={<a href="/editions" />} />
134
- <SidebarItem icon={<Icon name="user" />} label="Collectors" render={<a href="/collectors" />} />
133
+ <SidebarItem icon={<Icon name="folder" />} label="Accounts" render={<a href="/accounts" />} />
134
+ <SidebarItem icon={<Icon name="user" />} label="Customers" render={<a href="/customers" />} />
135
135
  <SidebarItem
136
136
  icon={<Icon name="bell" />}
137
137
  label="Activity"
@@ -162,7 +162,7 @@ export function DashboardPage() {
162
162
  <Page max="wide" gap={4} className="py-6">
163
163
  <PageHeader
164
164
  title="Overview"
165
- description="Sales and collector activity for the last 30 days."
165
+ description="Revenue and account activity for the last 30 days."
166
166
  actions={
167
167
  <Row gap={2}>
168
168
  <Button variant="outline">
@@ -171,7 +171,7 @@ export function DashboardPage() {
171
171
  </Button>
172
172
  <Button>
173
173
  <Icon name="plus" />
174
- New edition
174
+ New account
175
175
  </Button>
176
176
  </Row>
177
177
  }
@@ -201,43 +201,43 @@ export function DashboardPage() {
201
201
  <Grid cols={{ base: 1, xl: 3 }} gap={3}>
202
202
  <Card className="flex flex-col xl:col-span-2">
203
203
  <CardHeader>
204
- <CardTitle>Editions</CardTitle>
205
- <CardDescription>Sales across your releases, newest first.</CardDescription>
204
+ <CardTitle>Accounts</CardTitle>
205
+ <CardDescription>Seats across your customers, newest first.</CardDescription>
206
206
  </CardHeader>
207
207
  <CardContent flush className="mt-4 flex-1">
208
208
  <DataTable
209
- data={editions}
209
+ data={accounts}
210
210
  rowKey={(row) => row.id}
211
- defaultSort={{ key: "released", direction: "desc" }}
211
+ defaultSort={{ key: "started", direction: "desc" }}
212
212
  columns={[
213
- { key: "title", header: "Edition", sortable: true, className: "text-label-md whitespace-nowrap" },
213
+ { key: "title", header: "Account", sortable: true, className: "text-label-md whitespace-nowrap" },
214
214
  {
215
215
  key: "status",
216
216
  header: "Status",
217
217
  cell: (row) => (
218
- <Badge appearance="plain" tone={STATUS_TONE[row.status as EditionStatus]} dot>
219
- {STATUS_LABEL[row.status as EditionStatus]}
218
+ <Badge appearance="plain" tone={STATUS_TONE[row.status as AccountStatus]} dot>
219
+ {STATUS_LABEL[row.status as AccountStatus]}
220
220
  </Badge>
221
221
  ),
222
222
  },
223
223
  {
224
- key: "sold",
225
- header: "Sold",
224
+ key: "used",
225
+ header: "Seats",
226
226
  sortable: true,
227
- sortValue: (row) => row.sold / row.supply,
227
+ sortValue: (row) => row.used / row.seats,
228
228
  cell: (row) => (
229
229
  <Row gap={3} align="center" className="min-w-40">
230
230
  <Meter
231
- value={row.sold}
232
- max={row.supply}
231
+ value={row.used}
232
+ max={row.seats}
233
233
  size="sm"
234
- tone={row.sold === row.supply ? "success" : "primary"}
235
- aria-label={`${row.title} sold`}
236
- getAriaValueText={(_, v) => `${v} of ${row.supply} sold`}
234
+ tone={row.used === row.seats ? "success" : "primary"}
235
+ aria-label={`${row.title} seats used`}
236
+ getAriaValueText={(_, v) => `${v} of ${row.seats} seats used`}
237
237
  className="flex-1"
238
238
  />
239
239
  <span className="text-mono-caption whitespace-nowrap text-muted-foreground">
240
- {row.sold}/{row.supply}
240
+ {row.used}/{row.seats}
241
241
  </span>
242
242
  </Row>
243
243
  ),
@@ -247,21 +247,21 @@ export function DashboardPage() {
247
247
  header: "Revenue",
248
248
  align: "end",
249
249
  sortable: true,
250
- sortValue: (row) => row.sold * row.price,
250
+ sortValue: (row) => row.used * row.price,
251
251
  cell: (row) =>
252
- row.sold > 0 ? (
253
- <span className="text-mono-sm">{usd.format(row.sold * row.price)}</span>
252
+ row.used > 0 ? (
253
+ <span className="text-mono-sm">{usd.format(row.used * row.price)}</span>
254
254
  ) : (
255
255
  <span className="text-mono-sm text-muted-foreground">—</span>
256
256
  ),
257
257
  },
258
258
  {
259
- key: "released",
260
- header: "Release",
259
+ key: "started",
260
+ header: "Started",
261
261
  align: "end",
262
262
  sortable: true,
263
263
  cell: (row) => (
264
- <span className="text-body-sm whitespace-nowrap text-muted-foreground">{shortDate(row.released)}</span>
264
+ <span className="text-body-sm whitespace-nowrap text-muted-foreground">{shortDate(row.started)}</span>
265
265
  ),
266
266
  },
267
267
  ]}
@@ -269,10 +269,10 @@ export function DashboardPage() {
269
269
  </CardContent>
270
270
  <CardFooter className="justify-between">
271
271
  <span className="text-caption text-muted-foreground">
272
- Showing {editions.length} of {editionCount} editions
272
+ Showing {accounts.length} of {accountCount} accounts
273
273
  </span>
274
- <Button variant="ghost" size="sm" bleed="end" render={<a href="/editions" />}>
275
- View all editions
274
+ <Button variant="ghost" size="sm" bleed="end" render={<a href="/accounts" />}>
275
+ View all accounts
276
276
  <Icon name="arrow-right" />
277
277
  </Button>
278
278
  </CardFooter>
@@ -303,21 +303,21 @@ export function DashboardPage() {
303
303
  </Card>
304
304
  <Card>
305
305
  <CardHeader>
306
- <CardTitle>Top collectors</CardTitle>
306
+ <CardTitle>Top customers</CardTitle>
307
307
  <CardDescription>By spend this month.</CardDescription>
308
308
  <CardAction>
309
- <Button variant="ghost" size="sm" render={<a href="/collectors" />}>
309
+ <Button variant="ghost" size="sm" render={<a href="/customers" />}>
310
310
  View all
311
311
  </Button>
312
312
  </CardAction>
313
313
  </CardHeader>
314
314
  <CardList>
315
- {collectors.map((person) => (
315
+ {customers.map((person) => (
316
316
  <Entity
317
317
  key={person.id}
318
318
  leading={<Avatar variant="art" size="sm" fallback={person.initials} alt="" />}
319
319
  title={person.name}
320
- subtitle={`${person.handle} · ${person.pieces} pieces`}
320
+ subtitle={`${person.handle} · ${person.seats} seats`}
321
321
  action={<span className="text-mono-sm">{usd.format(person.spent)}</span>}
322
322
  />
323
323
  ))}
@@ -329,7 +329,7 @@ export function DashboardPage() {
329
329
  <Card className="flex flex-col">
330
330
  <CardHeader>
331
331
  <CardTitle>Upcoming releases</CardTitle>
332
- <CardDescription>Scheduled to open to collectors.</CardDescription>
332
+ <CardDescription>Scheduled to ship to customers.</CardDescription>
333
333
  </CardHeader>
334
334
  <CardList>
335
335
  {upcoming.map((release) => (
@@ -342,7 +342,7 @@ export function DashboardPage() {
342
342
  ))}
343
343
  </CardList>
344
344
  <CardFooter className="mt-auto">
345
- <Button variant="outline" size="sm" render={<a href="/editions/new" />}>
345
+ <Button variant="outline" size="sm" render={<a href="/releases/new" />}>
346
346
  <Icon name="calendar" />
347
347
  Schedule a release
348
348
  </Button>
@@ -351,7 +351,7 @@ export function DashboardPage() {
351
351
  <Card className="xl:col-span-2">
352
352
  <CardHeader>
353
353
  <CardTitle>Activity</CardTitle>
354
- <CardDescription>Collects, tips, and follows.</CardDescription>
354
+ <CardDescription>Seats, upgrades, and invoices.</CardDescription>
355
355
  <CardAction>
356
356
  <Button variant="ghost" size="sm" render={<a href="/activity" />}>
357
357
  View all