sheleg-design-skill 1.53.0 → 1.54.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +5 -4
  3. package/bin/cli.js +132 -1
  4. package/cursor/rules/sheleg-design.mdc +6 -0
  5. package/kits/surveyor/.design-sync/config.json +14 -0
  6. package/kits/surveyor/.design-sync/conventions.md +35 -0
  7. package/kits/surveyor/README.md +33 -0
  8. package/kits/surveyor/package.json +29 -0
  9. package/kits/surveyor/src/Button.md +17 -0
  10. package/kits/surveyor/src/Button.tsx +33 -0
  11. package/kits/surveyor/src/Card.md +11 -0
  12. package/kits/surveyor/src/Card.tsx +24 -0
  13. package/kits/surveyor/src/Chip.md +11 -0
  14. package/kits/surveyor/src/Chip.tsx +25 -0
  15. package/kits/surveyor/src/Closer.md +14 -0
  16. package/kits/surveyor/src/Closer.tsx +21 -0
  17. package/kits/surveyor/src/Delta.md +12 -0
  18. package/kits/surveyor/src/Delta.tsx +23 -0
  19. package/kits/surveyor/src/Dialogue.md +11 -0
  20. package/kits/surveyor/src/Dialogue.tsx +24 -0
  21. package/kits/surveyor/src/Empty.md +13 -0
  22. package/kits/surveyor/src/Empty.tsx +21 -0
  23. package/kits/surveyor/src/Field.md +12 -0
  24. package/kits/surveyor/src/Field.tsx +38 -0
  25. package/kits/surveyor/src/Heading.md +11 -0
  26. package/kits/surveyor/src/Heading.tsx +19 -0
  27. package/kits/surveyor/src/NavBar.md +14 -0
  28. package/kits/surveyor/src/NavBar.tsx +20 -0
  29. package/kits/surveyor/src/Portrait.md +11 -0
  30. package/kits/surveyor/src/Portrait.tsx +12 -0
  31. package/kits/surveyor/src/Pulse.md +11 -0
  32. package/kits/surveyor/src/Pulse.tsx +15 -0
  33. package/kits/surveyor/src/Rule.md +10 -0
  34. package/kits/surveyor/src/Rule.tsx +18 -0
  35. package/kits/surveyor/src/Stat.md +10 -0
  36. package/kits/surveyor/src/Stat.tsx +17 -0
  37. package/kits/surveyor/src/StatSlab.md +12 -0
  38. package/kits/surveyor/src/StatSlab.tsx +16 -0
  39. package/kits/surveyor/src/TintPanel.md +12 -0
  40. package/kits/surveyor/src/TintPanel.tsx +17 -0
  41. package/kits/surveyor/src/index.ts +35 -0
  42. package/kits/surveyor/src/styles.css +828 -0
  43. package/kits/surveyor/tsconfig.json +15 -0
  44. package/package.json +3 -3
  45. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  46. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  47. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  48. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +1 -1
  49. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  50. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +2 -2
  51. package/plugins/sheleg-design/skills/sheleg-design/STYLE_PACK_INDEX.md +3 -2
  52. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
  53. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
  54. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +6 -1
  55. package/plugins/sheleg-design/skills/sheleg-design/styles/surveyor.md +402 -0
  56. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
  57. package/plugins/sheleg-design/skills/sheleg-design/styles/test-drive.md +4 -1
  58. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/surveyor.css +285 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,109 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.54.1] - 2026-08-29
