@reqvire-org/ads 0.2.9 → 0.3.1
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 +2 -2
- package/dist/bootstrap-ds/ADS.md +19 -8
- package/dist/bootstrap-ds/README.md +13 -11
- 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/ds.config.json +10 -0
- package/dist/check.mjs +2 -9
- package/dist/init.mjs +22 -5
- package/dist/mcp/apps.mjs +2 -2
- package/dist/mcp/context.mjs +1 -1
- 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.mjs +16 -5
- package/dist/skills/README.md +1 -1
- package/dist/skills/ads-assimilate/SKILL.md +28 -12
- package/dist/skills/ads-assimilate/references/cli.md +2 -2
- package/dist/skills/ads-break-ui/SKILL.md +28 -12
- package/dist/skills/ads-break-ui/references/verification.md +4 -0
- package/dist/skills/ads-designer/SKILL.md +30 -14
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +19 -8
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +13 -11
- 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/ds.config.json +10 -0
- package/dist/skills/ads-designer/references/cli.md +2 -2
- package/dist/skills/ads-import/SKILL.md +28 -12
- package/dist/skills/ads-import/references/cli.md +2 -2
- package/dist/skills/ads-motion/SKILL.md +28 -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 +23 -9
- package/docs/ARCHITECTURE.md +12 -0
- package/docs/CHANGELOG.md +16 -1
- package/docs/GUIDE.md +16 -13
- package/docs/MCP.md +44 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -202,7 +202,7 @@ app's bundler. With no icon source, generation is skipped. See the
|
|
|
202
202
|
Install skills with `npx skills`:
|
|
203
203
|
|
|
204
204
|
```sh
|
|
205
|
-
npx skills add reqvire-org/
|
|
205
|
+
npx skills add reqvire-org/ads --skill ads-designer --skill ads-import --skill ads-assimilate --skill ads-break-ui --skill ads-motion
|
|
206
206
|
```
|
|
207
207
|
|
|
208
208
|
For local development, install from this checkout:
|
|
@@ -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
|
@@ -5,7 +5,7 @@ token values, and branding. Tool updates preserve those values and customized
|
|
|
5
5
|
code; source and theme imports are explicit operations.
|
|
6
6
|
|
|
7
7
|
For the architectural rationale, see the
|
|
8
|
-
[ADS architecture](https://github.com/reqvire-org/
|
|
8
|
+
[ADS architecture](https://github.com/reqvire-org/ads/blob/main/docs/ARCHITECTURE.md).
|
|
9
9
|
The rules and commands needed to use this starter are included locally.
|
|
10
10
|
|
|
11
11
|
## Ownership
|
|
@@ -114,13 +114,24 @@ design-system/
|
|
|
114
114
|
Flat owners may use `Button.scenario.tsx` and `Button.fixture.ts` instead.
|
|
115
115
|
`Scenario.tsx` exports a default independently mountable view and literal metadata
|
|
116
116
|
`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
|
-
|
|
117
|
+
Token entries under the configured token directory declare
|
|
118
|
+
`export const scenario = { title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples };`.
|
|
119
|
+
`TokenSamples` is any project-owned public review pattern, not a required name.
|
|
120
|
+
Its colocated fixture declares literal `samples: [{ token, property, probe }]`.
|
|
121
|
+
Each token must exist and have sample coverage; duplicate bindings, missing probes,
|
|
122
|
+
empty covers and value-only inspectors fail. The default entry passes the declared
|
|
123
|
+
samples to a rendered public pattern's `samples` prop. No token-name/property mapping
|
|
124
|
+
or sample values are embedded in the tool. Display values come from foundation CSS;
|
|
125
|
+
probes are isolated verification inputs owned by the fixture.
|
|
126
|
+
|
|
127
|
+
The renderer marks the actual styled element with `data-ads-token` and
|
|
128
|
+
`data-ads-property`. In a browser, the App validates the declared property/value and
|
|
129
|
+
probe using `CSS.supports`, checks the sample is rendered, then temporarily changes
|
|
130
|
+
that token to its fixture probe. Its computed sample property must respond. Values
|
|
131
|
+
are restored before yielding to painting; hardcoded lookalikes fail. Source gates
|
|
132
|
+
prove the contract structure, not successful rendering. The browser check covers
|
|
133
|
+
the mounted scenario and theme; visual quality and other states still need review.
|
|
134
|
+
|
|
124
135
|
It imports real public UI through `@ds` and its local fixture. The adapter supplies
|
|
125
136
|
initial state, callbacks and simulated services. The default entry needs no
|
|
126
137
|
mandatory caller inputs or prior page initialization. Related parts may share a
|
|
@@ -1,19 +1,19 @@
|
|
|
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
|
|
9
|
-
[architecture](https://github.com/reqvire-org/
|
|
9
|
+
[architecture](https://github.com/reqvire-org/ads/blob/main/docs/ARCHITECTURE.md).
|
|
10
10
|
|
|
11
11
|
## Start
|
|
12
12
|
|
|
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} />; }
|
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";
|
|
@@ -51,14 +51,7 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
51
51
|
const css = postcss.parse(readFileSync(foundationEntry, "utf8"), { from: foundationEntry });
|
|
52
52
|
const imports = css.nodes.filter((node) => node.type !== "comment");
|
|
53
53
|
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
|
-
});
|
|
54
|
+
const actual = imports.map(node => foundationImportTarget(node, foundationEntry));
|
|
62
55
|
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
56
|
} catch (error) { report(foundationEntry, "css-syntax", error.reason ?? error.message, error.line ?? 1); }
|
|
64
57
|
function checkCss(file, text, offset = { line: 1, column: 1 }) {
|
package/dist/init.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, writeFileSync, chmodSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import ts from "typescript";
|
|
4
5
|
import { isInside, loadConfig, sourceFiles, supportedFrameworks } from "./config.mjs";
|
|
5
6
|
import { createLintConfig } from "./lint-config.mjs";
|
|
6
7
|
import { foundationFiles } from "./token-policy.mjs";
|
|
@@ -152,10 +153,16 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
|
|
|
152
153
|
});
|
|
153
154
|
if (!existingTokens.length) {
|
|
154
155
|
for (const name of ["Foundation.scenario.tsx", "Foundation.fixture.ts"]) {
|
|
155
|
-
|
|
156
|
-
if (selectedFramework === "preact" && name === "Foundation.scenario.tsx") content = content.replace('from "react"', 'from "preact/hooks"');
|
|
156
|
+
const content = readFileSync(path.join(starter, "design-system/tokens", name), "utf8").replaceAll('"@ds"', JSON.stringify(config.publicAlias));
|
|
157
157
|
addMissing(path.join(tokenRoot, name), content);
|
|
158
158
|
}
|
|
159
|
+
for (const name of ["TokenPreview.tsx", "Scenario.tsx", "fixture.ts"]) {
|
|
160
|
+
let content = readFileSync(path.join(starter, "design-system/patterns/token-preview", name), "utf8");
|
|
161
|
+
if (selectedFramework === "preact") content = content.replace('from "react"', 'from "preact/hooks"');
|
|
162
|
+
addMissing(path.join(dsRoot, "patterns/token-preview", name), content.replaceAll('"@ds"', JSON.stringify(config.publicAlias)));
|
|
163
|
+
}
|
|
164
|
+
const reviewFile = path.relative(root, path.join(dsRoot, "patterns/token-preview/TokenPreview.tsx")).replaceAll("\\", "/");
|
|
165
|
+
if (!config.boundaries.styleWrites.some(item => item.file === reviewFile)) config.boundaries.styleWrites.push({ file: reviewFile, properties: ["--ux-token-preview-value"] });
|
|
159
166
|
}
|
|
160
167
|
tokenFiles.sort((a, b) => foundationFiles.indexOf(path.basename(a)) - foundationFiles.indexOf(path.basename(b)) || a.localeCompare(b));
|
|
161
168
|
const stylesFile = path.join(dsRoot, config.stylesEntry);
|
|
@@ -165,7 +172,17 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
|
|
|
165
172
|
};
|
|
166
173
|
addMissing(stylesFile, tokenFiles.map((file) => `@import ${JSON.stringify(relativeImport(stylesFile, file))};`).join("\n") + "\n");
|
|
167
174
|
const entryFile = path.join(dsRoot, config.publicEntry);
|
|
168
|
-
|
|
175
|
+
const reviewExport = !existingTokens.length ? `export { TokenPreview } from ${JSON.stringify(relativeImport(entryFile, path.join(dsRoot, "patterns/token-preview/TokenPreview")))};\n` : "";
|
|
176
|
+
addMissing(entryFile, `// Export your project's public components and types here.\nimport ${JSON.stringify(relativeImport(entryFile, stylesFile))};\n${reviewExport}`);
|
|
177
|
+
if (reviewExport && existsSync(entryFile) && !readFileSync(entryFile, "utf8").includes("patterns/token-preview/TokenPreview")) {
|
|
178
|
+
let current = readFileSync(entryFile, "utf8");
|
|
179
|
+
if (/\bTokenPreview\b/.test(current)) throw new Error("TokenPreview is already owned by this project. Add a token review scenario using its public API before initializing the foundation.");
|
|
180
|
+
const ast = ts.createSourceFile(entryFile, current, ts.ScriptTarget.Latest, true);
|
|
181
|
+
for (const statement of [...ast.statements].reverse()) if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause) && !statement.exportClause.elements.length) {
|
|
182
|
+
current = current.slice(0, statement.getStart(ast)) + current.slice(statement.end);
|
|
183
|
+
}
|
|
184
|
+
updates.set(entryFile, current + "\n" + reviewExport);
|
|
185
|
+
}
|
|
169
186
|
for (const section of ["components", "patterns", "composition"]) addMissing(path.join(dsRoot, section, ".gitkeep"), "");
|
|
170
187
|
addMissing(path.join(dsRoot, "assets/brand/README.md"), readFileSync(path.join(starter, "design-system/assets/brand/README.md")));
|
|
171
188
|
addMissing(path.join(dsRoot, "assets/fonts/.gitkeep"), "");
|
|
@@ -175,9 +192,9 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
|
|
|
175
192
|
else {
|
|
176
193
|
const original = readFileSync(configFile, "utf8");
|
|
177
194
|
const saved = JSON.parse(original);
|
|
178
|
-
if (saved.framework === undefined) {
|
|
195
|
+
if (saved.framework === undefined || !existingTokens.length) {
|
|
179
196
|
const indent = original.match(/\n([\t ]+)"/)?.[1] ?? " ";
|
|
180
|
-
updates.set(configFile, `${JSON.stringify({ ...saved, framework: selectedFramework }, null, indent)}\n`);
|
|
197
|
+
updates.set(configFile, `${JSON.stringify({ ...saved, framework: selectedFramework, ...(!existingTokens.length ? { boundaries: config.boundaries } : {}) }, null, indent)}\n`);
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
const lintConfigFile = path.join(root, ".ds/oxlintrc.json");
|
package/dist/mcp/apps.mjs
CHANGED
|
@@ -12,7 +12,7 @@ const bridge = (project = false) => readFileSync(new URL(project ? "./project-wi
|
|
|
12
12
|
|
|
13
13
|
export function previewApp(selection) {
|
|
14
14
|
const script = readFileSync(new URL("./preview-widget.bundle.js", import.meta.url), "utf8").replaceAll("</script", "<\\/script");
|
|
15
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ADS preview</title><style>html,body,#root{height:100%;margin:0}body{overflow:
|
|
15
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ADS preview</title><style>html,body,#root{height:100%;margin:0}body{overflow:auto}#ads-preview-status{position:fixed;inset:12px 12px auto;z-index:2147483647;padding:12px;border:1px solid GrayText;background:Canvas;color:CanvasText;font:13px system-ui;white-space:pre-wrap}#ads-preview-status[hidden]{display:none}</style></head><body><aside id="ads-preview-status" role="status" hidden></aside><div id="root"></div>${selection ? `<script type="application/json" id="ads-preview-selection">${safeJson(selection)}</script>` : ""}<script type="module">${script}</script></body></html>`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function tokenWidgetData(inventory) {
|
|
@@ -44,7 +44,7 @@ export function projectApp(options, app, build) {
|
|
|
44
44
|
if (statSync(file).size > 2 * 1024 * 1024) throw new Error("Preview exceeds 2 MiB. Build a focused component/pattern example.");
|
|
45
45
|
const html = readFileSync(file, "utf8");
|
|
46
46
|
if (!/<\/body\s*>/i.test(html) || /<script\b[^>]*\bsrc\s*=|<link\b[^>]*\brel\s*=\s*["']?stylesheet\b/i.test(html)) throw new Error("Preview must be built as self-contained HTML, with JavaScript and styles inlined.");
|
|
47
|
-
const seed = { id: app.id, session: build?.session ?? null, revision: build?.current ? build.revision : null, state: build?.state ?? "unreported" };
|
|
47
|
+
const seed = { id: app.id, kind: app.kind, ...(app.samples ? { samples: app.samples } : {}), session: build?.session ?? null, revision: build?.current ? build.revision : null, state: build?.state ?? "unreported" };
|
|
48
48
|
return html.replace(/<\/body\s*>/i, () => `<script type="application/json" id="ads-preview-seed">${safeJson(seed)}</script><script type="module">${bridge(true)}</script></body>`);
|
|
49
49
|
}
|
|
50
50
|
|
package/dist/mcp/context.mjs
CHANGED
|
@@ -56,7 +56,7 @@ Use this server for UI components/patterns, tokens/themes/branding, layout, inte
|
|
|
56
56
|
3. Select one workflow with ads_skills, then ads_read_skill. ${route.skills.map((skill) => `${skill.task}: ${skill.name}`).join("; ")}. Native skill activation is host-controlled. Relative skill references resolve from its SKILL.md URI. Read support files only when relevant.
|
|
57
57
|
4. ${route.preview} Token samples: ads_scenarios(kind: tokens), then ads_show_scenario or ads_show_tokens(category: id). Token source inspection: ads_tokens with a focused query. Registered previews: ${previews.map((preview) => `${preview.id}: ${preview.tool ?? "ads_show_scenario"}`).join(", ") || "none"}. Showcase URL: ${context.showcase.url ?? "not configured; use the project's showcase command"}.
|
|
58
58
|
Inspection connects a public export to its owning source, available guides, token values and discovered preview IDs. Bounded tools keep discovery focused; explicit resource reads supply detail. Source metadata does not prove layout, interaction or motion. A scenario renders the real public implementation and its fixture context in either review surface.
|
|
59
|
-
5. Check ads_status.mutations. When enabled, discover one owner with ads_units, read relevant source ranges with ads_read_file and submit only a Git unified diff through ads_apply_patch({unit, patch, dryRun}). Include that owner's scenarios, fixtures and required public re-exports, never peer units. tokens is the foundation unit. Dry-run to inspect validation; apply only a passing patch. Fix rejected patches in the owning unit. Intentional project setup, registered guard/compiler code and exceptions use configuration; shared globals use foundation, guides documentation and assets their discovered assets/* unit. Keep mandatory ADS gates. Binary/large Git diffs may use patchFile under .ds/patches/ to avoid model-context bytes. In read-only mode, source edits use the client editor/filesystem tools. Reuse the local public API (${context.publicEntry}). Edit presentation in its ADS owner; keep application data/services/routing in application code and development fixtures beside their scenario; larger scenarios live in composition/. Linaria extracts owned styling. Inspect relevant tokens with ads_tokens and source/owning guides before edits.
|
|
59
|
+
5. Check ads_status.mutations. When enabled, discover one owner with ads_units, read relevant source ranges with ads_read_file and submit only a Git unified diff through ads_apply_patch({unit, patch, dryRun}). Include that owner's scenarios, fixtures and required public re-exports, never peer units. tokens is the foundation unit; its manifest lists companionFiles for required canonical foundation imports when token files are created/deleted in the same patch. Preserve unrelated imports/comments and public-entry statements, including export {};. Dry-run to inspect validation; apply only a passing patch. Fix rejected patches in the owning unit; report unsupported coupled changes instead of bypassing rejection with direct writes. Intentional project setup, registered guard/compiler code and exceptions use configuration; shared globals use foundation, guides documentation and assets their discovered assets/* unit. Keep mandatory ADS gates. Binary/large Git diffs may use patchFile under .ds/patches/ to avoid model-context bytes. In read-only mode, source edits use the client editor/filesystem tools. Reuse the local public API (${context.publicEntry}). Edit presentation in its ADS owner; keep application data/services/routing in application code and development fixtures beside their scenario; larger scenarios live in composition/. Linaria extracts owned styling. Inspect relevant tokens with ads_tokens and source/owning guides before edits.
|
|
60
60
|
6. ${route.validation} Registered commands: ${context.commands.map((command) => command.args?.join(" ") ?? `choose the project package manager for ${command.script}`).join("; ") || "none discovered; inspect the project's build setup"}. Run them from ${context.projectRoot}.
|
|
61
61
|
7. After edits, inspect ads_status.build or ads://project/build explicitly. Running/unreported/old output is not a current successful preview. Fix diagnostics in owned source, wait for rebuilding and invoke the preview tool again. Notifications may not start an agent turn.
|
|
62
62
|
|