@reqvire-org/ads 0.3.0 → 0.3.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/README.md +1 -1
- package/dist/bootstrap-ds/ADS.md +47 -7
- package/dist/bootstrap-ds/README.md +12 -10
- package/dist/bootstrap-ds/design-system/index.ts +1 -1
- package/dist/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
- package/dist/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
- package/dist/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
- package/dist/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
- package/dist/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
- package/dist/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/bootstrap-ds/ds.config.json +10 -0
- package/dist/check.mjs +29 -13
- package/dist/contracts/foundation.json +211 -0
- package/dist/foundation.mjs +114 -0
- package/dist/init.mjs +22 -5
- package/dist/mcp/apps.mjs +2 -2
- package/dist/mcp/build-status.mjs +22 -9
- package/dist/mcp/context.mjs +1 -1
- package/dist/mcp/preview-watch.mjs +35 -12
- package/dist/mcp/preview-widget.bundle.js +40 -38
- package/dist/mcp/project-widget.bundle.js +42 -38
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/server.mjs +5 -5
- package/dist/preview-resources.mjs +1 -1
- package/dist/scenarios-compiler.mjs +86 -43
- package/dist/scenarios.mjs +16 -5
- package/dist/skills/ads-assimilate/SKILL.md +47 -12
- package/dist/skills/ads-assimilate/references/cli.md +3 -2
- package/dist/skills/ads-break-ui/SKILL.md +47 -12
- package/dist/skills/ads-break-ui/references/verification.md +4 -0
- package/dist/skills/ads-designer/SKILL.md +49 -14
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +47 -7
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +12 -10
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +10 -0
- package/dist/skills/ads-designer/references/cli.md +3 -2
- package/dist/skills/ads-import/SKILL.md +47 -12
- package/dist/skills/ads-import/references/cli.md +3 -2
- package/dist/skills/ads-motion/SKILL.md +47 -12
- package/dist/token-policy.mjs +12 -0
- package/dist/token-samples.mjs +8 -0
- package/dist/units.mjs +34 -10
- package/docs/ADS.md +52 -9
- package/docs/ARCHITECTURE.md +44 -3
- package/docs/CHANGELOG.md +32 -1
- package/docs/GUIDE.md +17 -6
- package/docs/MCP.md +62 -9
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -272,7 +272,7 @@ The [dummy project](tests/fixtures/guard-project/README.md) tests the generic gu
|
|
|
272
272
|
There are two different starting points in this checkout:
|
|
273
273
|
|
|
274
274
|
- [bootstrap-ds/](bootstrap-ds/README.md) is an empty React application starter:
|
|
275
|
-
token defaults, build setup, empty component
|
|
275
|
+
token defaults, build setup, empty product component folders, a project-owned token review pattern, and a project preview compiler adapter. It is bundled with `ads-designer` so an agent can copy it when starting
|
|
276
276
|
from zero. `ads init` uses its empty foundation files when a project has no ADS.
|
|
277
277
|
- [design-system/](design-system/README.md) is this repository's working example,
|
|
278
278
|
with real components, patterns, and scenarios inspected through the built-in showcase. It demonstrates the architecture
|
package/dist/bootstrap-ds/ADS.md
CHANGED
|
@@ -40,6 +40,35 @@ Token names and prefixes are fixed by ADS architecture; foreign brand prefixes
|
|
|
40
40
|
are rejected. Shared assignments belong in token files. Components own
|
|
41
41
|
`--ds-*`, patterns `--ux-*`, and the showcase `--showcase-*`; token files cannot
|
|
42
42
|
assign those local variables or override component state.
|
|
43
|
+
|
|
44
|
+
Foundation roles are enforced by `foundation-role`, `foundation-selector` and
|
|
45
|
+
`foundation-import`, alongside the regular token checks. Shared definitions
|
|
46
|
+
belong on the document root or explicit root themes; ordinary declarations belong
|
|
47
|
+
in `base.css`, and font-face descriptors in optional `fonts.css`. The entry imports
|
|
48
|
+
canonical sheets; those sheets cannot import another styling layer. Component
|
|
49
|
+
classes, IDs, data/ARIA hooks, contextual selectors, keyframes and scoped UI rules
|
|
50
|
+
are rejected in the foundation, including nested functional selectors. Raw visual
|
|
51
|
+
values and undefined token references are checked in native defaults too; literal
|
|
52
|
+
font-face metadata keeps its separate asset role.
|
|
53
|
+
|
|
54
|
+
`base.css` has three closed roles:
|
|
55
|
+
|
|
56
|
+
- Document defaults: root/body typography, canvas colors and theme behavior,
|
|
57
|
+
global text selection and document scrollbars.
|
|
58
|
+
- Native resets: permitted normalization properties and values, such as zero
|
|
59
|
+
spacing, inherited control typography, list resets and responsive replaced media.
|
|
60
|
+
- Global accessibility: universal `:focus-visible` outline rules, and reduced-motion
|
|
61
|
+
overrides inside `@media (prefers-reduced-motion: reduce)`.
|
|
62
|
+
|
|
63
|
+
The `foundation-role` check validates every selector branch, property and value.
|
|
64
|
+
Native selectors are not a bypass: token-backed button backgrounds, flex/grid
|
|
65
|
+
layout, component-specific focus rules and hover/disabled skins are rejected.
|
|
66
|
+
Shared token definitions belong in value sheets, not `base.css`. No project token
|
|
67
|
+
names are embedded in this policy. A zero-specificity `:where()` wrapper does not
|
|
68
|
+
change ownership. Put component design and animation in Linaria owners, and
|
|
69
|
+
scenario layout variations in typed public review-pattern props. Browser review
|
|
70
|
+
still checks the effects and accessibility of permitted defaults.
|
|
71
|
+
|
|
43
72
|
Own primitive markup, focus, hover, selected, disabled, loading, and motion in
|
|
44
73
|
the primitive. Use typed props for variations. Keep reusable structure and skin
|
|
45
74
|
in colocated Linaria blocks using `css` and `cx` from `@linaria/atomic`.
|
|
@@ -114,13 +143,24 @@ design-system/
|
|
|
114
143
|
Flat owners may use `Button.scenario.tsx` and `Button.fixture.ts` instead.
|
|
115
144
|
`Scenario.tsx` exports a default independently mountable view and literal metadata
|
|
116
145
|
`export const scenario = { title: "Button states", covers: ["Button"] };`.
|
|
117
|
-
Token entries under the configured token directory
|
|
118
|
-
`export const scenario = { title:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
Token entries under the configured token directory declare
|
|
147
|
+
`export const scenario = { title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples };`.
|
|
148
|
+
`TokenSamples` is any project-owned public review pattern, not a required name.
|
|
149
|
+
Its colocated fixture declares literal `samples: [{ token, property, probe }]`.
|
|
150
|
+
Each token must exist and have sample coverage; duplicate bindings, missing probes,
|
|
151
|
+
empty covers and value-only inspectors fail. The default entry passes the declared
|
|
152
|
+
samples to a rendered public pattern's `samples` prop. No token-name/property mapping
|
|
153
|
+
or sample values are embedded in the tool. Display values come from foundation CSS;
|
|
154
|
+
probes are isolated verification inputs owned by the fixture.
|
|
155
|
+
|
|
156
|
+
The renderer marks the actual styled element with `data-ads-token` and
|
|
157
|
+
`data-ads-property`. In a browser, the App validates the declared property/value and
|
|
158
|
+
probe using `CSS.supports`, checks the sample is rendered, then temporarily changes
|
|
159
|
+
that token to its fixture probe. Its computed sample property must respond. Values
|
|
160
|
+
are restored before yielding to painting; hardcoded lookalikes fail. Source gates
|
|
161
|
+
prove the contract structure, not successful rendering. The browser check covers
|
|
162
|
+
the mounted scenario and theme; visual quality and other states still need review.
|
|
163
|
+
|
|
124
164
|
It imports real public UI through `@ds` and its local fixture. The adapter supplies
|
|
125
165
|
initial state, callbacks and simulated services. The default entry needs no
|
|
126
166
|
mandatory caller inputs or prior page initialization. Related parts may share a
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Empty native design system
|
|
2
2
|
|
|
3
|
-
This private React/TypeScript workspace contains semantic CSS tokens,
|
|
4
|
-
public barrel, Linaria extraction and a
|
|
5
|
-
no components,
|
|
3
|
+
This private React/TypeScript workspace contains semantic CSS tokens, a project-owned
|
|
4
|
+
token review pattern, a public barrel, Linaria extraction and a preview compiler
|
|
5
|
+
adapter. It has no product components, icons, fonts or example screens. The minimal showcase
|
|
6
6
|
shell and MCP client come from ADS tooling.
|
|
7
7
|
|
|
8
8
|
Read the local [contract](ADS.md) and the
|
|
@@ -13,7 +13,7 @@ Read the local [contract](ADS.md) and the
|
|
|
13
13
|
Copy the contents of `bootstrap-ds/`, including dotfiles, into a new project.
|
|
14
14
|
The installed `ads-designer` skill contains the same files in
|
|
15
15
|
`assets/bootstrap-ds/`. Set the project name in `package.json`. Use Node 22.12 or
|
|
16
|
-
newer and ADS 0.3 or later for the shared-resource preview compiler.
|
|
16
|
+
newer and ADS 0.3.1 or later for the shared-resource preview compiler.
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
19
|
npx @reqvire-org/ads init --framework react
|
|
@@ -25,7 +25,7 @@ npm run dev:showcase
|
|
|
25
25
|
For unreleased tooling, run init using the real local package:
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.
|
|
28
|
+
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.1.tgz"
|
|
29
29
|
npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
|
|
30
30
|
npm install
|
|
31
31
|
npm run build
|
|
@@ -44,7 +44,7 @@ native Preact app without a saved ADS framework, use `ads init --framework preac
|
|
|
44
44
|
Init records that choice and generates native hook imports without migrating the
|
|
45
45
|
app or rewriting its compiler. Subsequent init runs reuse the saved choice.
|
|
46
46
|
Init adds missing foundation files and connects architectural checks; it does
|
|
47
|
-
not configure Linaria or generate component source. Generated `.ds/` files are
|
|
47
|
+
not configure Linaria or generate product component source. Generated `.ds/` files are
|
|
48
48
|
ignored; original artwork and provenance belong in `assets/brand/`.
|
|
49
49
|
|
|
50
50
|
## Inspect and add UI
|
|
@@ -56,10 +56,12 @@ Its MCP client discovers entries, invokes preview tools and follows notification
|
|
|
56
56
|
There are no required TokensPage/ComponentsPage/PatternsPage/CompositionPage
|
|
57
57
|
files, and no separate project browser app to implement or rebuild.
|
|
58
58
|
|
|
59
|
-
The existing `tokens/Foundation.scenario.tsx` and colocated fixture
|
|
60
|
-
starter token values.
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
The existing `tokens/Foundation.scenario.tsx` and colocated fixture visually demonstrate all
|
|
60
|
+
starter token values. The fixture owns `{ token, property, probe }` bindings;
|
|
61
|
+
the public `TokenPreview` pattern owns their presentation and applies the real CSS
|
|
62
|
+
variables. Add/remove samples alongside token definitions. `ads check` rejects
|
|
63
|
+
missing coverage and invented names; the App verifies the declared CSS bindings
|
|
64
|
+
in the browser. Extend or replace the project-owned pattern as your tokens evolve.
|
|
63
65
|
|
|
64
66
|
Create a component under `design-system/components/`, export it through `@ds`,
|
|
65
67
|
and colocate `Scenario.tsx` plus `fixture.ts` (or `<Owner>.scenario.tsx` and
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { css, cx } from "@linaria/atomic";
|
|
3
|
+
|
|
4
|
+
export interface TokenSample { token: `--${string}`; property: string; }
|
|
5
|
+
export interface TokenPreviewProps { title: string; samples: readonly TokenSample[]; text: string; }
|
|
6
|
+
const sampleStyle = css`
|
|
7
|
+
display: inline-block;
|
|
8
|
+
position: relative;
|
|
9
|
+
white-space: pre-line;
|
|
10
|
+
&[data-ads-property="animation-duration"] { animation-duration: var(--ux-token-preview-value); }
|
|
11
|
+
&[data-ads-property="animation-timing-function"] { animation-timing-function: var(--ux-token-preview-value); }
|
|
12
|
+
&[data-ads-property="background-color"] { background-color: var(--ux-token-preview-value); }
|
|
13
|
+
&[data-ads-property="border-radius"] { border-radius: var(--ux-token-preview-value); }
|
|
14
|
+
&[data-ads-property="box-shadow"] { box-shadow: var(--ux-token-preview-value); }
|
|
15
|
+
&[data-ads-property="color"] { color: var(--ux-token-preview-value); }
|
|
16
|
+
&[data-ads-property="font-family"] { font-family: var(--ux-token-preview-value); }
|
|
17
|
+
&[data-ads-property="font-size"] { font-size: var(--ux-token-preview-value); }
|
|
18
|
+
&[data-ads-property="font-weight"] { font-weight: var(--ux-token-preview-value); }
|
|
19
|
+
&[data-ads-property="line-height"] { line-height: var(--ux-token-preview-value); }
|
|
20
|
+
&[data-ads-property="width"] { width: var(--ux-token-preview-value); }
|
|
21
|
+
&[data-ads-property="z-index"] { z-index: var(--ux-token-preview-value); }
|
|
22
|
+
`;
|
|
23
|
+
function Sample({ sample, text }: { sample: TokenSample; text: string }) {
|
|
24
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
25
|
+
useLayoutEffect(() => { ref.current?.style.setProperty("--ux-token-preview-value", `var(${sample.token})`); }, [sample.token]);
|
|
26
|
+
return <span ref={ref} className={cx("ux-token-preview__sample", sampleStyle)} data-ads-token={sample.token} data-ads-property={sample.property}>{text}</span>;
|
|
27
|
+
}
|
|
28
|
+
/** Project-owned review UI. Values always come from the real foundation CSS. */
|
|
29
|
+
export function TokenPreview({ title, samples, text }: TokenPreviewProps) {
|
|
30
|
+
return <section className="ux-token-preview"><h1>{title}</h1>{samples.map(sample => <div key={`${sample.token}:${sample.property}`} className="ux-token-preview__item"><code className="ux-token-preview__name">{sample.token}</code><Sample sample={sample} text={text}/></div>)}</section>;
|
|
31
|
+
}
|
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
export const fixture = {
|
|
2
2
|
"title": "Foundation",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
"--accent-
|
|
6
|
-
"--accent-
|
|
7
|
-
"--
|
|
8
|
-
"--bg-
|
|
9
|
-
"--bg-
|
|
10
|
-
"--
|
|
11
|
-
"--border-
|
|
12
|
-
"--border-
|
|
13
|
-
"--
|
|
14
|
-
"--dur-
|
|
15
|
-
"--
|
|
16
|
-
"--
|
|
17
|
-
"--focus-
|
|
18
|
-
"--
|
|
19
|
-
"--font-
|
|
20
|
-
"--
|
|
21
|
-
"--leading-
|
|
22
|
-
"--
|
|
23
|
-
"--radius-
|
|
24
|
-
"--radius-
|
|
25
|
-
"--
|
|
26
|
-
"--shadow-
|
|
27
|
-
"--
|
|
28
|
-
"--space-
|
|
29
|
-
"--space-
|
|
30
|
-
"--space-
|
|
31
|
-
"--space-
|
|
32
|
-
"--space-
|
|
33
|
-
"--space-
|
|
34
|
-
"--
|
|
35
|
-
"--text-
|
|
36
|
-
"--text-
|
|
37
|
-
"--text-
|
|
38
|
-
"--text-
|
|
39
|
-
"--text-
|
|
40
|
-
"--text-
|
|
41
|
-
"--text-
|
|
42
|
-
"--
|
|
43
|
-
"--weight-
|
|
44
|
-
"--weight-
|
|
45
|
-
"--
|
|
3
|
+
"text": "Aa",
|
|
4
|
+
samples: [
|
|
5
|
+
{"token": "--accent", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
6
|
+
{"token": "--accent-fg", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
7
|
+
{"token": "--accent-hover", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
8
|
+
{"token": "--bg-canvas", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
9
|
+
{"token": "--bg-hover", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
10
|
+
{"token": "--bg-surface", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
11
|
+
{"token": "--border-default", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
12
|
+
{"token": "--border-focus", "property": "background-color", "probe": "rgb(31, 97, 163)"},
|
|
13
|
+
{"token": "--border-w", "property": "width", "probe": "13.375px"},
|
|
14
|
+
{"token": "--dur-fast", "property": "animation-duration", "probe": "0.375s"},
|
|
15
|
+
{"token": "--dur-normal", "property": "animation-duration", "probe": "0.375s"},
|
|
16
|
+
{"token": "--ease-standard", "property": "animation-timing-function", "probe": "cubic-bezier(.13,.27,.61,.83)"},
|
|
17
|
+
{"token": "--focus-offset", "property": "width", "probe": "13.375px"},
|
|
18
|
+
{"token": "--focus-w", "property": "width", "probe": "13.375px"},
|
|
19
|
+
{"token": "--font-mono", "property": "font-family", "probe": "monospace, serif"},
|
|
20
|
+
{"token": "--font-sans", "property": "font-family", "probe": "monospace, serif"},
|
|
21
|
+
{"token": "--leading-normal", "property": "line-height", "probe": "2.375"},
|
|
22
|
+
{"token": "--leading-tight", "property": "line-height", "probe": "2.375"},
|
|
23
|
+
{"token": "--radius-lg", "property": "border-radius", "probe": "13.375px"},
|
|
24
|
+
{"token": "--radius-md", "property": "border-radius", "probe": "13.375px"},
|
|
25
|
+
{"token": "--radius-sm", "property": "border-radius", "probe": "13.375px"},
|
|
26
|
+
{"token": "--shadow-raised", "property": "box-shadow", "probe": "1px 3px 5px rgb(31, 97, 163)"},
|
|
27
|
+
{"token": "--shadow-xs", "property": "box-shadow", "probe": "1px 3px 5px rgb(31, 97, 163)"},
|
|
28
|
+
{"token": "--space-0", "property": "width", "probe": "13.375px"},
|
|
29
|
+
{"token": "--space-12", "property": "width", "probe": "13.375px"},
|
|
30
|
+
{"token": "--space-16", "property": "width", "probe": "13.375px"},
|
|
31
|
+
{"token": "--space-2", "property": "width", "probe": "13.375px"},
|
|
32
|
+
{"token": "--space-4", "property": "width", "probe": "13.375px"},
|
|
33
|
+
{"token": "--space-6", "property": "width", "probe": "13.375px"},
|
|
34
|
+
{"token": "--space-8", "property": "width", "probe": "13.375px"},
|
|
35
|
+
{"token": "--text-base", "property": "font-size", "probe": "41.375px"},
|
|
36
|
+
{"token": "--text-body", "property": "color", "probe": "rgb(31, 97, 163)"},
|
|
37
|
+
{"token": "--text-inverse", "property": "color", "probe": "rgb(31, 97, 163)"},
|
|
38
|
+
{"token": "--text-lg", "property": "font-size", "probe": "41.375px"},
|
|
39
|
+
{"token": "--text-muted", "property": "color", "probe": "rgb(31, 97, 163)"},
|
|
40
|
+
{"token": "--text-sm", "property": "font-size", "probe": "41.375px"},
|
|
41
|
+
{"token": "--text-strong", "property": "color", "probe": "rgb(31, 97, 163)"},
|
|
42
|
+
{"token": "--text-xl", "property": "font-size", "probe": "41.375px"},
|
|
43
|
+
{"token": "--weight-medium", "property": "font-weight", "probe": "327"},
|
|
44
|
+
{"token": "--weight-normal", "property": "font-weight", "probe": "327"},
|
|
45
|
+
{"token": "--weight-semibold", "property": "font-weight", "probe": "327"},
|
|
46
|
+
{"token": "--z-overlay", "property": "z-index", "probe": "367"},
|
|
46
47
|
]
|
|
47
48
|
} as const;
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TokenPreview } from "@ds";
|
|
2
2
|
import { fixture } from "./Foundation.fixture";
|
|
3
|
-
export const scenario = { title: fixture.title, covers: [],
|
|
4
|
-
export default function Scenario() {
|
|
5
|
-
const [values, setValues] = useState<Record<string, string>>({});
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
const read = () => { const style = getComputedStyle(document.documentElement); setValues(Object.fromEntries(fixture.tokens.map(name => [name, style.getPropertyValue(name).trim()]))); };
|
|
8
|
-
read();
|
|
9
|
-
const observer = new MutationObserver(read);
|
|
10
|
-
observer.observe(document.documentElement, {attributes: true, attributeFilter: ["data-theme"]});
|
|
11
|
-
return () => observer.disconnect();
|
|
12
|
-
}, []);
|
|
13
|
-
return <section><h1>{fixture.title}</h1><dl>{fixture.tokens.map(name => <div key={name}><dt><code>{name}</code></dt><dd>{values[name]}</dd></div>)}</dl></section>;
|
|
14
|
-
}
|
|
3
|
+
export const scenario = { title: fixture.title, covers: ["TokenPreview"], samples: fixture.samples };
|
|
4
|
+
export default function Scenario() { return <TokenPreview {...fixture} />; }
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
:root { color-scheme: light; }
|
|
2
|
+
:root[data-theme="dark"] { color-scheme: dark; }
|
|
3
|
+
|
|
1
4
|
*,
|
|
2
5
|
*::before,
|
|
3
6
|
*::after {
|
|
@@ -5,7 +8,7 @@
|
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
body {
|
|
8
|
-
margin:
|
|
11
|
+
margin: 0;
|
|
9
12
|
background: var(--bg-canvas);
|
|
10
13
|
color: var(--text-body);
|
|
11
14
|
font-family: var(--font-sans);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
--bg-canvas: #faf9f6;
|
|
4
4
|
--bg-surface: #fff;
|
|
5
5
|
--bg-hover: #f1efea;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
:root[data-theme="dark"] {
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
--bg-canvas: #17191e;
|
|
20
20
|
--bg-surface: #20232a;
|
|
21
21
|
--bg-hover: #2c3039;
|
package/dist/check.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import postcss from "postcss";
|
|
|
5
5
|
import valueParser from "postcss-value-parser";
|
|
6
6
|
import { importViolation, isInside, loadConfig, sourceFiles, isDevelopmentFile, isTestFile } from "./config.mjs";
|
|
7
7
|
import { inspectTokens, references } from "./tokens.mjs";
|
|
8
|
-
import { foundationFiles, isSharedToken } from "./token-policy.mjs";
|
|
8
|
+
import { foundationFiles, foundationImportTarget, isSharedToken } from "./token-policy.mjs";
|
|
9
9
|
import { inspectAssets } from "./assets.mjs";
|
|
10
10
|
import { appImportViolation, showcaseImportViolation, checkComponentVariable, importResolver, projectCompilerOptions, isUiCssProperties, styleWriteViolation } from "./boundaries.mjs";
|
|
11
11
|
import { inspectPublicProps } from "./public-props.mjs";
|
|
@@ -14,6 +14,7 @@ import { inspectShowcasePages } from "./showcase.mjs";
|
|
|
14
14
|
import { inspectShowcaseCoverage } from "./showcase-coverage.mjs";
|
|
15
15
|
import { inspectScenarioCoverage } from "./scenarios.mjs";
|
|
16
16
|
import { uiOperation, importedUiApi } from "./ui-framework.mjs";
|
|
17
|
+
import { foundationDeclaration, foundationSelector } from "./foundation.mjs";
|
|
17
18
|
|
|
18
19
|
const showcaseControls = new Set(["a", "button", "input", "select", "textarea", "option", "optgroup", "table", "thead", "tbody", "tfoot", "tr", "td", "th", "dialog", "details", "summary", "progress", "meter"]);
|
|
19
20
|
const svgTags = new Set(["svg", "g", "path", "circle", "rect", "ellipse", "line", "polyline", "polygon", "text", "tspan", "use", "defs", "clipPath", "mask", "filter", "linearGradient", "radialGradient", "stop", "marker", "pattern"]);
|
|
@@ -51,23 +52,17 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
51
52
|
const css = postcss.parse(readFileSync(foundationEntry, "utf8"), { from: foundationEntry });
|
|
52
53
|
const imports = css.nodes.filter((node) => node.type !== "comment");
|
|
53
54
|
const expected = foundationFiles.filter((name) => name !== "fonts.css" || existsSync(path.join(dsRoot, config.tokenDir, name))).map((name) => path.join(dsRoot, config.tokenDir, name));
|
|
54
|
-
const actual = imports.map(
|
|
55
|
-
if (node.type !== "atrule" || node.name !== "import" || node.nodes) return null;
|
|
56
|
-
const parts = valueParser(node.params).nodes.filter((part) => part.type !== "space" && part.type !== "comment");
|
|
57
|
-
if (parts.length !== 1) return null;
|
|
58
|
-
const part = parts[0];
|
|
59
|
-
const value = part.type === "string" ? part.value : part.type === "function" && part.value === "url" && part.nodes.length === 1 ? part.nodes[0].value : null;
|
|
60
|
-
return value ? path.resolve(path.dirname(foundationEntry), value) : null;
|
|
61
|
-
});
|
|
55
|
+
const actual = imports.map(node => foundationImportTarget(node, foundationEntry));
|
|
62
56
|
if (actual.length !== expected.length || actual.some((file, index) => file !== expected[index] || !existsSync(file))) report(foundationEntry, "styles-entry", "Foundation CSS must import only fonts (when present), colors, typography, spacing, elevation, and base, in that order.");
|
|
63
57
|
} catch (error) { report(foundationEntry, "css-syntax", error.reason ?? error.message, error.line ?? 1); }
|
|
64
|
-
function checkCss(file, text, offset = { line: 1, column: 1 }) {
|
|
58
|
+
function checkCss(file, text, offset = { line: 1, column: 1 }, foundation = false) {
|
|
65
59
|
let css;
|
|
66
60
|
try { css = postcss.parse(text, { from: file }); }
|
|
67
61
|
catch (error) { report(file, "css-syntax", error.reason ?? error.message, offset.line + (error.line ?? 1) - 1); return; }
|
|
68
|
-
if (isDevelopmentFile(file, options) || !isInside(path.join(dsRoot, "components"), file) && !isInside(path.join(dsRoot, "patterns"), file) && file !== foundationEntry) report(file, "style-ownership", "Visual CSS belongs to the local ADS's runtime components or patterns.");
|
|
62
|
+
if (!foundation && (isDevelopmentFile(file, options) || !isInside(path.join(dsRoot, "components"), file) && !isInside(path.join(dsRoot, "patterns"), file) && file !== foundationEntry)) report(file, "style-ownership", "Visual CSS belongs to the local ADS's runtime components or patterns.");
|
|
69
63
|
if (isDevelopmentFile(file, options) && css.nodes.some((node) => node.type !== "comment" && !(node.type === "atrule" && node.name === "import"))) report(file, "showcase-style", "Scenarios and fixtures define no styles. Put the frame/layout in a public ADS pattern and product UI in ADS components or patterns.", offset.line);
|
|
70
64
|
css.walkAtRules("import", (rule) => {
|
|
65
|
+
if (foundation) report(file, "foundation-import", "Import foundation sheets through the configured styles entry. Token sheets cannot import an unchecked styling layer.", rule.source.start.line);
|
|
71
66
|
const first = valueParser(rule.params).nodes.find((node) => node.type !== "space" && node.type !== "comment");
|
|
72
67
|
const specifier = first?.type === "string" ? first.value : first?.type === "function" && first.value === "url" ? first.nodes[0]?.value : null;
|
|
73
68
|
if (!specifier || /^(?:[a-z]+:|\/\/)/i.test(specifier)) return;
|
|
@@ -76,19 +71,40 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
76
71
|
if (showcaseImportViolation(specifier, file, options, resolveImport)) report(file, "showcase-import", "Showcase code and styles are development fixtures; runtime code must not import them.", line);
|
|
77
72
|
});
|
|
78
73
|
css.walkRules((rule) => {
|
|
74
|
+
if (foundation) {
|
|
75
|
+
if (rule.parent.type === "rule") report(file, "foundation-role", "Foundation rules cannot nest owner/context selectors. Use root theme conditions or Linaria owners.", rule.source.start.line);
|
|
76
|
+
const violation = foundationSelector(rule.selector, { definitions: rule.nodes.some(node => node.type === "decl" && node.prop.startsWith("--")) });
|
|
77
|
+
if (violation) report(file, "foundation-selector", violation, rule.source.start.line, rule.source.start.column);
|
|
78
|
+
}
|
|
79
79
|
if (isInside(path.join(dsRoot, "showcase"), file) && /:(?:hover|active|focus(?:-visible|-within)?|checked|disabled|enabled)\b|\[(?:aria-(?:selected|pressed|checked|disabled|expanded)|data-state)\b/.test(rule.selector)) report(file, "showcase-state", "Interactive state styling belongs to the real ADS component or pattern, not showcase or mock styles.", offset.line + rule.source.start.line - 1);
|
|
80
80
|
if (/\.ads-[\w-]+/.test(rule.selector) && !isInside(path.join(dsRoot, "components"), file)) report(file, "style-ownership", "ADS component internals are owned by components, not app or showcase CSS.", offset.line + rule.source.start.line - 1);
|
|
81
81
|
if (/\.ux-[\w-]+/.test(rule.selector) && !isInside(path.join(dsRoot, "patterns"), file)) report(file, "style-ownership", "Pattern internals are owned by patterns, not app or showcase CSS.", offset.line + rule.source.start.line - 1);
|
|
82
82
|
if (/\.showcase-[\w-]+/.test(rule.selector) && !isInside(path.join(dsRoot, "showcase"), file)) report(file, "style-ownership", "Showcase internals are owned by showcase scaffolding.", offset.line + rule.source.start.line - 1);
|
|
83
83
|
});
|
|
84
|
+
if (foundation) css.walkAtRules(rule => {
|
|
85
|
+
if (/(?:^|-)keyframes$|^(?:scope|custom-selector|starting-style)$/i.test(rule.name)) report(file, "foundation-role", "Animations and scoped UI styles belong in their component/pattern's Linaria source, not the token foundation.", rule.source.start.line);
|
|
86
|
+
if (rule.name === "font-face" && path.basename(file) !== "fonts.css") report(file, "foundation-role", "Font faces belong in the canonical fonts.css foundation sheet.", rule.source.start.line);
|
|
87
|
+
});
|
|
84
88
|
css.walkDecls((decl) => {
|
|
89
|
+
if (foundation && decl.prop.startsWith("--")) {
|
|
90
|
+
if (path.basename(file) === "base.css") report(file, "foundation-role", "Shared token definitions belong in value sheets, not base.css.", decl.source.start.line, decl.source.start.column);
|
|
91
|
+
return; // Definition names, values and aliases are validated by inspectTokens.
|
|
92
|
+
}
|
|
93
|
+
const fontDescriptor = foundation && path.basename(file) === "fonts.css" && decl.parent.type === "atrule" && decl.parent.name === "font-face";
|
|
94
|
+
if (foundation && !fontDescriptor && (path.basename(file) !== "base.css" || decl.parent.type !== "rule")) report(file, "foundation-role", "Token sheets contain shared definitions; native defaults belong in base.css, font descriptors in fonts.css, and UI styling in Linaria owners.", decl.source.start.line, decl.source.start.column);
|
|
95
|
+
if (foundation && path.basename(file) === "base.css" && decl.parent.type === "rule") {
|
|
96
|
+
const violation = foundationDeclaration(decl.parent, decl);
|
|
97
|
+
if (violation) report(file, "foundation-role", violation, decl.source.start.line, decl.source.start.column);
|
|
98
|
+
}
|
|
85
99
|
const variable = checkComponentVariable(decl.prop, file, options);
|
|
86
100
|
if (variable) report(file, variable.rule, variable.message, offset.line + decl.source.start.line - 1, decl.source.start.column);
|
|
87
101
|
for (const ref of references(decl.value)) {
|
|
88
102
|
const local = /^(?:--ds-|--ux-|--showcase-)/.test(ref.name);
|
|
103
|
+
if (foundation && local) report(file, "foundation-role", "Foundation defaults consume shared tokens, not component, pattern or scenario-local variables.", offset.line + decl.source.start.line - 1, decl.source.start.column);
|
|
89
104
|
if (!local && !isSharedToken(ref.name)) report(file, "token-name", `${ref.name} does not follow ADS's fixed token naming contract.`, offset.line + decl.source.start.line - 1, decl.source.start.column);
|
|
90
105
|
else if (!local && !declaredTokens.has(ref.name) && !ref.fallback) report(file, "token-reference", `Undefined shared token ${ref.name}.`, offset.line + decl.source.start.line - 1, decl.source.start.column);
|
|
91
106
|
}
|
|
107
|
+
if (fontDescriptor) return; // Font metadata defines source assets, not token-backed UI styles.
|
|
92
108
|
const violations = new Set();
|
|
93
109
|
valueParser(decl.value).walk((node) => {
|
|
94
110
|
if (node.type === "function" && node.value === "url") return false;
|
|
@@ -98,12 +114,12 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
98
114
|
if (node.type === "word" && /^ease(?:-in|-out|-in-out)?$/.test(node.value)) violations.add("Use a motion token for easing.");
|
|
99
115
|
if (node.type === "word" && /^(?:color|background-color|(?:border|outline|caret|text-decoration|column-rule)(?:-[a-z]+)?-color|fill|stroke)$/.test(decl.prop) && /^[a-z]+$/i.test(node.value) && !/^(?:inherit|initial|unset|revert|currentcolor|transparent|none)$/i.test(node.value)) violations.add("Use a semantic color token.");
|
|
100
116
|
});
|
|
101
|
-
if (/^(?:font-family|font-weight|z-index)$/.test(decl.prop) && !/^(?:var\(|inherit$|auto$)/.test(decl.value)) violations.add("Use a semantic typography or layering token.");
|
|
117
|
+
if (/^(?:font-family|font-weight|z-index)$/.test(decl.prop) && !/^(?:var\(|inherit$|initial$|unset$|revert(?:-layer)?$|auto$)/.test(decl.value) && !(foundation && decl.prop === "font-weight" && /^(?:bolder|lighter)$/.test(decl.value))) violations.add("Use a semantic typography or layering token.");
|
|
102
118
|
for (const message of violations) report(file, "style-token", message, offset.line + decl.source.start.line - 1, decl.source.start.column);
|
|
103
119
|
});
|
|
104
120
|
}
|
|
105
121
|
for (const file of files) {
|
|
106
|
-
if (file.endsWith(".css") && isInside(path.join(dsRoot, config.tokenDir), file) && !isDevelopmentFile(file, options)) continue;
|
|
122
|
+
if (file.endsWith(".css") && isInside(path.join(dsRoot, config.tokenDir), file) && !isDevelopmentFile(file, options)) { checkCss(file, readFileSync(file, "utf8"), undefined, true); continue; }
|
|
107
123
|
if (file.endsWith(".html")) { inspectHtml(readFileSync(file, "utf8"), (rule, message, line) => report(file, rule, message, line)); continue; }
|
|
108
124
|
if (file.endsWith(".css")) {
|
|
109
125
|
const text = readFileSync(file, "utf8");
|