10
+
11
+ ### The installer refuses the shadow it documents
12
+
13
+ Both installers write a plain skill copy on request, and on a machine where
14
+ `sheleg-design` is installed as a Claude Code plugin, a write to that home's
15
+ `~/.claude/skills/sheleg-design` is a copy that shadows the plugin and serves
16
+ this frozen version forever. Neither installer checked the plugin channel at
17
+ all, and CI tested a fresh HOME only, so the plugin-present case had never run
18
+ anywhere. Canon: make-skill v0.25.0, `references/distribution.md` §"The
19
+ installer must refuse the shadow it documents"; family audit SHD-07/UM-03.
20
+
21
+ ### Added
22
+
23
+ - `bin/cli.js` and `install.sh` now consult the **target home's**
24
+ `~/.claude/plugins/installed_plugins.json` before any write to that home's
25
+ `~/.claude/skills/sheleg-design`, and refuse with **exit 3** when the plugin
26
+ channel owns the skill. The refusal names the real spec read from the JSON
27
+ (`sheleg-design@<marketplace>` — the marketplace name differs from the
28
+ plugin name here, and a remedy that guesses it sends the operator to a
29
+ marketplace that does not exist), prints the plugin-channel remedy
30
+ (`claude plugin marketplace update` + `claude plugin update <spec>`, plus
31
+ the family launcher line), and offers `--force` as the deliberate override.
32
+ The `plugins/marketplaces/` directory is read only as the fallback signal —
33
+ it under-reports (a `directory`-sourced marketplace has no dir there), which
34
+ is the fail-open class the canon names. A missing or unparsable JSON reads
35
+ as "no plugin": fail open, never crash. Only the Claude Code channel is
36
+ gated — `.cursor/` and every other agent's install are untouched, and a
37
+ project-level `.claude/` falls open naturally because a project holds no
38
+ plugin registry.
39
+ - `install.sh` accepts `--force` (the override for the gate above) and refuses
40
+ unknown flags with exit 2 instead of treating them as a target directory.
41
+ - `test/installer_test.js` — thirteen cases against throwaway HOMEs, wired
42
+ into `npm test` and CI: plugin-present (exit 3 + remedy + nothing written,
43
+ all three asserted), the differently-named marketplace spec carried into the
44
+ remedy, `--force` installing, corrupt JSON installing, a prefix-collider
45
+ (`sheleg-design-extra@x`) not falsely refused, marketplaces-dir-only still
46
+ refusing, fresh HOME still installing, the `.cursor` channel untouched by
47
+ the gate, and the install.sh mirrors of the same. Watched failing first: run
48
+ against the pre-fix installers, 7 cases red.
49
+
50
+ ### Changed
51
+
52
+ - Both installers now end a successful install by saying how the next version
53
+ arrives (`npx sheleg-design-skill@latest --force`, or the family launcher) —
54
+ an installer that never mentions updates has still chosen an update model:
55
+ never.
56
+ - `bin/cli.js --help` documents the exit-code contract, including the new
57
+ exit 3.
58
+ - CONTRIBUTING: release tags must be **annotated** (`git tag -a`) — v1.53.0
59
+ and v1.54.0 were lightweight, and `git submodule status` describes a pinned
60
+ commit with `git describe`, which ignores lightweight tags, so the family
61
+ umbrella misreported the member's version (SHD-07/UM-03). Applies from this
62
+ release forward; the old tags are not re-cut.
63
+
64
+ ## [1.54.0] - 2026-08-29
65
+
66
+ ### The thirty-eighth pack — the terrain is mapped
67
+
68
+ **`surveyor`**, extracted from an AI-visibility tracker's front page and named per
69
+ ADR-0001 for the register it encodes: a page that sells a survey of terrain the
70
+ buyer cannot see — contour lines for texture, counted stat slabs for readings, and
71
+ the product appearing only as flat portraits.
72
+
73
+ ### Added
74
+
75
+ - `styles/surveyor.md` — widened, all thirteen headings; `Themes: light only`;
76
+ `Rank: unordered — 3 status role(s)`, and the missing warn is a stated decision
77
+ rather than a gap: the authored palette ships no amber, and one invented from the
78
+ WordPress vendor presets would be the exact rule-7 trap. Two forks written from
79
+ both sides: against `test-drive` (a flat portrait against a running machine on
80
+ the same warm paper) and against `showroom` (a field survey pinned flat against
81
+ one lit museum specimen).
82
+ - `styles/tokens/surveyor.css` — the authored layer dug out of a WordPress site:
83
+ SE Ranking's own `se-uikit` tokens verified against the render, with the vendor
84
+ preset `:root` (a full palette that paints nothing) left behind. The corrections
85
+ climb the reference's own ladder: its CTA label measures `#ffffff` on `#0d9488`
86
+ at 3.74:1, so the hover step `#0a7269` becomes the text-bearing `--action` and
87
+ the brand teal keeps large-only jobs; the pink that answers (`#ff91da`, 1.84:1)
88
+ gains a speaking step `--pink-deep` `#a83f88` at 5.32:1. The page's elevation
89
+ model is recorded as measured: box-shadow none everywhere except the nav after
90
+ it detaches on scroll.
91
+ - `kits/surveyor/` — the six-name spine plus `StatSlab`, `Delta`, `Portrait`,
92
+ `TintPanel`, `Closer`, `Dialogue`, `Pulse`, `Field`, `NavBar`, `Empty`. The
93
+ contour terrain ships as a CSS technique (`repeating-radial-gradient` rings)
94
+ over the three measured line inks, because a PNG asset cannot live in a token
95
+ layer.
96
+ - `test/scenarios.md` T36 — `surveyor` against `test-drive`, the warm-paper
97
+ analytics-landing fork, with its negative branch per standing instruction 4.
98
+ - ADR-0001 gains its fifteenth application: `surveyor`, with `contour`, `atlas`,
99
+ `heatmap`/`benchmark` and `field-survey` weighed and rejected.
100
+
101
+ ### Changed
102
+
103
+ - `test-drive` and `showroom` each gain the reciprocal fork clause naming
104
+ `surveyor`.
105
+ - Counted claims moved with the tree: thirty-eight packs and kits across
106
+ `SKILL.md`, the index (six core / thirty-two widened), `SURFACE_COMPOSITION.md`
107
+ (the accent is `--accent` in 35 of the 38; twenty-five layers carry a
108
+ non-text role), `FIGMA_BRIDGE.md` (twelve of the thirty-eight ship a twin),
109
+ the template's theme split (12/17/9), README, DOCMAP, both installers and all
110
+ three manifests.
111
+
9
112
  ## [1.53.0] - 2026-08-27
