division-ds 0.2.0 → 0.3.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.
- package/AGENTS.md +11 -5
- package/division-ds-0.3.0.tgz +0 -0
- package/package.json +1 -1
- package/public/docs/components/accordion.md +2 -0
- package/public/docs/components/ai-label.md +2 -0
- package/public/docs/components/alert-dialog.md +2 -0
- package/public/docs/components/alert.md +2 -0
- package/public/docs/components/approval-request.md +2 -0
- package/public/docs/components/auth-form.md +2 -0
- package/public/docs/components/avatar-group.md +2 -0
- package/public/docs/components/avatar.md +2 -0
- package/public/docs/components/badge.md +2 -0
- package/public/docs/components/breadcrumbs.md +2 -0
- package/public/docs/components/button.md +2 -0
- package/public/docs/components/card.md +2 -0
- package/public/docs/components/carousel.md +2 -0
- package/public/docs/components/cart.md +2 -0
- package/public/docs/components/chart-panel.md +2 -0
- package/public/docs/components/chat-message.md +3 -0
- package/public/docs/components/chat-thread.md +2 -0
- package/public/docs/components/checkbox.md +2 -0
- package/public/docs/components/checkout-form.md +2 -0
- package/public/docs/components/command-palette.md +6 -1
- package/public/docs/components/data-table.md +2 -0
- package/public/docs/components/date-picker.md +166 -0
- package/public/docs/components/date-presets.md +111 -0
- package/public/docs/components/date-range-picker.md +135 -0
- package/public/docs/components/description-list.md +2 -0
- package/public/docs/components/dialog.md +2 -0
- package/public/docs/components/divider.md +2 -0
- package/public/docs/components/dropdown-menu.md +2 -0
- package/public/docs/components/empty-state.md +2 -0
- package/public/docs/components/footer.md +2 -0
- package/public/docs/components/form-field.md +4 -1
- package/public/docs/components/icon-button.md +2 -0
- package/public/docs/components/input.md +2 -0
- package/public/docs/components/kanban-board.md +2 -0
- package/public/docs/components/kbd.md +2 -0
- package/public/docs/components/link.md +2 -0
- package/public/docs/components/list-item.md +3 -0
- package/public/docs/components/loading-animation.md +2 -0
- package/public/docs/components/multi-select.md +2 -0
- package/public/docs/components/navbar.md +2 -0
- package/public/docs/components/newsletter-form.md +2 -0
- package/public/docs/components/page-header.md +2 -0
- package/public/docs/components/pagination.md +2 -0
- package/public/docs/components/product-card.md +3 -0
- package/public/docs/components/progress.md +3 -1
- package/public/docs/components/prompt-input.md +2 -0
- package/public/docs/components/quantity-stepper.md +2 -0
- package/public/docs/components/radio.md +38 -1
- package/public/docs/components/rating.md +2 -0
- package/public/docs/components/score-ring.md +2 -0
- package/public/docs/components/search.md +2 -0
- package/public/docs/components/section-heading.md +2 -0
- package/public/docs/components/segmented-control.md +2 -0
- package/public/docs/components/select.md +3 -1
- package/public/docs/components/sheet.md +2 -0
- package/public/docs/components/sidebar.md +2 -0
- package/public/docs/components/skeleton.md +2 -0
- package/public/docs/components/slider.md +3 -1
- package/public/docs/components/sparkline.md +2 -0
- package/public/docs/components/spinner.md +4 -2
- package/public/docs/components/stat.md +2 -0
- package/public/docs/components/status-tracker.md +2 -0
- package/public/docs/components/stepper.md +2 -0
- package/public/docs/components/switch.md +4 -2
- package/public/docs/components/tabs.md +2 -0
- package/public/docs/components/tag.md +2 -0
- package/public/docs/components/textarea.md +2 -0
- package/public/docs/components/time-field.md +127 -0
- package/public/docs/components/timeline.md +2 -0
- package/public/docs/components/toast.md +2 -0
- package/public/docs/components/tool-call.md +2 -0
- package/public/docs/components/tooltip.md +2 -0
- package/public/manifest/components.json +947 -72
- package/public/manifest/tokens.json +36 -5
- package/scripts/division-ds.mjs +5 -5
- package/scripts/lib/agent-docs.mjs +49 -6
- package/scripts/lib/ai-copy.mjs +15 -1
- package/scripts/lib/check-text.mjs +13 -0
- package/scripts/lib/mcp-server.mjs +2 -1
- package/scripts/lib/mcp-tools.mjs +25 -12
- package/scripts/lib/props-validator.mjs +2 -1
- package/scripts/lib/search-score.mjs +24 -0
- package/scripts/lib/token-discipline.mjs +10 -3
- package/skills/division-ds/SKILL.md +2 -2
- package/skills/division-ds/commands/add.md +1 -1
- package/skills/division-ds/commands/build.md +1 -1
- package/skills/division-ds/commands/fix.md +1 -1
- package/skills/division-ds/commands/review.md +2 -2
- package/skills/division-ds/topics/ai.md +2 -1
- package/skills/division-ds/topics/forms.md +3 -0
- package/skills/division-ds/topics/layout.md +3 -1
- package/division-ds-0.2.0.tgz +0 -0
|
@@ -564,7 +564,11 @@
|
|
|
564
564
|
"name": "--color-accent-signal-fill",
|
|
565
565
|
"layer": "semantic",
|
|
566
566
|
"value": "#f2550f",
|
|
567
|
-
"use": "A solid block in the brand
|
|
567
|
+
"use": "A solid block in the brand colour, exactly as chosen: the primary Button, IconButton, a solid signal Badge. Put accent.signal.ink on it, never text.primary. Never use it as the only sign of a state; use accent.signal.indicator.",
|
|
568
|
+
"measured": {
|
|
569
|
+
"dark": "The brand colour, exactly as chosen: Button, IconButton and solid Badge backgrounds, and brand decoration. Text on it is accent.signal.ink. Never the only sign of a state; that is accent.signal.indicator. The theme builder never moves it.",
|
|
570
|
+
"light": "The brand colour, exactly as chosen: Button, IconButton and solid Badge backgrounds, and brand decoration. Text on it is accent.signal.ink. Never the only sign of a state; that is accent.signal.indicator. The theme builder never moves it."
|
|
571
|
+
}
|
|
568
572
|
},
|
|
569
573
|
{
|
|
570
574
|
"name": "--color-accent-signal-text",
|
|
@@ -585,7 +589,28 @@
|
|
|
585
589
|
"value": "#0a0602",
|
|
586
590
|
"use": "Text and icons on accent.signal.fill.",
|
|
587
591
|
"measured": {
|
|
588
|
-
"dark": "5.85:1 on accent.signal.fill"
|
|
592
|
+
"dark": "5.85:1 on accent.signal.fill",
|
|
593
|
+
"light": "5.85:1 on accent.signal.fill"
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
"name": "--color-accent-signal-indicator",
|
|
598
|
+
"layer": "semantic",
|
|
599
|
+
"value": "#f2550f",
|
|
600
|
+
"use": "The accent where colour alone shows a state or value: a checked control, Switch on, Progress, Slider, Rating stars, the current item in Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows. Put accent.signal.indicator-ink on it.",
|
|
601
|
+
"measured": {
|
|
602
|
+
"dark": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals. 5.80:1 on surface.base. Same stop as accent.signal.fill here; the theme builder moves it to a nearby stop when a chosen accent is too pale or too dark to reach 3:1, and leaves accent.signal.fill alone.",
|
|
603
|
+
"light": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals. 3.14:1 on surface.base. Same stop as accent.signal.fill here; the theme builder moves it to a nearby stop when a chosen accent is too pale or too dark to reach 3:1, and leaves accent.signal.fill alone."
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
"name": "--color-accent-signal-indicator-ink",
|
|
608
|
+
"layer": "semantic",
|
|
609
|
+
"value": "#0a0602",
|
|
610
|
+
"use": "Text and icons on accent.signal.indicator.",
|
|
611
|
+
"measured": {
|
|
612
|
+
"dark": "Text and icons on accent.signal.indicator. 5.85:1. The theme builder picks white or this dark ink, whichever reaches 4.5:1.",
|
|
613
|
+
"light": "Text and icons on accent.signal.indicator. 5.85:1. The theme builder picks white or this dark ink, whichever reaches 4.5:1."
|
|
589
614
|
}
|
|
590
615
|
},
|
|
591
616
|
{
|
|
@@ -595,7 +620,7 @@
|
|
|
595
620
|
"use": "The faint orange hover wash under a nav item or an inactive tab.",
|
|
596
621
|
"measured": {
|
|
597
622
|
"dark": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated. The focus ring and a checked control's fill drop under 3:1 on it in light theme, so neither is ever drawn on it.",
|
|
598
|
-
"light": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated.
|
|
623
|
+
"light": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated. A checked control's indicator drops under 3:1 on it in light theme and the focus ring is not gated on it, so neither is ever drawn on it."
|
|
599
624
|
}
|
|
600
625
|
},
|
|
601
626
|
{
|
|
@@ -782,8 +807,14 @@
|
|
|
782
807
|
{
|
|
783
808
|
"name": "--color-focus-ring",
|
|
784
809
|
"layer": "semantic",
|
|
785
|
-
"value":
|
|
786
|
-
|
|
810
|
+
"value": {
|
|
811
|
+
"dark": "#f2550f",
|
|
812
|
+
"light": "#c6440b"
|
|
813
|
+
},
|
|
814
|
+
"use": "The keyboard focus outline. Use it only in focus-visible:outline-[var(--color-focus-ring)], with a 2px outline and a 2px offset.",
|
|
815
|
+
"measured": {
|
|
816
|
+
"light": "4.51:1 on surface.base. A step darker than accent.signal.fill, which reaches only 3.14:1 on the light page: the ring is the one mark every keyboard user depends on, so it gets a margin."
|
|
817
|
+
}
|
|
787
818
|
},
|
|
788
819
|
{
|
|
789
820
|
"name": "--color-chart-series-1",
|
package/scripts/division-ds.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { existsSync, globSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
20
20
|
import { dirname, join, relative, resolve } from 'node:path'
|
|
21
21
|
import { fileURLToPath } from 'node:url'
|
|
22
|
-
import { extractRules } from './lib/agent-docs.mjs'
|
|
22
|
+
import { DOCS_SITE, extractRules, SKILL_SOURCE } from './lib/agent-docs.mjs'
|
|
23
23
|
import { checkText } from './lib/check-text.mjs'
|
|
24
24
|
import { createServer } from './lib/mcp-server.mjs'
|
|
25
25
|
import { createTools } from './lib/mcp-tools.mjs'
|
|
@@ -28,14 +28,14 @@ import { applyPlan, diff, planRules, planSkill, SKILL_TARGETS, TARGETS } from '.
|
|
|
28
28
|
|
|
29
29
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
30
30
|
const DEFAULT_MANIFEST = join(ROOT, 'public/manifest/components.json')
|
|
31
|
-
const CHECKED = /\.(tsx|jsx|ts)$/
|
|
31
|
+
const CHECKED = /\.(tsx|jsx|ts|css)$/
|
|
32
32
|
const SKIPPED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'coverage'])
|
|
33
33
|
|
|
34
34
|
const HELP = `division-ds: Division DS gates and agent rules for a project that uses the components.
|
|
35
35
|
|
|
36
36
|
Usage:
|
|
37
37
|
division-ds check [paths...] [--manifest <path|url>] [--tokens <path|url>] [--json]
|
|
38
|
-
Checks .tsx, .jsx and .
|
|
38
|
+
Checks .tsx, .jsx, .ts and .css files (default: ./src; a .css file gets the colour gate only). A path may be a file, a directory or a
|
|
39
39
|
glob ("src/**/*.tsx"). Runs five gates and exits 1 on any finding:
|
|
40
40
|
token-discipline raw colours, Tailwind palette classes, token names that do not exist
|
|
41
41
|
button-spacing two Buttons/IconButtons side by side with no gap on their parent
|
|
@@ -149,12 +149,12 @@ async function check(options) {
|
|
|
149
149
|
if (gate === 'button-spacing') console.error(` ${ofGate[0].fix}`)
|
|
150
150
|
console.error('')
|
|
151
151
|
}
|
|
152
|
-
console.error(
|
|
152
|
+
console.error(`Each component's props, values and examples: ${DOCS_SITE}docs/components/<slug>.md. Tokens: ${DOCS_SITE}llms-tokens.txt.`)
|
|
153
153
|
} else {
|
|
154
154
|
console.log(`division-ds check passed: ${files.length} file(s), ${index.size} components, ${tokenNames.length} tokens, 0 findings.`)
|
|
155
155
|
// It reads code, not the running page: say so, or a pass reads as "the screen works" (the
|
|
156
156
|
// 2026-10-20 outside test shipped three runtime bugs past a clean check).
|
|
157
|
-
console.log(
|
|
157
|
+
console.log(`Checked: colours, tokens, button spacing, inline host cards, props and AI copy. Not checked: how the screen behaves; run the review checklist in a browser (commands/review.md from \`npx division-ds skill\`, or ${SKILL_SOURCE}commands/review.md).`)
|
|
158
158
|
}
|
|
159
159
|
process.exit(findings.length > 0 ? 1 : 0)
|
|
160
160
|
}
|
|
@@ -8,6 +8,8 @@ const TIER_NOUN = { atoms: 'Atom', molecules: 'Molecule', organisms: 'Organism'
|
|
|
8
8
|
export const DOCS_SITE = 'https://victor-ilioiu.vercel.app/systems/division-ds/'
|
|
9
9
|
/** Where shadcn downloads the components from: public/r/ in the public GitHub repository. */
|
|
10
10
|
export const REGISTRY = 'https://raw.githubusercontent.com/Victor-Ilioiu/division-ds/main/public/r'
|
|
11
|
+
/** The skill's files in the public repository, for a reader who has the package but not the skill. */
|
|
12
|
+
export const SKILL_SOURCE = 'https://github.com/Victor-Ilioiu/division-ds/blob/main/skills/division-ds/'
|
|
11
13
|
|
|
12
14
|
/** A table cell: one line, with the pipes that would end the cell escaped. */
|
|
13
15
|
const cell = text => String(text ?? '').replace(/\s*\n\s*/g, ' ').replace(/\|/g, '\\|')
|
|
@@ -19,14 +21,50 @@ export function importLines(entry) {
|
|
|
19
21
|
return entry.imports.map(({ names, from }) => `import { ${names.join(', ')} } from '${from}'`).join('\n')
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/** The other components a page documents, by export name: ChatMessage's ResponseFeedback,
|
|
25
|
+
* ProductCard's ProductGrid. Parts named after the component (CardBody, RadioGroup) and helpers
|
|
26
|
+
* (lower case) are left out. The 2026-10-29 outside test could not find ResponseFeedback because
|
|
27
|
+
* only its parent was listed anywhere an agent looks first. */
|
|
28
|
+
export function alsoExports(entry) {
|
|
29
|
+
return entry.imports
|
|
30
|
+
.flatMap(({ names }) => names)
|
|
31
|
+
.filter(name => /^[A-Z][a-z]/.test(name) && !name.startsWith(entry.name) && !name.endsWith('Provider'))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The docs site's top-bar search reads this, fetched the first time search opens. Slim on purpose:
|
|
35
|
+
* the names, the one-line summaries and the prop names and descriptions the shared scorer
|
|
36
|
+
* (search-score.mjs) ranks on, and the screen-facing tokens. Primitives are left out, as in the
|
|
37
|
+
* MCP search: they are never the answer for a screen. */
|
|
38
|
+
export function searchIndex(entries, tokens) {
|
|
39
|
+
const index = {
|
|
40
|
+
generatedBy: 'scripts/build-agent-docs.mjs',
|
|
41
|
+
components: entries.map(entry => ({
|
|
42
|
+
name: entry.name,
|
|
43
|
+
slug: entry.slug,
|
|
44
|
+
tier: entry.tier,
|
|
45
|
+
summary: entry.summary,
|
|
46
|
+
useWhen: entry.useWhen,
|
|
47
|
+
also: alsoExports(entry),
|
|
48
|
+
props: entry.props.map(prop => ({ name: prop.name, description: prop.description ?? '' })),
|
|
49
|
+
})),
|
|
50
|
+
tokens: tokens.tokens.filter(token => token.layer !== 'primitive').map(token => ({ name: token.name, layer: token.layer, use: token.use ?? '' })),
|
|
51
|
+
}
|
|
52
|
+
return JSON.stringify(index) + '\n'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Said under every Import block: the paths are the repository's, not an installed project's. */
|
|
56
|
+
export const INSTALLED_PATHS = 'Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.'
|
|
57
|
+
|
|
22
58
|
/** One component as a markdown page. `entry` is a manifest entry (see `manifestEntry`). */
|
|
23
59
|
export function componentMarkdown(entry) {
|
|
24
60
|
const out = [`# ${entry.name}`, '', `> ${entry.summary}`, '']
|
|
25
61
|
out.push(`- **Best for:** ${entry.useWhen}`, `- **Not for:** ${entry.notFor}`)
|
|
62
|
+
const also = alsoExports(entry)
|
|
63
|
+
if (also.length) out.push(`- **Also on this page:** ${also.map(code).join(', ')}`)
|
|
26
64
|
out.push(`- ${TIER_NOUN[entry.tier]}. Source: \`${entry.source}\``)
|
|
27
65
|
out.push(`- Rendered docs page: \`/components/${entry.slug}\``, '')
|
|
28
66
|
|
|
29
|
-
out.push('## Import', '', fence(importLines(entry)), '')
|
|
67
|
+
out.push('## Import', '', fence(importLines(entry)), '', INSTALLED_PATHS, '')
|
|
30
68
|
|
|
31
69
|
if (entry.requires?.length) {
|
|
32
70
|
out.push('## Requires', '', ...entry.requires.map(item => `- ${item}`), '')
|
|
@@ -108,8 +146,10 @@ const byTier = entries =>
|
|
|
108
146
|
export const firstSentence = text => text.match(/^.*?[.!?](?=\s|$)/)?.[0] ?? text
|
|
109
147
|
|
|
110
148
|
/** One component's line in llms.txt: what it is, then best for and not for. */
|
|
111
|
-
export const indexLine = entry =>
|
|
112
|
-
|
|
149
|
+
export const indexLine = entry => {
|
|
150
|
+
const also = alsoExports(entry)
|
|
151
|
+
return `- [${entry.name}](${entry.docs.replace(/^\//, '')}): ${firstSentence(entry.summary)} Best for: ${entry.useWhen} Not for: ${entry.notFor}${also.length ? ` Also: ${also.join(', ')}.` : ''}`
|
|
152
|
+
}
|
|
113
153
|
|
|
114
154
|
/** llms.txt, in the llmstxt.org shape: a title, a summary, then sections of links. */
|
|
115
155
|
export function llmsIndex(entries, { rules }) {
|
|
@@ -245,7 +285,10 @@ export function designFile(entries, manifest, { fonts, rules }) {
|
|
|
245
285
|
]
|
|
246
286
|
for (const [tier, group] of byTier(entries)) {
|
|
247
287
|
out.push(`## ${tier[0].toUpperCase()}${tier.slice(1)}`, '')
|
|
248
|
-
for (const entry of group)
|
|
288
|
+
for (const entry of group) {
|
|
289
|
+
const also = alsoExports(entry)
|
|
290
|
+
out.push(`- [${entry.name}](${entry.docs.replace(/^\//, '')}): ${firstSentence(entry.summary)}${also.length ? ` Also: ${also.join(', ')}.` : ''}`)
|
|
291
|
+
}
|
|
249
292
|
out.push('')
|
|
250
293
|
}
|
|
251
294
|
out.push('## Rules', '', 'In full: [llms-guidelines.txt](llms-guidelines.txt).', '', ...ruleHeadlines(rules).map(line => `- ${line}`), '')
|
|
@@ -462,7 +505,7 @@ const SKILL_COMMANDS = {
|
|
|
462
505
|
body: [
|
|
463
506
|
'Go through every line. A line that does not hold is a change to make, not a note to leave.',
|
|
464
507
|
'',
|
|
465
|
-
'- [ ] **States.** Every region has its empty, loading, error, success and disabled or no-permission state, as planned
|
|
508
|
+
'- [ ] **States.** Every region has its empty, loading, error, success and disabled or no-permission state, as planned before the code (the regions-and-states plan of `commands/build.md`). An empty list says what is empty and offers the action that fills it.',
|
|
466
509
|
'- [ ] **Headings.** One `PageHeader`, so one `h1`. Section headings continue the outline: `h2` under the `h1`, `h3` inside an `h2`, no level skipped. `SectionHeading` is given the level.',
|
|
467
510
|
'- [ ] **Tokens only.** No hex, `rgb()`, palette class or invented token name. `division-ds check` passes.',
|
|
468
511
|
'- [ ] **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`.',
|
|
@@ -557,7 +600,7 @@ export function skillFiles(split, componentCount) {
|
|
|
557
600
|
'',
|
|
558
601
|
// A project that installed the components has no public/ folder of ours (the 2026-10-20
|
|
559
602
|
// outside test followed these steps to files that did not exist).
|
|
560
|
-
`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.`,
|
|
603
|
+
`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. The other procedures named here (\`commands/build.md\` and the rest) are in the skill: \`${SKILL_SOURCE}commands/<name>.md\`, or \`npx division-ds skill\` to install them.`,
|
|
561
604
|
'',
|
|
562
605
|
...command.body,
|
|
563
606
|
'',
|
package/scripts/lib/ai-copy.mjs
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
// - `assistant-copy`: "answer", "please" or "!" in the interface text of the assistant components
|
|
17
17
|
// (ChatThread, ChatMessage, PromptInput, ApprovalRequest, ToolCall): their label-like string
|
|
18
18
|
// props only. Message bodies, a ToolCall's input and output and PromptInput's suggestions are
|
|
19
|
-
// content written by the person or the model, not the interface, and are never read.
|
|
19
|
+
// content written by the person or the model, not the interface, and are never read. A button
|
|
20
|
+
// placed inside one of them (a Button in ChatMessage's `actions`) is its interface too: its label
|
|
21
|
+
// and aria-label are checked the same way (missed by the 2026-10-29 outside test's probe).
|
|
20
22
|
// Only literals are read (a string attribute, JSX text, a ternary of two literals). A variable is
|
|
21
23
|
// not judged, the same as the props gate.
|
|
22
24
|
import ts from 'typescript'
|
|
@@ -63,11 +65,22 @@ export function findAiCopyViolations(text, path) {
|
|
|
63
65
|
const at = source.getLineAndCharacterOfPosition(node.getStart(source))
|
|
64
66
|
violations.push({ path, line: at.line + 1, column: at.character + 1, rule, found, fix })
|
|
65
67
|
}
|
|
68
|
+
// How many assistant components the walk is inside: a button in there speaks for the assistant.
|
|
69
|
+
let insideAssistant = 0
|
|
66
70
|
const checkCta = (node, value, where) => {
|
|
67
71
|
if (AI_WORD.test(value)) add(node, 'ai-in-cta', `${where}: "${value.trim()}"`, AI_FIX)
|
|
72
|
+
if (insideAssistant > 0) {
|
|
73
|
+
for (const word of ASSISTANT_WORDS) {
|
|
74
|
+
if (word.pattern.test(value)) add(node, 'assistant-copy', `${where} in an assistant component: "${value.trim()}"`, word.fix)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
68
77
|
}
|
|
78
|
+
const tagOf = node =>
|
|
79
|
+
ts.isJsxElement(node) ? node.openingElement.tagName.getText(source) : ts.isJsxSelfClosingElement(node) ? node.tagName.getText(source) : undefined
|
|
69
80
|
|
|
70
81
|
const visit = node => {
|
|
82
|
+
const opensAssistant = tagOf(node) in ASSISTANT_COPY
|
|
83
|
+
if (opensAssistant) insideAssistant++
|
|
71
84
|
if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
|
|
72
85
|
const tag = node.tagName.getText(source)
|
|
73
86
|
for (const attribute of node.attributes.properties) {
|
|
@@ -96,6 +109,7 @@ export function findAiCopyViolations(text, path) {
|
|
|
96
109
|
if (label.trim()) checkCta(node, label, `<${tag}> label`)
|
|
97
110
|
}
|
|
98
111
|
ts.forEachChild(node, visit)
|
|
112
|
+
if (opensAssistant) insideAssistant--
|
|
99
113
|
}
|
|
100
114
|
visit(source)
|
|
101
115
|
return violations.sort((a, b) => a.line - b.line || a.column - b.column)
|
|
@@ -10,10 +10,23 @@ import { findViolations as tokenDiscipline, formatViolation as formatTokenDiscip
|
|
|
10
10
|
export const BUTTON_SPACING_FIX =
|
|
11
11
|
'Wrap the buttons in one parent with a flex/grid class and a gap- utility (flex flex-wrap items-center gap-3), and move any margin from the buttons onto it.'
|
|
12
12
|
|
|
13
|
+
/** A stylesheet with the value of every declaration of a real token blanked, newlines kept so line
|
|
14
|
+
* numbers hold. Setting a token is how a project themes the system (the theme builder writes
|
|
15
|
+
* exactly that into index.css), so a literal colour there is the point; anywhere else in the file,
|
|
16
|
+
* or on a custom property the system does not have, it is reported as in any other file. */
|
|
17
|
+
export function blankTokenOverrides(text, tokenNames) {
|
|
18
|
+
const known = new Set(tokenNames)
|
|
19
|
+
return text.replace(/(--[a-z0-9-]+)(\s*:)([^;}]*)/g, (match, name, colon, value) =>
|
|
20
|
+
// The var() references stay, so a token set from a name that does not exist is still caught.
|
|
21
|
+
known.has(name) ? name + colon + value.replace(/var\([^)]*\)|[^\n]/g, part => (part.length > 1 ? part : ' ')) : match
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
/** Every finding in `text`. `path` decides which gates apply (button spacing and AI copy need JSX) and is
|
|
14
26
|
* echoed into each finding. `index` is buildIndex(manifest); `tokenNames` the token names. */
|
|
15
27
|
export function checkText(text, path, index, tokenNames) {
|
|
16
28
|
const findings = []
|
|
29
|
+
if (/\.css$/.test(path)) text = blankTokenOverrides(text, tokenNames)
|
|
17
30
|
for (const v of tokenDiscipline(text, path, tokenNames)) {
|
|
18
31
|
findings.push({ gate: 'token-discipline', path, line: v.line, rule: v.rule, found: v.found, fix: v.fix, text: formatTokenDiscipline(v) })
|
|
19
32
|
}
|
|
@@ -12,7 +12,8 @@ export const PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26', '202
|
|
|
12
12
|
|
|
13
13
|
const INSTRUCTIONS = `Division DS: a React 19, Tailwind 4 and Radix design system.
|
|
14
14
|
Before writing a component, call get_component for its props and examples. Use only tokens from get_tokens or search, as var(--name).
|
|
15
|
-
Before saying a screen is done, call validate_code on each file you wrote and fix every finding, then go through get_review_checklist
|
|
15
|
+
Before saying a screen is done, call validate_code on each file you wrote (.css too) and fix every finding, then go through get_review_checklist.
|
|
16
|
+
Shared helpers (theme toggle, form errors, money, links): https://victor-ilioiu.vercel.app/systems/division-ds/docs/helpers.md`
|
|
16
17
|
|
|
17
18
|
/** A JSON-RPC error reply. */
|
|
18
19
|
const failure = (id, code, message) => ({ jsonrpc: '2.0', id, error: { code, message } })
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
// Most design-system MCP servers stop at list / search / get docs (shadcn, Chakra, Dialtone,
|
|
6
6
|
// Blade, Vibe; surveyed 2026-10-25). `validate_code` is the one they lack: it runs the same gates
|
|
7
7
|
// as `division-ds check` on the code the agent has just written, before the agent says it is done.
|
|
8
|
+
import { alsoExports, INSTALLED_PATHS } from './agent-docs.mjs'
|
|
8
9
|
import { checkText } from './check-text.mjs'
|
|
9
10
|
import { contrastRatio } from './contrast.mjs'
|
|
10
11
|
import { buildIndex, closest } from './props-validator.mjs'
|
|
12
|
+
import { scorer, words } from './search-score.mjs'
|
|
11
13
|
|
|
12
14
|
export const REGISTRY_BASE = 'https://raw.githubusercontent.com/Victor-Ilioiu/division-ds/main/public/r'
|
|
13
15
|
const TIERS = ['atoms', 'molecules', 'organisms']
|
|
@@ -15,7 +17,6 @@ const LAYERS = ['primitive', 'spacing', 'motion', 'semantic', 'component']
|
|
|
15
17
|
// WCAG 2.2 minimums: SC 1.4.3 for text, SC 1.4.11 for the boundary of a control or a graphic.
|
|
16
18
|
const MINIMUMS = { normal: 4.5, large: 3, ui: 3 }
|
|
17
19
|
|
|
18
|
-
const words = text => String(text).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)
|
|
19
20
|
|
|
20
21
|
/** `context`: { manifest, tokens, version, readDoc(slug) -> string|undefined, readChecklist() -> string|undefined } */
|
|
21
22
|
export function createTools(context) {
|
|
@@ -30,7 +31,11 @@ export function createTools(context) {
|
|
|
30
31
|
|
|
31
32
|
function findComponent(name) {
|
|
32
33
|
const wanted = String(name ?? '').trim().toLowerCase()
|
|
33
|
-
return
|
|
34
|
+
return (
|
|
35
|
+
components.find(c => c.name.toLowerCase() === wanted || c.slug === wanted) ??
|
|
36
|
+
// A component documented on another's page (ResponseFeedback on ChatMessage's) opens that page.
|
|
37
|
+
components.find(c => alsoExports(c).some(other => other.toLowerCase() === wanted))
|
|
38
|
+
)
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
function unknownComponent(name) {
|
|
@@ -66,7 +71,12 @@ export function createTools(context) {
|
|
|
66
71
|
run({ tier } = {}) {
|
|
67
72
|
if (tier !== undefined && !TIERS.includes(tier)) throw new ToolError(`tier is one of ${TIERS.join(', ')}.`)
|
|
68
73
|
const rows = components.filter(c => !tier || c.tier === tier)
|
|
69
|
-
return rows
|
|
74
|
+
return rows
|
|
75
|
+
.map(c => {
|
|
76
|
+
const also = alsoExports(c)
|
|
77
|
+
return `- ${c.name} (${c.tier}): ${c.useWhen}${c.notFor ? ` Not for: ${c.notFor}` : ''}${also.length ? ` Also on its page: ${also.join(', ')}.` : ''}`
|
|
78
|
+
})
|
|
79
|
+
.join('\n')
|
|
70
80
|
},
|
|
71
81
|
},
|
|
72
82
|
{
|
|
@@ -85,7 +95,7 @@ export function createTools(context) {
|
|
|
85
95
|
const c = findComponent(name) ?? unknownComponent(name)
|
|
86
96
|
const imports = c.imports.map(i => `import { ${i.names.join(', ')} } from '${i.from}'`).join('\n')
|
|
87
97
|
const props = c.props.map(p => `- ${p.name}: ${p.type}${p.default ? ` = ${p.default}` : ''}. ${p.description ?? ''}`.trimEnd()).join('\n')
|
|
88
|
-
const head = `# ${c.name} (${c.tier})\n\n${c.summary}\n\n${imports}\n\
|
|
98
|
+
const head = `# ${c.name} (${c.tier})\n\n${c.summary}\n\n${imports}\n\n${INSTALLED_PATHS}\n\n## Props\n\n${props}`
|
|
89
99
|
if (section === 'props') return head
|
|
90
100
|
const page = context.readDoc?.(c.slug)
|
|
91
101
|
// The page is the full contract (examples, keyboard, do and don't, written for agents).
|
|
@@ -112,13 +122,16 @@ export function createTools(context) {
|
|
|
112
122
|
run({ query, limit = 10 }) {
|
|
113
123
|
const terms = words(query)
|
|
114
124
|
if (terms.length === 0) throw new ToolError('query has no words to search for.')
|
|
115
|
-
const score = (
|
|
116
|
-
const t = words(title).join(' ')
|
|
117
|
-
const b = words(body)
|
|
118
|
-
return terms.reduce((sum, term) => sum + (t.includes(term) ? 3 : 0) + b.filter(w => w.startsWith(term)).length, 0)
|
|
119
|
-
}
|
|
125
|
+
const score = scorer(query)
|
|
120
126
|
const hits = [
|
|
121
|
-
|
|
127
|
+
// The title holds every name on the page (ChatMessage and ResponseFeedback); the body adds
|
|
128
|
+
// the prop descriptions, where what a part does is written ("helpful", "thumbs up").
|
|
129
|
+
...components.map(c => {
|
|
130
|
+
const also = alsoExports(c)
|
|
131
|
+
const props = c.props.map(p => `${p.name} ${p.description ?? ''}`).join(' ')
|
|
132
|
+
const line = `${c.useWhen}${also.length ? ` Also on its page: ${also.join(', ')}.` : ''}`
|
|
133
|
+
return { kind: 'component', name: c.name, line, score: score(`${c.name} ${also.join(' ')}`, `${c.summary} ${c.useWhen}`, props, c.props.map(p => p.name).join(' ')) }
|
|
134
|
+
}),
|
|
122
135
|
// Primitives are not for screens (tokens.json's rule), so they never come up as an answer.
|
|
123
136
|
...tokens.tokens.filter(t => t.layer !== 'primitive').map(t => ({ kind: `${t.layer} token`, name: t.name, line: t.use ?? '', score: score(t.name, t.use ?? '') })),
|
|
124
137
|
]
|
|
@@ -158,13 +171,13 @@ export function createTools(context) {
|
|
|
158
171
|
type: 'object',
|
|
159
172
|
properties: {
|
|
160
173
|
code: { type: 'string', description: 'The whole file, imports included: props are matched through the imports.' },
|
|
161
|
-
filename: { type: 'string', description: 'Its name, e.g. "src/pages/Settings.tsx". The extension decides which gates run. Default "Screen.tsx".' },
|
|
174
|
+
filename: { type: 'string', description: 'Its name, e.g. "src/pages/Settings.tsx". The extension decides which gates run: a .css file gets the colour and token checks, and setting a real token (--color-surface-base: ...) is allowed there, since that is how a project themes the system. Default "Screen.tsx".' },
|
|
162
175
|
},
|
|
163
176
|
required: ['code'],
|
|
164
177
|
additionalProperties: false,
|
|
165
178
|
},
|
|
166
179
|
run({ code, filename = 'Screen.tsx' }) {
|
|
167
|
-
if (!/\.(tsx|jsx|ts)$/.test(filename)) throw new ToolError('filename ends in .tsx, .jsx or .
|
|
180
|
+
if (!/\.(tsx|jsx|ts|css)$/.test(filename)) throw new ToolError('filename ends in .tsx, .jsx, .ts or .css.')
|
|
168
181
|
const findings = checkText(String(code), filename, index, tokenNames)
|
|
169
182
|
if (findings.length === 0) {
|
|
170
183
|
return `No findings in ${filename}: colours, tokens, button spacing, props and AI copy are clean.\nNot checked: how the screen behaves. Run get_review_checklist, in a browser.`
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
// accepted; one the rest type names in its Omit<> is not. With a `...rest` row of a Radix
|
|
29
29
|
// component's props, any other prop is accepted (the Radix surface is not in the manifest).
|
|
30
30
|
import ts from 'typescript'
|
|
31
|
+
import { DOCS_SITE } from './agent-docs.mjs'
|
|
31
32
|
import { parseRowName, resolvePart } from './component-props.mjs'
|
|
32
33
|
|
|
33
34
|
/** Always accepted on any component: React's own props, and attributes every DOM wrapper takes. */
|
|
@@ -125,7 +126,7 @@ export function buildIndex(manifest) {
|
|
|
125
126
|
if (exportNames.length === 0) continue
|
|
126
127
|
const components = Object.fromEntries(exportNames.map(name => [name, true]))
|
|
127
128
|
const specs = Object.fromEntries(
|
|
128
|
-
exportNames.map(name => [name, { entry: entry.name, docs: entry.docs
|
|
129
|
+
exportNames.map(name => [name, { entry: entry.name, docs: entry.docs && `${DOCS_SITE}${entry.docs.replace(/^\//, '')}`, props: new Map(), rest: null }])
|
|
129
130
|
)
|
|
130
131
|
let unresolved = false
|
|
131
132
|
for (const row of entry.props ?? []) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The one ranking for "find me the thing for X". The MCP server's `search` tool and the docs site's
|
|
2
|
+
// top-bar search both call it, so an agent and a person typing the same words get the same order.
|
|
3
|
+
// Pure and dependency-free: it runs in Node and in the browser.
|
|
4
|
+
|
|
5
|
+
export const words = text => String(text).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)
|
|
6
|
+
|
|
7
|
+
/** A scorer for one query. `title` is the names, `body` the summary; `props` (descriptions) and
|
|
8
|
+
* `propNames` count once each, as hints, so a long prop table does not outrank a component or
|
|
9
|
+
* token whose own summary is about the query. 0 means no match. */
|
|
10
|
+
export function scorer(query) {
|
|
11
|
+
const terms = words(query)
|
|
12
|
+
return (title, body, props = '', propNames = '') => {
|
|
13
|
+
if (terms.length === 0) return 0
|
|
14
|
+
const t = words(title).join(' ')
|
|
15
|
+
const b = words(body)
|
|
16
|
+
const p = words(props)
|
|
17
|
+
const n = words(propNames)
|
|
18
|
+
const inMain = term => t.includes(term) || b.some(w => w.startsWith(term))
|
|
19
|
+
// Every word of the query in the name or summary beats one word found often: "page
|
|
20
|
+
// background" is the surface token, not PageHeader, whose name matches "page" alone.
|
|
21
|
+
const all = terms.length > 1 && terms.every(inMain) ? 5 : 0
|
|
22
|
+
return all + terms.reduce((sum, term) => sum + (t.includes(term) ? 3 : 0) + Math.min(b.filter(w => w.startsWith(term)).length, 3) + (inMain(term) ? 0 : n.some(w => w.startsWith(term)) ? 2 : p.some(w => w.startsWith(term)) ? 1 : 0), 0)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
// Colour only, on purpose. Spacing is not gated: enforcing it before the scale is complete makes
|
|
10
10
|
// an author (or an agent) invent a token name to satisfy the rule, which is the worse failure.
|
|
11
11
|
|
|
12
|
+
import { DOCS_SITE } from './agent-docs.mjs'
|
|
13
|
+
|
|
14
|
+
/** Where a token list lives for whoever reads the finding: the MCP tool, or the docs site. A
|
|
15
|
+
* repository path meant nothing in a project that installed the components (2026-10-29 test). */
|
|
16
|
+
const TOKEN_LIST = `get_tokens over MCP, or ${DOCS_SITE}llms-tokens.txt`
|
|
17
|
+
|
|
12
18
|
export const SCANNED_ROOTS = ['src']
|
|
13
19
|
export const SCANNED_EXTENSIONS = /\.(tsx?|css)$/
|
|
14
20
|
|
|
@@ -16,6 +22,7 @@ export const SCANNED_EXTENSIONS = /\.(tsx?|css)$/
|
|
|
16
22
|
* A new entry needs a reason as good as these; "it was easier" is the case the gate exists for. */
|
|
17
23
|
export const EXEMPT = [
|
|
18
24
|
{ pattern: /^src\/styles\/tokens\.css$/, why: 'generated from tokens/: this is where the literals are meant to be' },
|
|
25
|
+
{ pattern: /(^|\/)division-ds\/styles\/tokens\.css$/, why: 'the same file, installed into a project by shadcn; it also names the MCP Apps host\'s own variables' },
|
|
19
26
|
{ pattern: /\.test\.tsx?$/, why: 'tests assert on literal values and on deliberately wrong input' },
|
|
20
27
|
{ pattern: /^src\/demo\/(scene|blob|terrain)\//, why: 'WebGL materials: three.js takes colours as numbers, not CSS variables' },
|
|
21
28
|
{ pattern: /^src\/components\/atoms\/loading-animation\/(palette|scene|kit|variants)\.ts$/, why: 'WebGL materials, read from the tokens at runtime where they can be' },
|
|
@@ -98,9 +105,9 @@ export function findViolations(text, path, tokenNames) {
|
|
|
98
105
|
if (name.endsWith('-')) continue
|
|
99
106
|
if (!known.has(name)) {
|
|
100
107
|
const near = suggest(name, tokenNames)
|
|
101
|
-
add('unknown-token', `var(${name})`, near.length ? `No such token. Did you mean ${near.join(', ')}?` :
|
|
108
|
+
add('unknown-token', `var(${name})`, near.length ? `No such token. Did you mean ${near.join(', ')}?` : `No such token. The full list: ${TOKEN_LIST}.`)
|
|
102
109
|
} else if (PRIMITIVE_RAMP.test(name) && inComponents(path)) {
|
|
103
|
-
add('primitive-in-component', `var(${name})`,
|
|
110
|
+
add('primitive-in-component', `var(${name})`, `A component reads semantic tokens, not a ramp step. Pick one by intent (accent.<state>.text, .fill or .ink): ${TOKEN_LIST}.`)
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
|
|
@@ -108,7 +115,7 @@ export function findViolations(text, path, tokenNames) {
|
|
|
108
115
|
// where a literal colour would be.
|
|
109
116
|
const rest = line.replace(/var\([^)]*\)/g, '')
|
|
110
117
|
for (const match of rest.matchAll(HEX)) {
|
|
111
|
-
add('raw-colour', match[0],
|
|
118
|
+
add('raw-colour', match[0], `Use a semantic token: text-[var(--color-text-primary)], bg-[var(--color-surface-base)]. Tokens and what each is for: ${TOKEN_LIST}.`)
|
|
112
119
|
}
|
|
113
120
|
for (const match of rest.matchAll(COLOR_FUNCTION)) {
|
|
114
121
|
add('raw-colour', match[0] + '…)', 'Use a semantic token. If no token fits, the colour is a change to the system: add it under tokens/ with a contrast pair.')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: division-ds
|
|
3
|
-
description: Use when building or changing UI with Division DS (React 19, Tailwind 4, Radix;
|
|
3
|
+
description: Use when building or changing UI with Division DS (React 19, Tailwind 4, Radix; 72 components). Covers which component and token to use, the composition rules prop types cannot express (providers, FormField, button gaps, card borders), planning a screen's states, and how to check the result. Load it before writing any screen or component in a project that has Division DS components.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Division DS
|
|
@@ -82,7 +82,7 @@ provider. A project that uses the components mounts both itself (step 4 of `inst
|
|
|
82
82
|
`src/demo/states/` files are not part of the system.
|
|
83
83
|
|
|
84
84
|
**What the system does not have.** No Popover, no Combobox (single value: use `Select`; several:
|
|
85
|
-
`MultiSelect`)
|
|
85
|
+
`MultiSelect`). Do not reach into `src/demo/` from a component.
|
|
86
86
|
|
|
87
87
|
**Vocabulary.** Inside this repository, `npm run check:vocabulary` bans a list of words in `src/`,
|
|
88
88
|
`scripts/` and `e2e/` (the list is `scripts/lib/vocabulary.mjs`; it is not published with the
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Load when adding one component to a screen that already exists. Generated by `npm run agent-docs`; the procedure lives in scripts/lib/agent-docs.mjs.
|
|
4
4
|
|
|
5
|
-
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on.
|
|
5
|
+
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on. The other procedures named here (`commands/build.md` and the rest) are in the skill: `https://github.com/Victor-Ilioiu/division-ds/blob/main/skills/division-ds/commands/<name>.md`, or `npx division-ds skill` to install them.
|
|
6
6
|
|
|
7
7
|
1. Find it in `public/llms.txt`. Its "Best for" should describe your case and its "Not for"
|
|
8
8
|
should not; if "Not for" names your case, use the component it names instead.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Load when building a new screen or page. Plans the regions and their states before any code. Generated by `npm run agent-docs`; the procedure lives in scripts/lib/agent-docs.mjs.
|
|
4
4
|
|
|
5
|
-
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on.
|
|
5
|
+
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on. The other procedures named here (`commands/build.md` and the rest) are in the skill: `https://github.com/Victor-Ilioiu/division-ds/blob/main/skills/division-ds/commands/<name>.md`, or `npx division-ds skill` to install them.
|
|
6
6
|
|
|
7
7
|
Plan before code. The states a screen gets wrong are the ones nobody planned: an empty list
|
|
8
8
|
with no message, a load that fails into a blank panel, a button that is disabled with no reason.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Load when a gate, `division-ds check` or a test has failed and named a rule. What each message means and the fix. Generated by `npm run agent-docs`; the procedure lives in scripts/lib/agent-docs.mjs.
|
|
4
4
|
|
|
5
|
-
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on.
|
|
5
|
+
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on. The other procedures named here (`commands/build.md` and the rest) are in the skill: `https://github.com/Victor-Ilioiu/division-ds/blob/main/skills/division-ds/commands/<name>.md`, or `npx division-ds skill` to install them.
|
|
6
6
|
|
|
7
7
|
Every finding prints `path:line [rule] what was found` and a line saying what to write instead.
|
|
8
8
|
Do what that line says. Never silence a gate: no new EXEMPT entry, no `token-discipline: allow`
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Load when a screen is written and you are about to say it is done. The checklist to run over it. Generated by `npm run agent-docs`; the procedure lives in scripts/lib/agent-docs.mjs.
|
|
4
4
|
|
|
5
|
-
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on.
|
|
5
|
+
Paths starting `public/` are in the Division DS repository. In a project that uses the components, read them online: `public/llms.txt` is `https://victor-ilioiu.vercel.app/systems/division-ds/llms.txt`, and so on. The other procedures named here (`commands/build.md` and the rest) are in the skill: `https://github.com/Victor-Ilioiu/division-ds/blob/main/skills/division-ds/commands/<name>.md`, or `npx division-ds skill` to install them.
|
|
6
6
|
|
|
7
7
|
Go through every line. A line that does not hold is a change to make, not a note to leave.
|
|
8
8
|
|
|
9
|
-
- [ ] **States.** Every region has its empty, loading, error, success and disabled or no-permission state, as planned
|
|
9
|
+
- [ ] **States.** Every region has its empty, loading, error, success and disabled or no-permission state, as planned before the code (the regions-and-states plan of `commands/build.md`). An empty list says what is empty and offers the action that fills it.
|
|
10
10
|
- [ ] **Headings.** One `PageHeader`, so one `h1`. Section headings continue the outline: `h2` under the `h1`, `h3` inside an `h2`, no level skipped. `SectionHeading` is given the level.
|
|
11
11
|
- [ ] **Tokens only.** No hex, `rgb()`, palette class or invented token name. `division-ds check` passes.
|
|
12
12
|
- [ ] **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`.
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
**Assistant copy.** A button names the action ("Summarize"), never "AI". Say "response", not
|
|
6
6
|
"answer". No "please" or "!" in assistant labels. The interface says "Assistant"; only its own
|
|
7
|
-
messages say "I".
|
|
7
|
+
messages say "I". Response feedback: `ResponseFeedback` in `actions`.
|
|
8
|
+
A value the assistant filled is `aiValue` on that `Input` or `Textarea` only; leaving it accepts it.
|
|
@@ -10,6 +10,9 @@ bare and name it through its own `label` prop ("Role for Ilse Varga"). `Switch`,
|
|
|
10
10
|
renders `ErrorSummary` at the top and focuses it; `useFieldErrors` holds the state (signature in
|
|
11
11
|
`public/docs/helpers.md`).
|
|
12
12
|
|
|
13
|
+
A `RadioGroup` in a form sits in a `<fieldset>` whose `<legend>` names it (code on the Radio
|
|
14
|
+
page). A date is `DatePicker` in a `FormField`; its value is 'YYYY-MM-DD'.
|
|
15
|
+
|
|
13
16
|
**Buttons.** Two or more buttons side by side need a parent with a gap
|
|
14
17
|
(`flex flex-wrap gap-3`); `DialogFooter` and `CardFooter` already are one. A `Button` is
|
|
15
18
|
`type="button"` unless you say `type="submit"`. A busy button takes `loading`, not `disabled`.
|
|
@@ -8,7 +8,9 @@ The shell paints the page background (`surface.base`) and supplies the gutter; a
|
|
|
8
8
|
own `max-w-*` and vertical spacing, and no page background. A form or an overlay is the
|
|
9
9
|
exception: it paints `surface.base` on itself, so its field colours never depend on what is
|
|
10
10
|
behind it. In a project that uses the components, your own root does this
|
|
11
|
-
(step 4 of `install.md`).
|
|
11
|
+
(step 4 of `install.md`). Toggle it with `setTheme()` from `src/lib/theme.ts`.
|
|
12
|
+
|
|
13
|
+
**App navigation.** A few views: `Navbar` (`current: true` on the open one). Many: `Sidebar`.
|
|
12
14
|
|
|
13
15
|
**Type.** Three families, as utilities: `font-display` (headings, figures), `font-body` (the
|
|
14
16
|
default, set by the shell), `font-mono` (labels, code, tabular numbers). A page title is
|
package/division-ds-0.2.0.tgz
DELETED
|
Binary file
|