@natjswenson/press 0.4.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to the **press** skill are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
6
6
  this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.1] - 2026-08-01
9
+
10
+ ### Fixed
11
+
12
+ - **Propagation targeted the default branch, not the integration branch.** Every
13
+ consumer runs `feature → dev → main`, so a PR opened straight into `main`
14
+ would violate its own flow. Worse, the default branch can legitimately lag:
15
+ the first real fan-out reported `budget` and `local-fitness` as *missing* only
16
+ because their migration was still sitting on `dev`. It now checks out `dev`
17
+ when the remote has one and falls back to the default branch otherwise.
18
+ - The clone was `--depth=1`, which cannot see other branches. Full clone now.
19
+
20
+ ## [0.5.0] - 2026-08-01
21
+
22
+ The last hand-maintained copy of the brand is gone.
23
+
24
+ ### Added
25
+
26
+ - **`python-consts` emitter** — flat module constants plus optional dicts, for a
27
+ script that reads tokens as Python names rather than a theme dict. The profile
28
+ README's SVG build has no override file to deep-merge and no stylesheet, so
29
+ `python-theme` would be dead weight around four strings.
30
+ - **`font_files`** — the vendorable faces, for engines that EMBED a font file
31
+ rather than resolving a CSS stack. Same type intent as `fonts.profiles`,
32
+ expressed where a stack is not an option: SF is not licensable, so Inter
33
+ stands in for the display voice and IBM Plex for serif and mono.
34
+
35
+ ### Changed
36
+
37
+ - **Migrated `natejswenson/natejswenson`** (the GitHub profile README). Its
38
+ header named `natejswenson.io/src/styles/global.css` as canonical without
39
+ anything keeping it honest — the ninth copy, and the last one. Every value is
40
+ byte-identical; the only content change is the comment header becoming a
41
+ region marker.
42
+ - That repo had **no CI at all**, so it gains a workflow running its existing
43
+ lint and 82 tests plus the brand check.
44
+
45
+ ### On verifying that migration
46
+
47
+ Its SVG tiles embed subsetted WOFF2, and **font subsetting is not
48
+ byte-reproducible** — rebuilding the untouched repo produced different base64
49
+ payloads. A byte comparison would have failed constantly while proving nothing.
50
+ Parity was verified by stripping the font payloads and comparing everything
51
+ else: 7 of 7 SVGs and the generated README identical.
52
+
8
53
  ## [0.4.1] - 2026-08-01
9
54
 
10
55
  ### Fixed
@@ -183,6 +228,8 @@ source of truth and a CI drift gate.
183
228
  rasterised cards, whose eyebrow legitimately runs at `.16em`. Scoped rather
184
229
  than waived, and caught by linting the real shipped corpus.
185
230
 
231
+ [0.5.1]: https://github.com/natejswenson/claude-skills/releases/tag/press-v0.5.1
232
+ [0.5.0]: https://github.com/natejswenson/claude-skills/releases/tag/press-v0.5.0
186
233
  [0.4.1]: https://github.com/natejswenson/claude-skills/releases/tag/press-v0.4.1
187
234
  [0.4.0]: https://github.com/natejswenson/claude-skills/releases/tag/press-v0.4.0
188
235
  [0.3.0]: https://github.com/natejswenson/claude-skills/releases/tag/press-v0.3.0
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: press
3
3
  description: The one brand system for everything produced in Claude — design tokens, the visual laws, the run-presentation contract, and the universal voice core. Use when composing or restyling any artifact (report, résumé, card, cover, PDF, HTML page, chart), when asked about brand colors, fonts, the accent law or "the PRESS look", when adding a new skill that renders anything, or when a brand value needs to change everywhere at once. Also handles "check the brand is in sync", "why do my colors differ", and onboarding a new consumer repo.
4
4
  user_invocable: true
5
- version: 0.4.1
5
+ version: 0.5.1
6
6
  ---
7
7
 
8
8
  # /press — the brand system
@@ -161,7 +161,7 @@ node bin/press.js propagate --repo ../budget --dry-run
161
161
  the region takes over, so the duplicate is **swallowed**, not left behind.
162
162
  5. `node bin/press.js emit --target <id> --init`
163
163
  6. Add a `press check` step to that repo's CI, **pinned to an exact version**:
164
- `npx -y @natjswenson/press@0.4.0 check --repo .`. Never write a bare
164
+ `npx -y @natjswenson/press@0.5.0 check --repo .`. Never write a bare
165
165
  `npx @natjswenson/press` — with no version reference at all, npx silently
166
166
  prefers a stale global install over the registry, which cost this repo a
167
167
  release once already. An exact pin also keeps a mutable dependency out of a
package/brand/tokens.json CHANGED
@@ -38,7 +38,11 @@
38
38
  "border_alpha": 0.16,
39
39
  "border_hover_alpha": 0.38,
40
40
  "accent_dim_alpha": 0.12,
41
- "fill_steps": ["ink", "ink_mid", "dim"]
41
+ "fill_steps": [
42
+ "ink",
43
+ "ink_mid",
44
+ "dim"
45
+ ]
42
46
  },
43
47
  "fonts": {
44
48
  "$comment": "The brand's type INTENT is one thing — SF-like sans for structure, New York-like serif for commentary, SF Mono-like mono for data. The fallback chain that actually achieves it depends on the rendering engine, so there are profiles rather than one list. Chromium resolves -apple-system to SF and never reaches the fallbacks; fontconfig (WeasyPrint) resolves none of the ui-*/-apple-system keywords and walks the chain for real, where a browser-tuned list lands on Helvetica Neue *Heavy Condensed* — measured with pdffonts, not assumed. Pick with params.font_profile; omitting it gives you `default_profile`. Single quotes so the same string is valid CSS and needs no escaping in a Python literal.",
@@ -58,6 +62,13 @@
58
62
  }
