@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 +47 -0
- package/SKILL.md +2 -2
- package/brand/tokens.json +12 -1
- package/lib/emit.mjs +30 -1
- package/lib/tokens.mjs +1 -0
- package/package.json +1 -1
- package/targets.json +39 -0
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.
|
|
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.
|
|
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": [
|
|
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
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
|
}
|