synthesisui 0.16.13 → 0.16.15
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/dist/claude-md.js +34 -2
- package/dist/commands/add.js +13 -1
- package/dist/commands/hook.js +40 -3
- package/dist/component-codegen.js +30 -0
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -118,6 +118,32 @@ If it reports a value with no token, do NOT invent one. Say which value and what
|
|
|
118
118
|
you would call it, and let a person decide. An agent that quiets the report by
|
|
119
119
|
adding a token is growing the system unsupervised, which is worse than the
|
|
120
120
|
drift because it looks solved.`;
|
|
121
|
+
/**
|
|
122
|
+
* TWO THINGS AN AGENT GOT WRONG ON 28/07, BOTH CHEAP TO SAY OUT LOUD.
|
|
123
|
+
*
|
|
124
|
+
* It started a dev server to look at its work and left it running. The owner
|
|
125
|
+
* found it by trying to start his own and getting "Another next dev server is
|
|
126
|
+
* already running", with a PID to kill. Nothing in any contract had said to
|
|
127
|
+
* clean up, and a build proves the same thing without holding a port.
|
|
128
|
+
*
|
|
129
|
+
* And it offered to rebuild the create-next-app landing page by hand, on
|
|
130
|
+
* `ds-button` and `ds-card`, when a command already resets it in two seconds.
|
|
131
|
+
* It had found `clean` while exploring the CLI, so this is not a discovery
|
|
132
|
+
* problem - it is that nothing told it the command answers that situation.
|
|
133
|
+
*
|
|
134
|
+
* `--force` needs a person because it deletes. The dry run does not, so the
|
|
135
|
+
* agent is pointed at the harmless half and told to ask for the other.
|
|
136
|
+
*/
|
|
137
|
+
const VERIFY = `
|
|
138
|
+
|
|
139
|
+
**Verify with a build, not a running server.** If you do start a dev server, stop
|
|
140
|
+
it before you finish - one left running holds a port the next person needs.
|
|
141
|
+
|
|
142
|
+
If \`create-next-app\` boilerplate is in your way (the default page, the README,
|
|
143
|
+
the SVGs in \`public/\`), do not rebuild those files by hand. Run
|
|
144
|
+
\`npx synthesisui@latest clean\` to see what it would remove: it only touches files
|
|
145
|
+
nobody has edited. Ask the person before running it with \`--force\`, because that
|
|
146
|
+
one deletes.`;
|
|
121
147
|
const SELF_CHECK_MANUAL = `
|
|
122
148
|
|
|
123
149
|
**After writing or editing any UI file, check it before moving on:**
|
|
@@ -130,7 +156,7 @@ a component checked while you still have its context is a two-line fix; the same
|
|
|
130
156
|
component found in a repo-wide audit next week is an archaeology exercise.${REFUSAL}
|
|
131
157
|
|
|
132
158
|
Better than remembering: \`npx synthesisui@latest connect\` installs the check as
|
|
133
|
-
an editor hook, so it runs on its own and this paragraph stops being your job
|
|
159
|
+
an editor hook, so it runs on its own and this paragraph stops being your job.${VERIFY}`;
|
|
134
160
|
const SELF_CHECK_HOOKED = `
|
|
135
161
|
|
|
136
162
|
**The check runs by itself.** A hook reports on every file you write, naming the
|
|
@@ -138,7 +164,7 @@ token this project already has for anything hardcoded. You do not need to run
|
|
|
138
164
|
\`doctor\` by hand, and you should not wait for a final pass.
|
|
139
165
|
|
|
140
166
|
When it names something, fix it in that file before moving to the next one -
|
|
141
|
-
you still have the context, so it is a two-line change.${REFUSAL}`;
|
|
167
|
+
you still have the context, so it is a two-line change.${REFUSAL}${VERIFY}`;
|
|
142
168
|
/**
|
|
143
169
|
* THE INTERFACE LANGUAGE, READ FROM THE PROJECT RATHER THAN ASKED FOR.
|
|
144
170
|
*
|
|
@@ -226,6 +252,12 @@ YOURSELF. It materializes that component as real typed code in this project, and
|
|
|
226
252
|
extend that. The person who asked you for a feature should never have to know these command
|
|
227
253
|
names or type them; finding the right component is your job, not theirs.
|
|
228
254
|
|
|
255
|
+
When you override a style a component already sets, add \`!\` to your class:
|
|
256
|
+
\`className="text-info!"\`, not \`className="text-info"\`. Both are plain utilities at the
|
|
257
|
+
same specificity, so which one wins is decided by the order Tailwind writes the stylesheet -
|
|
258
|
+
not by the order of the class names, which is what the code looks like it controls. This
|
|
259
|
+
applies to the Tailwind flavour only; the CSS one is layered and needs no marker.
|
|
260
|
+
|
|
229
261
|
Only write something new when nothing in the manifest covers the purpose - and when you do,
|
|
230
262
|
say which entry you considered and why it did not fit. To review a
|
|
231
263
|
component, create an isolated sample page (e.g. \`app/synthesisui-samples/<component>/\`) - do not
|
package/dist/commands/add.js
CHANGED
|
@@ -150,7 +150,19 @@ export async function add(slug, opts) {
|
|
|
150
150
|
console.log("");
|
|
151
151
|
console.log(line(`2. Scope your app: add data-ds="${payload.slug}" to a ROOT element, e.g. ${appDir}/layout.tsx:`));
|
|
152
152
|
console.log("");
|
|
153
|
-
console.log(snippet([
|
|
153
|
+
console.log(snippet([
|
|
154
|
+
`<body data-ds="${payload.slug}" className="bg-canvas">{children}</body>`,
|
|
155
|
+
]));
|
|
156
|
+
// THE SCOPE DELIBERATELY DOES NOT PAINT A BACKGROUND, and every project hits
|
|
157
|
+
// it. `[data-ds]` sets ink and body family only, because scopes NEST: a dark
|
|
158
|
+
// contrast section over a photograph carries its own nested `data-ds` so its
|
|
159
|
+
// tokens remap, and a background on every scope would paint over the picture.
|
|
160
|
+
// So the page background is the root element's job, and saying so here is the
|
|
161
|
+
// fix - an agent found this by reading rendered CSS on 28/07 and had to
|
|
162
|
+
// deduce it.
|
|
163
|
+
console.log(line(` \`bg-canvas\` is not optional: the scope sets text colour and type, not the page`));
|
|
164
|
+
console.log(line(` background. Scopes nest (a dark section carries its own), so painting every one`));
|
|
165
|
+
console.log(line(` would cover whatever is behind it.`));
|
|
154
166
|
// 3. Load the type - the DS ships token NAMES, not the fonts themselves.
|
|
155
167
|
// Next apps get fonts.ts MATERIALIZED (deterministic does, not teaches):
|
|
156
168
|
// next/font = self-hosted + preloaded + adjusted fallback, no FOUT
|
package/dist/commands/hook.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { relative, resolve } from "node:path";
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
4
4
|
import { loadSystem } from "./doctor.js";
|
|
5
5
|
const pass = () => ({ continue: true });
|
|
@@ -13,6 +13,39 @@ const speak = (context) => ({
|
|
|
13
13
|
/** Files where a design value can even appear. A hook that parses a README on
|
|
14
14
|
* every edit is paying for nothing. */
|
|
15
15
|
const UI_FILE = /\.(tsx|jsx|ts|js|css|scss|vue|svelte)$/i;
|
|
16
|
+
/** Written after the hook's first report of any kind, and never read for anything
|
|
17
|
+
* else. Lives in our own directory, so removing the system removes the memory
|
|
18
|
+
* too. It is committed with `_synthesisui/`, which means the greeting is
|
|
19
|
+
* per-project and not per-teammate - a wart worth the simplicity, since the
|
|
20
|
+
* second person to clone a wired repo is not the one wondering if it installed. */
|
|
21
|
+
const GREETED = "_synthesisui/.hook-greeted";
|
|
22
|
+
const markGreeted = (root) => writeFile(join(root, GREETED), "", "utf8").catch(() => { });
|
|
23
|
+
/** One sentence, once per project. Addressed to the agent because the hook has
|
|
24
|
+
* no way to reach the person, and the person is who needs to know. */
|
|
25
|
+
async function greet(root, rel) {
|
|
26
|
+
const marker = join(root, GREETED);
|
|
27
|
+
try {
|
|
28
|
+
await readFile(marker, "utf8");
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Not greeted yet. Write first: a failed write must not produce a hook that
|
|
33
|
+
// greets on every single edit for the rest of the project's life.
|
|
34
|
+
try {
|
|
35
|
+
await writeFile(marker, "", "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [
|
|
42
|
+
`${rel} - checked, nothing to name. Every value in it has a token.`,
|
|
43
|
+
"",
|
|
44
|
+
"That check now runs on every file you write, by itself. Tell the person once",
|
|
45
|
+
"that it is live, so they know it installed, and then stop mentioning it - from",
|
|
46
|
+
"here it only speaks when something needs a name.",
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
16
49
|
async function report(root, filePath) {
|
|
17
50
|
const { table } = await loadSystem(root);
|
|
18
51
|
if (table.byName.size === 0)
|
|
@@ -28,7 +61,11 @@ async function report(root, filePath) {
|
|
|
28
61
|
// no token to move to, so the only honest advice is "ask a person" - and
|
|
29
62
|
// saying that after every edit trains the reader to skip the block.
|
|
30
63
|
if (named.length === 0 && phantoms.length === 0)
|
|
31
|
-
return
|
|
64
|
+
return greet(root, rel);
|
|
65
|
+
// A report IS the evidence the greeting exists to provide, so it counts as the
|
|
66
|
+
// introduction. Otherwise a project whose first file had drift would get the
|
|
67
|
+
// "it is live" sentence afterwards, telling somebody who just watched it work.
|
|
68
|
+
await markGreeted(root);
|
|
32
69
|
const lines = [`${rel} - checked against ${table.name ?? table.slug}.`];
|
|
33
70
|
if (named.length > 0) {
|
|
34
71
|
lines.push("", "Values written by hand that this system already has a name for:", ...named
|
|
@@ -345,8 +345,38 @@ function header(slug, name, version, mode) {
|
|
|
345
345
|
`// On-system by construction: every style resolves to the DS tokens.`,
|
|
346
346
|
`// Global setup (once per app): ${setup}`,
|
|
347
347
|
`// and put data-ds="${slug}" on a root element (e.g. <body data-ds="${slug}">).`,
|
|
348
|
+
...(mode === "tailwind" ? [OVERRIDE_WARNING] : []),
|
|
348
349
|
].join("\n");
|
|
349
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* THE OVERRIDE THAT LOSES WITHOUT SAYING SO.
|
|
353
|
+
*
|
|
354
|
+
* `className` is appended last in the string below, which looks like it wins
|
|
355
|
+
* and does not. Two plain utilities setting the same property have the same
|
|
356
|
+
* specificity, so the winner is decided by the order Tailwind emits them into
|
|
357
|
+
* the stylesheet - which depends on the order of names in the theme, not on
|
|
358
|
+
* anything visible from here.
|
|
359
|
+
*
|
|
360
|
+
* Found by an agent building against these components: it wanted a blue title
|
|
361
|
+
* on a `CardTitle` whose base carries `text-foreground`, and only got it by
|
|
362
|
+
* reaching for `!` - which it knew about because it had written the trip-up
|
|
363
|
+
* into its own project memory on an earlier run (my-test4, 27/07).
|
|
364
|
+
*
|
|
365
|
+
* CSS mode does not have this. Its rules live in `@layer components`, which
|
|
366
|
+
* Tailwind's utilities outrank by construction, so an override there just
|
|
367
|
+
* works. This warning is emitted for the Tailwind flavour only.
|
|
368
|
+
*
|
|
369
|
+
* A real fix has to resolve utility conflicts - knowing that `text-info`
|
|
370
|
+
* collides with `text-foreground` but not with `text-lg`, though all three
|
|
371
|
+
* start with `text-`. That needs the system's own colour and size vocabulary,
|
|
372
|
+
* which this generator has. Until it is built, the failure at least stops
|
|
373
|
+
* being silent, which is the part that actually costs people time.
|
|
374
|
+
*/
|
|
375
|
+
const OVERRIDE_WARNING = `//
|
|
376
|
+
// Overriding a style this component already sets? Add \`!\`:
|
|
377
|
+
// <Thing className="text-info!" /> not className="text-info"
|
|
378
|
+
// Both are plain utilities at the same specificity, so which one wins is
|
|
379
|
+
// decided by the stylesheet's order, not by this string's.`;
|
|
350
380
|
const joinCls = (parts) => `[${parts.join(", ")}].filter(Boolean).join(" ")`;
|
|
351
381
|
/** JSDoc showing how to compose the component with its parts + content, so the
|
|
352
382
|
* materialized code doesn't read as "a bare shell renders nothing" (dogfood
|
package/package.json
CHANGED