division-ds 0.1.0 → 0.1.2

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.
package/AGENTS.md CHANGED
@@ -60,7 +60,7 @@ docs write `src/components/atoms/button/Button`, and from `src/pages/Screen.tsx`
60
60
  `'../lib/money'` from there. The tier barrels (`components/atoms`, `/molecules`,
61
61
  `/organisms`) are for the system's own composition; the organisms barrel pulls the chart library
62
62
  into whatever imports it. Icons come from `lucide-react`, with `aria-hidden="true"` unless the
63
- icon is the only content.
63
+ icon is the only content. Installed with shadcn, paths start `src/division-ds/` instead of `src/`.
64
64
 
65
65
  **Colour.** Every colour is a semantic token inside a Tailwind arbitrary value:
66
66
  `text-[var(--color-text-primary)]`, `bg-[var(--color-surface-base)]`. Never a hex, never
@@ -69,9 +69,10 @@ have not seen in `llms-tokens.txt`. An invented name fails silently in the brows
69
69
  `npm run verify`.
70
70
 
71
71
  **Providers.** One `<Toaster>` and one `<TooltipProvider>` sit at the root of the app (`AppShell`,
72
- `DemoLayout`). Assume both, including for a screen you are writing that is not mounted anywhere
73
- yet: it will be mounted under one of the two shells. Call `useToast()` freely; do not mount a
74
- second `Toaster`, and do not drop a toast or a tooltip because you cannot see the provider.
72
+ `DemoLayout` in this repository). Assume both, including for a screen you are writing that is not
73
+ mounted anywhere yet: it will be mounted under one of the two shells. Call `useToast()` freely; do
74
+ not mount a second `Toaster`, and do not drop a toast or a tooltip because you cannot see the
75
+ provider. A project that uses the components mounts both itself (step 4 of `install.md`).
75
76
 
76
77
  **Focus.** Anything focusable that you style yourself gets
77
78
  `focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-focus-ring)]`.
@@ -106,7 +107,8 @@ is the default. Tokens switch on their own, so a screen built from tokens needs
106
107
  The shell paints the page background (`surface.base`) and supplies the gutter; a screen sets its
107
108
  own `max-w-*` and vertical spacing, and no page background. A form or an overlay is the
108
109
  exception: it paints `surface.base` on itself, so its field colours never depend on what is
109
- behind it.
110
+ behind it. In a project that uses the components, your own root does this
111
+ (step 4 of `install.md`).
110
112
 
111
113
  **Type.** Three families, as utilities: `font-display` (headings, figures), `font-body` (the
112
114
  default, set by the shell), `font-mono` (labels, code, tabular numbers). A page title is
@@ -119,15 +121,17 @@ and spacing utilities are fine; only colour is restricted to tokens.
119
121
  **Cards.** To highlight or select a `Card`, set the border inline with a token:
120
122
  `style={{ border: '2px solid var(--color-accent-signal-fill)' }}`. A border utility class does
121
123
  not win against the panel's own border. `interactive` is only for a card that holds one link or
122
- button.
124
+ button; give that link `className={cardLinkClass}` (from `Card`) to stretch it over the card.
123
125
 
124
126
  **Links.** Components that render links take `renderLink`, defaulting to a plain `<a>`. Under a
125
- router, pass an adapter (`src/demo/routerLink.tsx` is the example). No component imports a router.
127
+ router, pass an adapter: `({ href, ...rest }) => <Link to={href} {...rest} />`. No component
128
+ imports a router.
126
129
 
127
130
  **Charts and bundle size.** `ChartPanel` and `Sparkline` bring in recharts, a few hundred kB.
128
131
  Load the route or screen that uses them with `React.lazy` and a `<Suspense>` fallback, so recharts
129
132
  lands in its own chunk and screens without a chart never download it. Without that, Vite warns
130
- about a chunk over 500 kB; the warning is about load time only, and the build is fine.
133
+ about a chunk over 500 kB; the warning is about load time only, and the build is fine. On a
134
+ live page, data may change as often as you like: a chart animates its first draw only.
131
135
 
132
136
  <!-- topic: forms -->
133
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "division-ds",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Checks a project that uses Division DS components: raw colours, invented tokens, touching buttons, unknown props.",
5
5
  "license": "MIT",
6
6
  "author": "Victor Ilioiu",
@@ -3861,7 +3861,8 @@
3861
3861
  "CardFooter",
3862
3862
  "CardHeader",