10
113
 
11
114
  ### The thirty-seventh pack — the visitor is handed the keys
package/README.md CHANGED
@@ -31,7 +31,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
31
31
 
32
32
  This skill is the taste layer. It gives a coding agent **one motion
33
33
  methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
34
- decides whether to animate before it decides how, and **thirty-seven locked style
34
+ decides whether to animate before it decides how, and **thirty-eight locked style
35
35
  packs** with ready-made design tokens, so what it builds reads as one system
36
36
  instead of a pile of effects.
37
37
 
@@ -77,6 +77,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
77
77
  | `onionskin` | a white technical sheet at **96.5% zero radius** — the squarest page in the library — where **two bases do all the work and everything quiet is one of them at an alpha**: text dims through the ink, structure through a navy that is never a word, so there is no grey ramp at all. A dot grid, dashed hairlines, three faces with one job each, and an **11px working size** | **developer and AI infrastructure whose front page is a working document** — memory and context engines, retrieval services, agent runtimes, evaluation and observability surfaces (standalone) |
78
78
  | `deskmate` | a warm beige working day lit from **one source above the top edge** — every gradient origin in the reference sits at or above `y=0` — where a single four-stop ramp (peach, lilac, violet, deep navy) washes the field, fills a panel and fills one word of a heading, and everything a hand touches is a 56px pill against 32px slabs | **products sold as a colleague rather than a tool** — AI employees and chat-native agents, assistants living in Slack or Teams, delegation and approval surfaces (standalone) |
79
79
  | `test-drive` | warm paper and white cards where **the live product runs inside drawn browser chrome** — traffic-light dots, a real address, the engine on — one coral in two tokens (the brand hue lights, fills and blinks; a darker step carries every body-size word), every control lit by a four-layer shadow in its own hue, a handwritten face and a yellow marker annotating from the margin | **self-serve SaaS sold by handing over the running product** — analytics, dashboards, developer tools with a visible console, indie SaaS (standalone) |
80
+ | `surveyor` | peach paper where **an unseen terrain is mapped** — fine contour lines as the one texture, flat white and tinted cards with no shadow anywhere, a teal whose text-bearing steps are the reference's own hover ladder, a pink that answers but may not speak, and the product appearing only as flat portraits | **visibility, monitoring and benchmark tools** — AI-search and SEO trackers, share-of-voice and brand-listening platforms sold to marketing teams (standalone) |
80
81
  | `outrank` | white field with one violet that carries text in both directions, a 5px light ring standing in for a button shadow, and two type families split by how long a thing is read | SEO and growth SaaS, agent-run back-office — the pack for a product that ships a landing and a dashboard at once |
81
82
  | `orchard` | warm oat field of rounded slabs, sage brand + one candy-orange action, rounded geometric display, soft-3D pills built from inset light | friendly consumer biotech, DTC wellness, testing kits & supplements |
82
83
  | `field-notes` | warm green-cast paper ruled by hairlines, one rust accent, a hero that dissolves into the page instead of ending, numbered mono eyebrows, crop marks, colour that encodes how a claim is known | open-source & developer tools sold on auditability — code intelligence, provenance, evals, agent memory |