59
63
  }
60
64
  },
65
+ "font_files": {
66
+ "$comment": "For engines that EMBED a real font file rather than resolving a CSS stack — the profile README's SVG build subsets these into each tile. SF is not vendorable, so Inter stands in for the display voice and IBM Plex for serif/mono. Same type intent as fonts.profiles, expressed where a stack is not an option; a consumer must vendor matching files under its own assets/fonts.",
67
+ "display_black": "Inter-Black.ttf",
68
+ "display_bold": "Inter-Bold.ttf",
69
+ "serif_italic": "IBMPlexSerif-Italic.ttf",
70
+ "mono": "IBMPlexMono-Regular.ttf"
71
+ },
61
72
  "identity": {
62
73
  "stamp": "NS",
63
74
  "byline": "linkedin.com/in/natejswenson"
package/lib/emit.mjs CHANGED
@@ -264,8 +264,37 @@ function jsonTokens(tokens, params) {
264
264
 
265
265
  // --------------------------------------------------------------------------
266
266
 
267
+
268
+ /**
269
+ * Flat module constants plus optional dicts, for a script that reads tokens as
270
+ * Python names rather than a theme dict. The profile README's SVG build is the
271
+ * case: it has no override file to deep-merge and no stylesheet, so the full
272
+ * `python-theme` machinery would be dead weight around four strings.
273
+ */
274
+ function pythonConsts(tokens, params) {
275
+ const out = [];
276
+ for (const spec of params.consts ?? []) {
277
+ const name = required(spec, 'name', 'python-consts');
278
+ out.push(`${name} = ${py(lookup(tokens, required(spec, 'token', 'python-consts'), params))}`);
279
+ }
280
+ for (const spec of params.dicts ?? []) {
281
+ const group = tokens[required(spec, 'group', 'python-consts')];
282
+ if (!group) throw new EmitError(`python-consts: unknown token group "${spec.group}"`);
283
+ if (out.length) out.push('');
284
+ out.push(`${required(spec, 'name', 'python-consts')} = {`);
285
+ for (const [key, value] of Object.entries(group)) {
286
+ const k = spec.key_style === 'kebab' ? key.replace(/_/g, '-') : key;
287
+ out.push(` ${py(k)}: ${py(value)},`);
288
+ }
289
+ out.push('}');
290
+ }
291
+ if (out.length === 0) throw new EmitError('python-consts needs consts and/or dicts');
292
+ return out.join('\n');
293
+ }
294
+
267
295
  export const EMITTERS = {
268
296
  'python-theme': pythonTheme,
297
+ 'python-consts': pythonConsts,
269
298
  'css-vars': cssVars,
270
299
  'md-palette': mdPalette,
271
300
  'markdown-block': markdownBlock,
@@ -317,7 +346,7 @@ const DERIVED = {
317
346
 
318
347
  function lookup(tokens, name, params = {}) {
319
348
  if (name in DERIVED) return tokens.derived[DERIVED[name]];
320
- for (const group of [tokens.colors, tokens.terminal, fontsFor(tokens, params), tokens.identity, tokens.marks]) {
349
+ for (const group of [tokens.colors, tokens.terminal, fontsFor(tokens, params), tokens.fontFiles, tokens.identity, tokens.marks]) {
321
350
  if (name in group) return group[name];
322
351
  }
323
352
  throw new EmitError(`unknown token "${name}"`);
package/lib/tokens.mjs CHANGED
@@ -88,6 +88,7 @@ function resolve(raw) {
88
88
  ),
89
89
  defaultFontProfile: raw.fonts?.default_profile,
90
90
  identity: clean(raw.identity),
91
+ fontFiles: clean(raw.font_files),
91
92
  marks: clean(raw.marks),
92
93
  limits: clean(raw.limits),
93
94
  derived: { hair, hairAlpha, border, borderHover, accentDim, fillSteps },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@natjswenson/press",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "One brand system — tokens, laws, run presentation and voice core — generated into every consumer with a CI drift gate",
5
5
  "license": "MIT",
6
6
  "author": "Nate Swenson",
package/targets.json CHANGED
@@ -359,6 +359,45 @@
359
359
  "replaceFrom": "^:root \\{$",
360
360
  "replaceTo": "^\\}$"
361
361
  }
362
+ },
363
+ {
364
+ "id": "profile-readme",
365
+ "repo": "natejswenson",
366
+ "path": "build_svg.py",
367
+ "region": "tokens",
368
+ "syntax": "python",
369
+ "emitter": "python-consts",
370
+ "params": {
371
+ "consts": [
372
+ {
373
+ "name": "PAPER",
374
+ "token": "paper"
375
+ },
376
+ {
377
+ "name": "INK",
378
+ "token": "ink"
379
+ },
380
+ {
381
+ "name": "DIM",
382
+ "token": "dim"
383
+ },
384
+ {
385
+ "name": "ACCENT",
386
+ "token": "accent"
387
+ }
388
+ ],
389
+ "dicts": [
390
+ {
391
+ "name": "FACES",
392
+ "group": "fontFiles",
393
+ "key_style": "kebab"
394
+ }
395
+ ]
396
+ },
397
+ "init": {
398
+ "replaceFrom": "^# --- PRESS tokens",
399
+ "replaceTo": "^\\}$"
400
+ }
362
401
  }
363
402
  ]
364
403
  }