3863
3863
  "CardMedia",
3864
- "CardTitle"
3864
+ "CardTitle",
3865
+ "cardLinkClass"
3865
3866
  ],
3866
3867
  "from": "src/components/molecules/card/Card"
3867
3868
  }
@@ -3918,6 +3919,14 @@
3918
3919
  "description": "Passed through to the Card root DOM element."
3919
3920
  }
3920
3921
  ],
3922
+ "api": [
3923
+ {
3924
+ "name": "cardLinkClass",
3925
+ "kind": "const",
3926
+ "code": "/** The stretched link an interactive card needs, for the `a` (or button) in its title: the link\n * is the name's own, its `::after` covers the card, and the focus ring is drawn there, around the\n * whole card. The shape ProductCard uses. Exported so a consumer never copies the string. */\nexport const cardLinkClass =\n \"outline-none hover:underline underline-offset-2 after:absolute after:inset-0 after:content-[''] focus-visible:after:outline-2 focus-visible:after:outline-offset-2 focus-visible:after:outline-[var(--color-focus-ring)]\"",
3927
+ "from": "src/components/molecules/card/Card"
3928
+ }
3929
+ ],
3921
3930
  "keyboard": [
3922
3931
  {
3923
3932
  "keys": [
@@ -4012,7 +4021,7 @@
4012
4021
  },
4013
4022
  {
4014
4023
  "caption": "Interactive: the whole card is one link. Hover over it, or press Tab to reach it",
4015
- "source": "const STRETCHED =\n \"outline-none hover:underline underline-offset-2 after:absolute after:inset-0 after:content-[''] focus-visible:after:outline-2 focus-visible:after:outline-offset-2 focus-visible:after:outline-[var(--color-focus-ring)]\"\n\nfunction DepotLink({ title, note, href }: { title: string; note: string; href: string }) {\n return (\n <Card interactive className=\"h-full\">\n <CardBody className=\"flex flex-col gap-1\">\n <CardTitle>\n <a href={href} className={STRETCHED}>\n {title}\n </a>\n </CardTitle>\n <CardDescription>{note}</CardDescription>\n </CardBody>\n </Card>\n )\n}\n\n<div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4 w-full\">\n <DepotLink title=\"Ridge depot\" note=\"Open until 18:00. Twelve kits ready.\" href=\"#ridge-depot\" />\n <DepotLink title=\"Quarry depot\" note=\"Open until 20:00. Four kits ready.\" href=\"#quarry-depot\" />\n <DepotLink title=\"Mill depot\" note=\"Closed today. Opens tomorrow, 08:00.\" href=\"#mill-depot\" />\n</div>"
4024
+ "source": "function DepotLink({ title, note, href }: { title: string; note: string; href: string }) {\n return (\n <Card interactive className=\"h-full\">\n <CardBody className=\"flex flex-col gap-1\">\n <CardTitle>\n <a href={href} className={cardLinkClass}>\n {title}\n </a>\n </CardTitle>\n <CardDescription>{note}</CardDescription>\n </CardBody>\n </Card>\n )\n}\n\n<div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4 w-full\">\n <DepotLink title=\"Ridge depot\" note=\"Open until 18:00. Twelve kits ready.\" href=\"#ridge-depot\" />\n <DepotLink title=\"Quarry depot\" note=\"Open until 20:00. Four kits ready.\" href=\"#quarry-depot\" />\n <DepotLink title=\"Mill depot\" note=\"Closed today. Opens tomorrow, 08:00.\" href=\"#mill-depot\" />\n</div>"
4016
4025
  },
4017
4026
  {
4018
4027
  "caption": "As a section",
@@ -7297,6 +7306,11 @@
7297
7306
  "type": "{ label: string; altText: string; onAction: () => void }",
7298
7307
  "description": "Optional. Radix (the library the toast is built on) requires altText: what to do somewhere else if someone can't reach the toast in time."
7299
7308
  },
7309
+ {
7310
+ "name": "replace (ToastOptions)",
7311
+ "type": "string",
7312
+ "description": "Optional. Closes any open toast raised with the same string as this one opens, so an action repeated quickly (\"Alert acknowledged\" three times) shows one toast instead of a stack."
7313
+ },
7300
7314
  {
7301
7315
  "name": "useToast()",
7302
7316
  "type": "{ toast: (options: ToastOptions) => string; dismiss: (id: string) => void }",
@@ -7307,7 +7321,7 @@
7307
7321
  {
7308
7322
  "name": "ToastOptions",
7309
7323
  "kind": "type",
7310
- "code": "export type ToastOptions = {\n title: string\n description?: string\n tone?: Tone\n /** ms before it leaves on its own. Default 5000; 8000 for hostile. */\n duration?: number\n /** Radix requires altText: the way to do the same thing for someone who cannot reach the toast\n * before it leaves. */\n action?: { label: string; altText: string; onAction: () => void }\n}",
7324
+ "code": "export type ToastOptions = {\n title: string\n description?: string\n tone?: Tone\n /** ms before it leaves on its own. Default 5000; 8000 for hostile. */\n duration?: number\n /** Radix requires altText: the way to do the same thing for someone who cannot reach the toast\n * before it leaves. */\n action?: { label: string; altText: string; onAction: () => void }\n /** Any open toast raised with the same string closes as this one opens, so a repeated action\n * (\"Alert acknowledged\" three times) shows one toast, not a stack over the page. */\n replace?: string\n}",
7311
7325
  "from": "src/components/molecules/toast/Toast"
7312
7326
  },
7313
7327
  {
@@ -7341,6 +7355,7 @@
7341
7355
  "Use a toast to confirm something the person just did.",
7342
7356
  "Give every action an altText that says where else it can be done.",
7343
7357
  "Keep the title under about six words.",
7358
+ "After an action runs, focus goes back to where it was before it entered the toast. If the action changes the page (Undo puts a row back), move focus to what changed yourself.",
7344
7359
  "A toast follows the innermost open overlay (a dialog or panel on top of the page). If it's raised while one is open, or is already showing when one opens, it shows inside that overlay, and it moves back to the page corner when the overlay closes. AlertDialog is the exception: its toasts always show in the page corner, above the scrim (the dimmed layer behind the dialog)."
7345
7360
  ],
7346
7361
  "donts": [
@@ -8311,6 +8326,12 @@
8311
8326
  "default": "[]",
8312
8327
  "description": "Line and bar only. Shades stretches of the x-axis, such as night hours on a 24-hour chart. from and to are x-axis values (values of xKey), both included. When from comes after to in the data, the band wraps past the end: 22:00 to 06:00 shades 22:00 to the last point and the first point to 06:00. A band naming a value that isn't in the data is skipped. Every band is listed under the chart with its label, so screen readers hear it too."
8313
8328
  },
8329
+ {
8330
+ "name": "limits",
8331
+ "type": "{ value: number; label?: string }[]",
8332
+ "default": "[]",
8333
+ "description": "Line and bar only, not at size sm. Draws a dashed horizontal line at a y value, such as an alert limit of 35 µg/m³. The y-axis grows to include it, so a limit above every data point stays in view. Every limit is listed under the chart with its label and its value formatted by formatValue, so screen readers hear it too."
8334
+ },
8314
8335
  {
8315
8336
  "name": "headingLevel",
8316
8337
  "type": "2 | 3 | 4",
@@ -8419,7 +8440,7 @@
8419
8440
  },
8420
8441
  {
8421
8442
  "caption": "A shaded band: night hours on a 24-hour series (the band wraps past midnight)",
8422
- "source": "type Hour = { hour: string; pm25: number }\n\nconst PM25 = [9, 8, 7, 7, 8, 11, 17, 26, 31, 24, 18, 15, 14, 13, 14, 16, 21, 29, 34, 27, 19, 14, 11, 10]\n\nconst HOURS: Hour[] = PM25.map((pm25, hour) => ({ hour: `${String(hour).padStart(2, '0')}:00`, pm25 }))\n\n<ChartPanel<Hour>\n title=\"Fine particles today\"\n summary=\"PM2.5 peaked at the morning and evening rush hours, 31 and 34 µg/m³, and stayed lowest overnight.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n data={HOURS}\n xKey=\"hour\"\n xLabel=\"Hour\"\n series={[{ key: 'pm25', label: 'PM2.5 (µg/m³)' }]}\n bands={[{ from: '22:00', to: '06:00', label: 'Night' }]}\n headingLevel={4}\n/>"
8443
+ "source": "type Hour = { hour: string; pm25: number }\n\nconst PM25 = [9, 8, 7, 7, 8, 11, 17, 26, 31, 24, 18, 15, 14, 13, 14, 16, 21, 29, 34, 27, 19, 14, 11, 10]\n\nconst HOURS: Hour[] = PM25.map((pm25, hour) => ({ hour: `${String(hour).padStart(2, '0')}:00`, pm25 }))\n\n<ChartPanel<Hour>\n title=\"Fine particles today\"\n summary=\"PM2.5 peaked at the morning and evening rush hours, 31 and 34 µg/m³, and stayed lowest overnight.\"\n kind=\"line\"\n area\n curve=\"smooth\"\n data={HOURS}\n xKey=\"hour\"\n xLabel=\"Hour\"\n series={[{ key: 'pm25', label: 'PM2.5 (µg/m³)' }]}\n bands={[{ from: '22:00', to: '06:00', label: 'Night' }]}\n limits={[{ value: 35, label: 'Alert limit (µg/m³)' }]}\n headingLevel={4}\n/>"
8423
8444
  },
8424
8445
  {
8425
8446
  "caption": "Small: stat cards, each with a sparkline (a tiny line chart with no axes)",
@@ -154,6 +154,9 @@ async function check(options) {
154
154
  console.error('Each component\'s props, values and examples: /docs/components/<slug>.md. Tokens: public/manifest/tokens.json.')
155
155
  } else {
156
156
  console.log(`division-ds check passed: ${files.length} file(s), ${index.size} components, ${tokenNames.length} tokens, 0 findings.`)
157
+ // It reads code, not the running page: say so, or a pass reads as "the screen works" (the
158
+ // 2026-10-20 outside test shipped three runtime bugs past a clean check).
159
+ console.log('Checked: colours, tokens, button spacing and props. Not checked: how the screen behaves; run the review checklist (commands/review.md) in a browser.')
157
160
  }
158
161
  process.exit(findings.length > 0 ? 1 : 0)
159
162
  }
@@ -416,11 +416,12 @@ const SKILL_COMMANDS = {
416
416
  '- [ ] **Buttons.** Two or more side by side share a parent with a gap. One primary button per view. A busy button takes `loading`; a disabled one says why nearby. Icon-only actions are `IconButton` with a `label`.',
417
417
  '- [ ] **Forms.** Every `Input`, `Textarea`, `Select`, `MultiSelect` and `QuantityStepper` is inside a `FormField`; `Switch`, `Checkbox` and `Radio` have an `id` and a `<label htmlFor>`. A failed submit shows `ErrorSummary` and focuses it.',
418
418
  '- [ ] **Status.** Every status has a word, not only a colour.',
419
- '- [ ] **Focus.** Everything interactive is reached by Tab in reading order and shows the focus ring; anything styled by hand has the `focus-visible:outline-*` classes. An overlay hands focus back to what opened it.',
419
+ '- [ ] **Focus.** Everything interactive is reached by Tab in reading order and shows the focus ring; anything styled by hand has the `focus-visible:outline-*` classes. An overlay hands focus back to what opened it. A button that removes its own row or item (Acknowledge, Delete, Dismiss) moves focus to the next one, else the previous one, else the empty state; focus never drops to the page.',
420
420
  '- [ ] **Text size.** Secondary text is 14px or larger; 11 to 12px only for uppercase mono labels.',
421
421
  '- [ ] **320px.** At 320px wide nothing scrolls sideways (`document.documentElement.scrollWidth <= 320`) and button rows wrap.',
422
422
  '- [ ] **Both themes.** It reads in dark and in light (`data-theme="light"` on `<html>`).',
423
423
  '- [ ] **Motion.** Anything animated by hand stops under reduced motion; nothing moves on its own.',
424
+ '- [ ] **Live data.** Values that update on their own change in place, with no announcement for each number; a change of status (good to unhealthy) is announced once, through one polite live region (`role="status"`). Charts animate only their first draw, so new data every few seconds is fine.',
424
425
  '',
425
426
  '## Check',
426
427
  '',
@@ -502,6 +503,10 @@ export function skillFiles(split, componentCount) {
502
503
  '',
503
504
  `> Load when ${command.when}. Generated by \`npm run agent-docs\`; the procedure lives in scripts/lib/agent-docs.mjs.`,
504
505
  '',
506
+ // A project that installed the components has no public/ folder of ours (the 2026-10-20
507
+ // outside test followed these steps to files that did not exist).
508
+ `Paths starting \`public/\` are in the Division DS repository. In a project that uses the components, read them online: \`public/llms.txt\` is \`${DOCS_SITE}llms.txt\`, and so on.`,
509
+ '',
505
510
  ...command.body,
506
511
  '',
507
512
  ].join('\n')