@@ -177,7 +178,7 @@ skills.
177
178
  | `DESIGN_SYNC_BRIDGE.md` | The Claude Design contract: what a pack sends to claude.ai/design and in what shape, the rule for each of the four reference types, and the border motion does not cross |
178
179
  | `FIGMA_BRIDGE.md` | The design↔code contract: how a pack's tokens map onto Figma variable collections and modes, how to implement a design without importing raw values, and what cannot cross the border |
179
180
  | `AI_PRODUCT_PATTERNS.md` | The surfaces a model drives: the five states of a call, streaming instead of spinners, latency, provenance and uncertainty, agent confirmations, and the bans that keep it honest |
180
- | `styles/*.md` | The thirty-seven style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
181
+ | `styles/*.md` | The thirty-eight style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
181
182
  | `styles/tokens/*.css` | The ready-made token layer per pack, copied verbatim instead of transcribed (`workbench` and `field-notes` each ship a light `:root` plus a `data-theme="dark"` twin) |
182
183
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
183
184
 
@@ -248,7 +249,7 @@ cd ./ds-workbench && npm install && npm run build
248
249
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
249
250
  pack's **bans** as the design system's own README, `styles.css` built from
250
251
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
251
- identical in all thirty-seven kits, so switching packs swaps identity rather than API,
252
+ identical in all thirty-eight kits, so switching packs swaps identity rather than API,
252
253
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
253
254
  not cross into Figma: a kit is the static half of a pack, and saying so is what
254
255
  stops an agent inventing motion to fill the silence.
@@ -298,7 +299,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
298
299
  One honest limit: the npx installer is checked by asserting its runtime bundle
299
300
  walker exists, not by reading a file list — it has none by design. What proves
300
301
  it ships the right files is CI, which installs the bundle through **both**
301
- installers and `diff -r`s the result against the source, then builds all thirty-seven
302
+ installers and `diff -r`s the result against the source, then builds all thirty-eight
302
303
  kits.
303
304
 
304
305
  `test/scenarios.md` (T1–T34) is the behavioral harness: fresh subagents given a
package/bin/cli.js CHANGED
@@ -225,6 +225,13 @@ ${c("bold", "Default")}
225
225
  Auto-detects: uses .cursor/ if present, else .claude/ if present,
226
226
  otherwise creates .cursor/skills/${SKILL_SLUG}/.
227
227
 
228
+ ${c("bold", "Exit codes")}
229
+ 0 installed or skipped 2 usage error
230
+ 1 packaging bug, or 3 refused: the sheleg-design PLUGIN is installed
231
+ overwrite refused in the target home — a plain copy in
232
+ ~/.claude/skills would shadow it and serve this
233
+ frozen version forever (--force overrides)
234
+
228
235
  ${c("bold", "What it installs")}
229
236
  SKILL.md the agent-facing skill (discovery + principles)
230
237
  SHELEG_DESIGN.md the full reference (architecture, recipes, why it works)
@@ -234,7 +241,7 @@ ${c("bold", "What it installs")}
234
241
  DESIGN_SYNC_BRIDGE.md the Claude Design contract (what a pack sends, and
235
242
  what does not cross)
236
243
  AI_PRODUCT_PATTERNS.md chat / agent / streaming surfaces (honest state)
237
- styles/ thirty-seven style packs — instrument-console (dark console),
244
+ styles/ thirty-eight style packs — instrument-console (dark console),
238
245
  editorial-luxury (warm editorial), workbench (light/dark
239
246
  product UI), briefing-room (dark 16:9 presentation deck),
240
247
  atrium (warm cream consumer health), patchbay (dark live
@@ -254,6 +261,10 @@ ${c("bold", "What it installs")}
254
261
  inside drawn browser chrome, controls lit by a
255
262
  four-layer shadow in their own hue, and a
256
263
  handwritten hand annotating the tour),
264
+ surveyor (peach paper mapping unseen terrain in
265
+ contour lines, flat shadowless cards, a teal that
266
+ states and a pink that answers, and the product
267
+ shown only as portraits),
257
268
  babylove (white +
258
269
  one orange, seven tokens over Tailwind defaults),
259
270
  outrank (white +
@@ -324,6 +335,112 @@ function resolveTargetDir(opts, cwd) {
324
335
  return path.join(cwd, ".cursor", "skills", SKILL_SLUG);
325
336
  }
326
337
 
338
+ // Exit codes are the contract: 0 installed or skipped, 1 packaging bug or
339
+ // overwrite refusal, 2 usage error, 3 refused — the plugin channel owns the
340
+ // target home's Claude Code install (--force overrides).
341
+ const EXIT_PLUGIN_PRESENT = 3;
342
+
343
+ /**
344
+ * The home whose Claude Code channel this write would land in, or null.
345
+ *
346
+ * Only the Claude Code channel is gated: a write to `<H>/.claude/skills/
347
+ * sheleg-design` is the shape that can shadow a plugin installed in `<H>`.
348
+ * `.cursor/` and every other agent's skill directory have no plugin channel,
349
+ * so installs there are untouched by the check. A project-level `.claude/`
350
+ * matches this shape too, and falls open naturally: a project holds no
351
+ * `plugins/installed_plugins.json`, so the gate reads "no plugin" and the
352
+ * install proceeds.
353
+ */
354
+ function claudeHomeOf(targetDir) {
355
+ const parts = path.resolve(targetDir).split(path.sep);
356
+ const tail = parts.slice(-3).join("/");
357
+ if (tail !== `.claude/skills/${SKILL_SLUG}`) return null;
358
+ return parts.slice(0, -3).join(path.sep) || path.sep;
359
+ }
360
+
361
+ /**
362
+ * The plugin spec (`<name>@<marketplace>`) installed for sheleg-design in
363
+ * this home, or null.
364
+ *
365
+ * `installed_plugins.json` is the record of what is actually installed. The
366
+ * `plugins/marketplaces/<name>` directory under-reports: a marketplace added
367
+ * from a local `directory` source has no dir there at all, and plugin names
368
+ * differ from marketplace names — this very plugin is `sheleg-design` shipped
369
+ * from the `sheleg-design-skill` marketplace — so a check keyed on the dir
370
+ * alone stays green while the shadow lands. Absence and corruption both read
371
+ * as "no plugin": the fresh HOME is the common case, and an installer that
372
+ * crashes on a parse error refuses the machines that need it most.
373
+ */
374
+ function installedPluginSpec(home) {
375
+ try {
376
+ const raw = fs.readFileSync(
377
+ path.join(home, ".claude", "plugins", "installed_plugins.json"),
378
+ "utf8",
379
+ );
380
+ const parsed = JSON.parse(raw);
381
+ const plugins =
382
+ parsed &&
383
+ typeof parsed === "object" &&
384
+ parsed.plugins &&
385
+ typeof parsed.plugins === "object"
386
+ ? parsed.plugins
387
+ : parsed;
388
+ if (!plugins || typeof plugins !== "object") return null;
389
+ for (const spec of Object.keys(plugins)) {
390
+ if (spec === SKILL_SLUG) return `${SKILL_SLUG}@${SKILL_SLUG}`;
391
+ if (spec.startsWith(SKILL_SLUG + "@")) return spec;
392
+ }
393
+ } catch {
394
+ // missing or corrupt = no plugin — fail open on absence, never crash
395
+ }
396
+ return null;
397
+ }
398
+
399
+ /**
400
+ * One channel per agent. A plain `<H>/.claude/skills/sheleg-design` beside an
401
+ * installed plugin is two listings of the same skill, and the stale copy wins
402
+ * — the exact shadow the family canon forbids (make-skill
403
+ * references/distribution.md §"The installer must refuse the shadow it
404
+ * documents"). Refuse rather than create it, and refuse LOUDLY: a refusal
405
+ * that exits 0 reads as success to every script above it. Reproduced live
406
+ * 2026-08-29: a bare `npx @ssheleg/telegram-dev` shipped three shadows past a
407
+ * marketplace-dir-only check while the plugin was enabled.
408
+ */
409
+ function refuseIfPluginOwnsChannel(targetDir, force) {
410
+ const home = claudeHomeOf(targetDir);
411
+ if (!home || force) return;
412
+ const spec = installedPluginSpec(home);
413
+ const marketplaces = path.join(home, ".claude", "plugins", "marketplaces");
414
+ const mktDir = [SKILL_SLUG, "sheleg-design-skill"]
415
+ .map((n) => path.join(marketplaces, n))
416
+ .find((p) => fs.existsSync(p));
417
+ if (!spec && !mktDir) return;
418
+
419
+ const remedySpec = spec || "sheleg-design@sheleg-design-skill";
420
+ const remedyMarketplace = remedySpec.split("@")[1];
421
+ const found = spec
422
+ ? `installed as the Claude Code plugin ${spec}\n` +
423
+ ` (declared in ${path.join(home, ".claude", "plugins", "installed_plugins.json")})`
424
+ : `registered as a Claude Code marketplace\n (${mktDir})`;
425
+ console.error(
426
+ c("yellow", `refused: ${SKILL_SLUG} is already ${found}.`) +
427
+ `\n A plain copy in ${path.join(home, ".claude", "skills", SKILL_SLUG)}\n` +
428
+ ` would shadow the plugin and serve this frozen version forever.\n` +
429
+ ` Update the plugin channel instead:\n` +
430
+ ` ${c("bold", `claude plugin marketplace update ${remedyMarketplace}`)}\n` +
431
+ ` ${c("bold", `claude plugin update ${remedySpec}`)}\n` +
432
+ ` Family launcher (updates every member, prunes shadow copies):\n` +
433
+ ` ${c("bold", "npx --yes sshlg-skills@latest update")}\n` +
434
+ ` Pass --force to write the plain copy anyway — a deliberate choice\n` +
435
+ ` to run two channels, where the stale one wins.`,
436
+ );
437
+ // Offered on the refusal path too: the skill IS present on this machine —
438
+ // as the plugin — so the routing block is exactly as wanted as on the
439
+ // install path.
440
+ offerRouters();
441
+ process.exit(EXIT_PLUGIN_PRESENT);
442
+ }
443
+
327
444
  function main() {
328
445
  const opts = parseArgs(process.argv.slice(2));
329
446
 
@@ -350,6 +467,10 @@ function main() {
350
467
 
351
468
  const targetDir = resolveTargetDir(opts, cwd);
352
469
 
470
+ // Before any write to a home's ~/.claude/skills/sheleg-design: if that
471
+ // home's plugin channel already owns this skill, refuse (exit 3).
472
+ refuseIfPluginOwnsChannel(targetDir, opts.force);
473
+
353
474
  // Verify the bundle is intact before touching the filesystem.
354
475
  for (const f of CORE_FILES) {
355
476
  if (!fs.existsSync(path.join(SKILL_DIR, f))) {
@@ -394,6 +515,16 @@ function main() {
394
515
  );
395
516
 
396
517
  offerRouters();
518
+
519
+ // The last line says how the next version arrives — "installed" is not a
520
+ // complete sentence. Auto-update is off on purpose: this member composes
521
+ // with its family, and per-marketplace autoUpdate moves each member on its
522
+ // own clock, into combinations nobody tested together.
523
+ console.log(
524
+ `Updates: rerun ${c("bold", "npx sheleg-design-skill@latest --force")}, or refresh the\n` +
525
+ `whole family with ${c("bold", "npx --yes sshlg-skills@latest update")} (every channel,\n` +
526
+ `and it prunes plain copies that would shadow a plugin).\n`,
527
+ );
397
528
  }
398
529
 
399
530
  /**
@@ -132,6 +132,12 @@ every control lit by a four-layer shadow in its own hue, a handwritten face and
132
132
  yellow marker annotating from the margin, machine surfaces quarantined in their own
133
133
  dark ladder that ignores the theme, for self-serve SaaS sold by handing over the
134
134
  running product;
135
+ surveyor — warm peach paper where an unseen terrain is mapped: fine contour lines as
136
+ the single texture, flat white and tinted cards with no shadow anywhere but the
137
+ detached nav, one teal whose text-bearing steps climb the reference's own hover
138
+ ladder, a pink that answers but may not speak, TT Fors at a 600 ceiling with tabular
139
+ numerals, and the product shown only as flat portraits, for visibility, monitoring
140
+ and benchmark tools sold to marketing teams;
135
141
  otherwise follow the contract below (self-contained on purpose).
136
142
 
137
143
  ## Whether to animate at all — before how
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/surveyor",
3
+ "globalName": "ShelegSurveyor",
4
+ "shape": "package",
5
+ "buildCmd": "npm run build",
6
+ "srcDir": "src",
7
+ "tsconfig": "tsconfig.json",
8
+ "cssEntry": "src/styles.css",
9
+ "docsDir": "src",
10
+ "readmeHeader": ".design-sync/conventions.md",
11
+ "guidelinesGlob": [
12
+ "guidelines/*.md"
13
+ ]
14
+ }
@@ -0,0 +1,35 @@
1
+ # Surveyor — conventions for a design agent
2
+
3
+ This kit is the built form of the Surveyor style pack. Read these before generating
4
+ anything with it; they are the pack's bans, stated where a design tool will see them.
5
+
6
+ ## Colour
7
+
8
+ - **The teal is a ladder and the roles are shifted one step down**: `--action`
9
+ (#0a7269) carries every word and text-bearing fill; `--accent` (#0d9488) is
10
+ large-only — stat figures, marks, the Q glyph. White body copy on #0d9488 (3.74:1)
11
+ is the reference's recorded failure; do not restore it.
12
+ - **The pink never speaks at `--pink`** (1.84:1 on the field): glyphs, series and
13
+ tints only. A pink word is `--pink-deep`.
14
+ - **Tense as tint**: mint = shipping, pink = coming. Swapping them lies.
15
+ - **Three statuses, no warn** — an amber here would be an invented value.
16
+
17
+ ## Geometry
18
+
19
+ - Controls 10px, cards 24px, panels 32px, badges 6px, pill 99px. The corpus's 20
20
+ and 12 are nested instances (24−4, 20−8), never free values.
21
+ - Nothing is bolder than 600, and the heading tracking RELAXES at narrow widths.
22
+
23
+ ## Elevation
24
+
25
+ - Flat. White/mint/pink tints on peach are the whole depth. The one shadow is the
26
+ detached nav's, toggled on scroll.
27
+
28
+ ## Proof
29
+
30
+ - The product appears as `Portrait` — a flat still. No browser chrome, no
31
+ spotlight, no animation inside the image.
32
+
33
+ ## Motion
34
+
35
+ - One 0.2s clock and one pulse ring. Nothing moves, lifts, scales or scrolls.
@@ -0,0 +1,33 @@
1
+ # @sheleg-design/surveyor
2
+
3
+ The React reference kit for the **Surveyor** style pack — warm peach paper where an
4
+ unseen terrain is mapped: contour lines for texture, flat portraits for proof, a teal
5
+ that states and a pink that answers.
6
+
7
+ The pack document is the source of truth for every value here:
8
+ `plugins/sheleg-design/skills/sheleg-design/styles/surveyor.md`. This kit is what
9
+ those values look like when they are built, and `src/styles.css` opens with the
10
+ pack's token layer copied byte for byte — never transcribed.
11
+
12
+ ```bash
13
+ npm install && npm run build # tsc only; there is no bundler in this kit
14
+ ```
15
+
16
+ ## What ships
17
+
18
+ **The spine**, identical in name, props and types across every SHELEG kit: `Button`,
19
+ `Card`, `Chip`, `Stat`, `Heading`, `Rule`.
20
+
21
+ **The signature**: `StatSlab` (the counted reading in the working hue), `Delta` (a
22
+ reading's movement, arrow included), `Portrait` (the flat still), `TintPanel` (tense
23
+ as tint), `Closer` (the one dark slab — the signature element), `Dialogue` (the
24
+ teal-Q/pink-A pair), `Pulse` (the one loop), `Field`, `NavBar`, `Empty`.
25
+
26
+ ## The three rules a generator loses first
27
+
28
+ 1. The page is flat: no shadow on anything but the detached nav, and elevation is a
29
+ tint. A `shadow-md` is a foreign object.
30
+ 2. The teal ladder is shifted for AA: `--action` `#0a7269` writes and fills;
31
+ `--accent` `#0d9488` is large-only (`#ffffff` on it is 3.74:1).
32
+ 3. The pink answers, it does not speak: `--pink` for glyphs and tints beside
33
+ readable ink, `--pink-deep` when pink must carry a word.
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@sheleg-design/surveyor",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.6.0",
27
+ "@types/react": "^18.3.0"
28
+ }
29
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ One height (`--control-h`, 48px: 12×24 padding on 16px/500) at `--r-control`, and a
6
+ fill that only ever changes colour — no shadow, no scale, no travel. `primary` rests
7
+ on `--action` (the reference's own hover step, promoted for AA: `#ffffff` on
8
+ `#0a7269` is 5.79:1) and hovers to `--action-hover`. `secondary` is the measured
9
+ outline: transparent on the field with a 1px `--ink` border. `ghost` tints `--mint`
10
+ on hover. Disabled keeps the reference's own literal pair.
11
+
12
+ Focus is the pack's correction: the fill step **and** a 2px `--focus-color` outline.
13
+
14
+ ```tsx
15
+ <Button>Start free trial</Button>
16
+ <Button variant="secondary">Book a demo</Button>
17
+ ```
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ButtonProps {
4
+ /** `primary` is the accent fill — at most one per view. */
5
+ variant?: 'primary' | 'secondary' | 'ghost';
6
+ size?: 'sm' | 'md' | 'lg';
7
+ disabled?: boolean;
8
+ onClick?: () => void;
9
+ children: ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export function Button({
14
+ variant = 'primary',
15
+ size = 'md',
16
+ disabled = false,
17
+ onClick,
18
+ children,
19
+ className,
20
+ }: ButtonProps) {
21
+ return (
22
+ <button
23
+ type="button"
24
+ className={['sv-btn', `sv-btn--${variant}`, `sv-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ A card is `--surface` at `--r-card` (24px) with 24px padding and **nothing else** —
6
+ no border, no shadow, no hover. Elevation on this page is a tint, and the one drop
7
+ shadow belongs to the detached nav.
8
+
9
+ ```tsx
10
+ <Card title="Business owners">…</Card>
11
+ ```
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface CardProps {
4
+ title?: string;
5
+ /** Right-aligned metadata on the title row: a count, an id, a timestamp. */
6
+ meta?: string;
7
+ children: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ export function Card({ title, meta, children, className }: CardProps) {
12
+ const head = title !== undefined || meta !== undefined;
13
+ return (
14
+ <section className={['sv-card', className].filter(Boolean).join(' ')}>
15
+ {head && (
16
+ <div className="sv-card__head">
17
+ {title !== undefined && <h3 className="sv-card__title">{title}</h3>}
18
+ {meta !== undefined && <span className="sv-card__meta">{meta}</span>}
19
+ </div>
20
+ )}
21
+ <div className="sv-card__body">{children}</div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ A 6px-radius badge in a tense tint: default is `--mint` with `--action` text
6
+ ("Coming soon" ships mint in the reference), `accent` is `--pink-wash` with
7
+ `--pink-deep`. A chip never carries a status alone — `Delta` does states.
8
+
9
+ ```tsx
10
+ <Chip>Coming soon</Chip>
11
+ ```
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ChipProps {
4
+ children: ReactNode;
5
+ selected?: boolean;
6
+ tone?: 'neutral' | 'accent';
7
+ className?: string;
8
+ }
9
+
10
+ export function Chip({ children, selected = false, tone = 'neutral', className }: ChipProps) {
11
+ return (
12
+ <span
13
+ className={[
14
+ 'sv-chip',
15
+ `sv-chip--${tone}`,
16
+ selected ? 'sv-chip--selected' : undefined,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ **The signature element.** The page's only dark object: an `--accent` slab at
6
+ `--r-panel` with two white contour origins bleeding off its edges, a white heading
7
+ (3.74:1 — legal at heading sizes only, which is why `support` renders at the
8
+ 24px question size and never body), and one white outline control. One per page;
9
+ a second closer has spent the device twice and broken it both times.
10
+
11
+ ```tsx
12
+ <Closer title="Need a custom plan?" support="Contact our team."
13
+ action={<Button variant="secondary">Book a demo</Button>} />
14
+ ```
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface CloserProps {
4
+ /** The heading — white on the brand teal is legal at heading sizes only. */
5
+ title: string;
6
+ /** One line of support copy, kept at heading scale. */
7
+ support?: string;
8
+ /** One white outline control. */
9
+ action: ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export function Closer({ title, support, action, className }: CloserProps) {
14
+ return (
15
+ <section className={['sv-closer', className].filter(Boolean).join(' ')}>
16
+ <h2 className="sv-closer__title">{title}</h2>
17
+ {support !== undefined && <p className="sv-closer__support">{support}</p>}
18
+ <span className="sv-closer__action">{action}</span>
19
+ </section>
20
+ );
21
+ }
@@ -0,0 +1,12 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ A reading's movement: number first, arrow second, hue third. Rising takes the
6
+ action family; falling takes `--pink-deep` — the *speaking* pink at 5.32:1, never
7
+ the glyph pink. The arrow ships inside the component because status is never
8
+ colour alone.
9
+
10
+ ```tsx
11
+ <Delta direction="down">1,3</Delta>
12
+ ```