nimiq-branding-cli 1.1.1 → 1.2.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/.audit/report.json +71 -0
- package/.audit/report.md +33 -0
- package/AUDIT.md +60 -0
- package/LINT.md +158 -0
- package/README.md +47 -4
- package/align/canonical.json +44 -0
- package/audit/learnings.json +51 -0
- package/bin/nq.js +88 -6
- package/hooks/ci-additions.md +53 -0
- package/hooks/pre-commit +10 -0
- package/hooks/session-start.sh +3 -0
- package/hooks/stack-align.yml +75 -0
- package/package.json +4 -4
- package/registry/SKILL-BLOCK.md +20 -0
- package/registry/components/account-header/meta.json +11 -1
- package/registry/components/account-list/meta.json +9 -1
- package/registry/components/account-ring/meta.json +9 -1
- package/registry/components/address-display/html/address-display.css +17 -13
- package/registry/components/address-display/html/demo.html +8 -10
- package/registry/components/address-display/meta.json +37 -7
- package/registry/components/address-display/vue/AddressDisplay.vue +11 -9
- package/registry/components/address-input/meta.json +53 -10
- package/registry/components/amount/meta.json +60 -11
- package/registry/components/amount-input/meta.json +55 -10
- package/registry/components/amount-with-fee/meta.json +58 -11
- package/registry/components/app-showcase-card/meta.json +17 -4
- package/registry/components/backup-banner/meta.json +29 -5
- package/registry/components/balance-distribution/meta.json +25 -5
- package/registry/components/buttons/meta.json +38 -7
- package/registry/components/card/meta.json +20 -4
- package/registry/components/close-button/meta.json +19 -4
- package/registry/components/consensus-icon/meta.json +26 -5
- package/registry/components/copyable/meta.json +25 -5
- package/registry/components/fiat-amount/meta.json +49 -9
- package/registry/components/flag-hex/html/demo.html +55 -0
- package/registry/components/flag-hex/html/flag-hex.css +21 -0
- package/registry/components/flag-hex/html/flag-hex.html +38 -0
- package/registry/components/flag-hex/meta.json +43 -0
- package/registry/components/flag-hex/vue/FlagHex.vue +98 -0
- package/registry/components/hero-section/meta.json +9 -4
- package/registry/components/honeycomb-band/meta.json +47 -9
- package/registry/components/identicon/meta.json +8 -1
- package/registry/components/label-input/meta.json +49 -9
- package/registry/components/loading-spinner/meta.json +19 -4
- package/registry/components/page-body/meta.json +23 -5
- package/registry/components/page-footer/meta.json +22 -5
- package/registry/components/page-header/meta.json +43 -8
- package/registry/components/payment-info-line/meta.json +10 -1
- package/registry/components/price-chart/meta.json +67 -11
- package/registry/components/qr-code/meta.json +22 -5
- package/registry/components/search-bar/meta.json +25 -5
- package/registry/components/select-bar/meta.json +37 -7
- package/registry/components/slider-toggle/meta.json +45 -8
- package/registry/components/small-page/meta.json +19 -4
- package/registry/components/status-alert/meta.json +37 -7
- package/registry/components/status-screen/meta.json +70 -13
- package/registry/components/swap-balance-bar/meta.json +8 -1
- package/registry/components/timer/meta.json +54 -10
- package/registry/components/toast-notification/meta.json +49 -9
- package/registry/components/tooltip/meta.json +69 -13
- package/registry/components/transaction-list/meta.json +9 -1
- package/registry/index.json +10 -0
- package/schemas/nimiq-stack.v1.json +103 -0
- package/scripts/_browser.mjs +27 -0
- package/scripts/align.mjs +435 -0
- package/scripts/apply-safe-bump.mjs +47 -0
- package/scripts/audit.mjs +247 -0
- package/scripts/bootstrap-provenance.mjs +126 -0
- package/scripts/hooks.mjs +152 -0
- package/scripts/lint.mjs +333 -0
- package/scripts/scaffold.mjs +391 -0
- package/scripts/sync-skill.mjs +84 -0
- package/scripts/verify.mjs +2 -2
- package/test/align.test.mjs +166 -0
- package/test/scaffold.test.mjs +103 -0
- package/upstream-pins.json +35 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedAtNote": "timestamp stamped by caller",
|
|
3
|
+
"verdict": "clean",
|
|
4
|
+
"repos": {
|
|
5
|
+
"hub": {
|
|
6
|
+
"branch": "master",
|
|
7
|
+
"pinned": "cf2ea419",
|
|
8
|
+
"live": "cf2ea419",
|
|
9
|
+
"pinnedFull": "cf2ea41941e433fa8e28ff4fe3d60870005c2115",
|
|
10
|
+
"liveFull": "cf2ea41941e433fa8e28ff4fe3d60870005c2115",
|
|
11
|
+
"via": "local",
|
|
12
|
+
"drifted": false
|
|
13
|
+
},
|
|
14
|
+
"wallet": {
|
|
15
|
+
"branch": "master",
|
|
16
|
+
"pinned": "656ed569",
|
|
17
|
+
"live": "656ed569",
|
|
18
|
+
"pinnedFull": "656ed569dac0e20ff61bba95a4fef14fce3541d6",
|
|
19
|
+
"liveFull": "656ed569dac0e20ff61bba95a4fef14fce3541d6",
|
|
20
|
+
"via": "local",
|
|
21
|
+
"drifted": false
|
|
22
|
+
},
|
|
23
|
+
"nimiq-ui": {
|
|
24
|
+
"branch": "main",
|
|
25
|
+
"pinned": "68e96cde",
|
|
26
|
+
"live": "68e96cde",
|
|
27
|
+
"pinnedFull": "68e96cdeb1104bd5cd1556f5cd2619b8509c4529",
|
|
28
|
+
"liveFull": "68e96cdeb1104bd5cd1556f5cd2619b8509c4529",
|
|
29
|
+
"via": "local",
|
|
30
|
+
"drifted": false
|
|
31
|
+
},
|
|
32
|
+
"nimiq-style": {
|
|
33
|
+
"branch": "master",
|
|
34
|
+
"pinned": "21c10a14",
|
|
35
|
+
"live": "21c10a14",
|
|
36
|
+
"pinnedFull": "21c10a14b324a133ddb78f906015765bf6cb13ec",
|
|
37
|
+
"liveFull": "21c10a14b324a133ddb78f906015765bf6cb13ec",
|
|
38
|
+
"via": "local",
|
|
39
|
+
"drifted": false
|
|
40
|
+
},
|
|
41
|
+
"vue-components": {
|
|
42
|
+
"branch": "master",
|
|
43
|
+
"pinned": "3c5b9724",
|
|
44
|
+
"live": "3c5b9724",
|
|
45
|
+
"pinnedFull": "3c5b97247e68a8bdcba5e904ff8ac29482995759",
|
|
46
|
+
"liveFull": "3c5b97247e68a8bdcba5e904ff8ac29482995759",
|
|
47
|
+
"via": "local",
|
|
48
|
+
"drifted": false
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"driftedComponents": [],
|
|
52
|
+
"tokenDrift": [],
|
|
53
|
+
"unknownPaths": [],
|
|
54
|
+
"verify": {
|
|
55
|
+
"ran": false
|
|
56
|
+
},
|
|
57
|
+
"newComponents": [
|
|
58
|
+
"vue-components:src/components/Account.vue",
|
|
59
|
+
"vue-components:src/components/AccountDetails.vue",
|
|
60
|
+
"vue-components:src/components/AccountSelector.vue",
|
|
61
|
+
"vue-components:src/components/BottomOverlay.vue",
|
|
62
|
+
"vue-components:src/components/Carousel.vue",
|
|
63
|
+
"vue-components:src/components/CircleSpinner.vue",
|
|
64
|
+
"vue-components:src/components/CopyableField.vue",
|
|
65
|
+
"vue-components:src/components/LanguageSelector.vue",
|
|
66
|
+
"vue-components:src/components/LongPressButton.vue",
|
|
67
|
+
"vue-components:src/components/QrScanner.vue",
|
|
68
|
+
"vue-components:src/components/Wallet.vue"
|
|
69
|
+
],
|
|
70
|
+
"summary": "verdict: clean · 0/5 upstream(s) drifted · 0 component(s) upstream-touched · 0 token-drift · 0 unknown path(s) · verify skipped"
|
|
71
|
+
}
|
package/.audit/report.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# nq audit report
|
|
2
|
+
|
|
3
|
+
**verdict: clean · 0/5 upstream(s) drifted · 0 component(s) upstream-touched · 0 token-drift · 0 unknown path(s) · verify skipped**
|
|
4
|
+
|
|
5
|
+
## Upstream pins vs live
|
|
6
|
+
|
|
7
|
+
| repo | branch | pinned | live | drifted | changed files |
|
|
8
|
+
|---|---|---|---|---|---|
|
|
9
|
+
| hub | master | `cf2ea419` | `cf2ea419` | no | — |
|
|
10
|
+
| wallet | master | `656ed569` | `656ed569` | no | — |
|
|
11
|
+
| nimiq-ui | main | `68e96cde` | `68e96cde` | no | — |
|
|
12
|
+
| nimiq-style | master | `21c10a14` | `21c10a14` | no | — |
|
|
13
|
+
| vue-components | master | `3c5b9724` | `3c5b9724` | no | — |
|
|
14
|
+
|
|
15
|
+
## New-component radar
|
|
16
|
+
|
|
17
|
+
Upstream components with no registry port yet (candidates — cross-ref FEATURES.md):
|
|
18
|
+
|
|
19
|
+
- `vue-components:src/components/Account.vue`
|
|
20
|
+
- `vue-components:src/components/AccountDetails.vue`
|
|
21
|
+
- `vue-components:src/components/AccountSelector.vue`
|
|
22
|
+
- `vue-components:src/components/BottomOverlay.vue`
|
|
23
|
+
- `vue-components:src/components/Carousel.vue`
|
|
24
|
+
- `vue-components:src/components/CircleSpinner.vue`
|
|
25
|
+
- `vue-components:src/components/CopyableField.vue`
|
|
26
|
+
- `vue-components:src/components/LanguageSelector.vue`
|
|
27
|
+
- `vue-components:src/components/LongPressButton.vue`
|
|
28
|
+
- `vue-components:src/components/QrScanner.vue`
|
|
29
|
+
- `vue-components:src/components/Wallet.vue`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
Verdicts: **clean** (nothing moved) · **safe** (only learnings-"ignore" churn → auto-PR pin bump) · **risky** (component/token/unknown/verify-fail → human review).
|
package/AUDIT.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Staying current with Nimiq — the self-learning branding audit
|
|
2
|
+
|
|
3
|
+
`nq verify` proves our **port matches the vendored upstream truth render**. But that render is a
|
|
4
|
+
frozen snapshot — if Nimiq redesigns a component or changes a token upstream, `verify` stays green
|
|
5
|
+
against a stale reference. `nq audit` closes that gap: it watches the **live** Nimiq source and tells
|
|
6
|
+
you when the real design has moved away from what the registry ships.
|
|
7
|
+
|
|
8
|
+
## The two axes of branding accuracy
|
|
9
|
+
|
|
10
|
+
| Axis | Question | Tool |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Port fidelity | Does our component still render like the upstream truth? | `nq verify` |
|
|
13
|
+
| Upstream currency | Has the live Nimiq source moved away from our pin? | `nq audit` |
|
|
14
|
+
|
|
15
|
+
## What `nq audit` does
|
|
16
|
+
|
|
17
|
+
1. **Upstream drift** — compares each `pinned` commit in [`upstream-pins.json`](upstream-pins.json)
|
|
18
|
+
to the live branch tip (`git ls-remote` locally or in CI). For drifted repos it gets the changed
|
|
19
|
+
file list (local clone `git diff`, or the GitHub compare API in CI).
|
|
20
|
+
2. **Provenance intersection** — every component's `meta.json` has a `source` block listing the exact
|
|
21
|
+
upstream files it was ported from. Changed files are intersected with these → drift is attributed to
|
|
22
|
+
the precise components it touches. **A changed file that any component lists is always escalated to
|
|
23
|
+
`risky` before the learnings rules run**, so broad benign-churn rules can never hide a real change.
|
|
24
|
+
3. **Token / framework drift** — flags changes to the design-token sources (`nimiq-style`, `@nimiq/css`).
|
|
25
|
+
4. **Port fidelity** — runs `nq verify all` and folds the result in.
|
|
26
|
+
5. **New-component radar** — lists upstream `@nimiq/vue-components` with no registry port yet.
|
|
27
|
+
|
|
28
|
+
Output: `.audit/report.json` (machine) + `.audit/report.md` (human).
|
|
29
|
+
|
|
30
|
+
## Verdicts
|
|
31
|
+
|
|
32
|
+
- **clean** — nothing moved, ports green. No action.
|
|
33
|
+
- **safe** — upstream moved, but every changed file is learnings-classified benign (deps, tests, i18n,
|
|
34
|
+
app logic, flow views), no component/token touched, and verify passed → the weekly workflow **auto-PRs
|
|
35
|
+
a pin bump**. Safe because the gate proved nothing visual changed.
|
|
36
|
+
- **risky** — a component's real source changed (needs hand re-port), a token changed, an **unknown** path
|
|
37
|
+
needs triage, or verify failed → the workflow opens/updates a **rolling GitHub issue** for a human.
|
|
38
|
+
|
|
39
|
+
## The self-learning loop (`audit/learnings.json`)
|
|
40
|
+
|
|
41
|
+
Each changed path is classified `ignore` / `token` / `branding` by glob rules. Unmatched paths are
|
|
42
|
+
`unknown` and surfaced for triage. **Resolving a triage = appending a rule** (with `seenCount++` and a
|
|
43
|
+
`learnedFrom` note). Next run, that churn auto-classifies — fewer false alarms, faster known fixes. The
|
|
44
|
+
store gets smarter every week. Seed rules came from the 2026-06-16 reconciliation of the hub/wallet
|
|
45
|
+
Bitcoin/Ledger/swap drift, which touched none of our ports.
|
|
46
|
+
|
|
47
|
+
## Weekly automation
|
|
48
|
+
|
|
49
|
+
[`.github/workflows/audit.yml`](.github/workflows/audit.yml) runs every Monday (and on demand via
|
|
50
|
+
`workflow_dispatch`): re-snaps references for a platform-consistent verify, runs `nq audit`, then
|
|
51
|
+
auto-PRs safe pin bumps or files the rolling issue. The pin record in `upstream-pins.json` is the
|
|
52
|
+
single committed source of truth for "what we are current with".
|
|
53
|
+
|
|
54
|
+
## Triage workflow (when you get a `risky` issue)
|
|
55
|
+
|
|
56
|
+
1. Read `.audit/report.md`. For each **upstream-touched component**, diff the upstream file and re-port
|
|
57
|
+
the truth render → `node scripts/snap.mjs <name>` → `node bin/nq.js verify <name>`.
|
|
58
|
+
2. For each **unknown path**, decide if it's benign or branding-relevant and add a rule to
|
|
59
|
+
`audit/learnings.json` (that's the learning).
|
|
60
|
+
3. Bump the pin in `upstream-pins.json`, run `nq sync-skill`, commit.
|
package/LINT.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# nq lint — brand + breathability enforcement
|
|
2
|
+
|
|
3
|
+
`nq audit` watches the *upstream* Nimiq design for drift. `nq lint` is the other direction:
|
|
4
|
+
it renders **your** page (desktop + a 390px mobile pass) and checks it against the Nimiq rules,
|
|
5
|
+
so the brand discipline that used to live only as prose (the 21 rules + AI-slop blacklist in the
|
|
6
|
+
`nimiq-ui` skill) becomes something a machine can actually *deny*, not just something a human is
|
|
7
|
+
asked to remember.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
nq lint <file.html | url> # render + check; exit 1 if any ERROR
|
|
11
|
+
nq lint page.html --fix # also auto-fix the safe text violations in place
|
|
12
|
+
nq lint https://site/ --json # machine-readable
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It renders with the same Playwright harness as `nq verify` (no new runtime dep). For URLs it
|
|
16
|
+
dismisses a language-picker splash (e.g. nimiq.tech) and scrolls to trigger lazy sections;
|
|
17
|
+
decorative SVG fields (honeycomb / identicon fences) are excluded from every measurement.
|
|
18
|
+
|
|
19
|
+
> **Lint source files, not live URLs, when you can.** A built/static `.html` file renders
|
|
20
|
+
> deterministically. A live SPA adds splash gates, lazy hydration and thousands of decorative
|
|
21
|
+
> nodes — handled, but fragile. URL mode is for spot-checks; the gate should point at source.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Two layers
|
|
26
|
+
|
|
27
|
+
### ERRORS — off-brand slop (hard-fail, exit 1)
|
|
28
|
+
Unambiguous, deterministic, and verified **not** to fire on nimiq.com itself (see calibration).
|
|
29
|
+
These are the "make us not do AI things" set.
|
|
30
|
+
|
|
31
|
+
| Check | Rule | Auto-fix |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| em/en dashes in copy | skill rule 18 | `--fix` → comma |
|
|
34
|
+
| periods on display titles / CTAs | skill rule 16 | `--fix` → strip |
|
|
35
|
+
| glassmorphism (translucent surface + backdrop blur) | skill rule 10 | manual |
|
|
36
|
+
| borders on inputs | skill rule 1 | manual (inset box-shadow) |
|
|
37
|
+
| off-palette colors | skill rule 13 | manual |
|
|
38
|
+
| low-contrast blue/navy text on dark | skill rule 20 | manual → `#0CA6FE` / white |
|
|
39
|
+
|
|
40
|
+
**Blue/navy on dark (rule 20)** is the headline a11y+brand check. A blue-family foreground (hue
|
|
41
|
+
195–245°) sitting on a dark surface (luminance < 0.12) below the AA floor — **4.5:1 normal, 3.0:1
|
|
42
|
+
large** — fails. Raw `#0582CA` on navy = 3.63:1 and `#265DD7` = 2.61:1 both error; the on-dark
|
|
43
|
+
variant `#0CA6FE` (5.68:1) and white pass cleanly. The background is read from the element's *own*
|
|
44
|
+
surface first (so blue text on a light card nested in a dark section is not a false positive), and
|
|
45
|
+
gradient-filled CTAs with white text are never flagged.
|
|
46
|
+
|
|
47
|
+
### WARNINGS — breathability / density (advisory, never block)
|
|
48
|
+
These **cannot** hard-fail: nimiq.com's own marketing trips several of them (it runs 12 font
|
|
49
|
+
sizes and 31–38% dense sections). They're calibrated to the measured Nimiq envelope and exist
|
|
50
|
+
to flag *"this is getting busy, is it justified?"* — the deterministic half of PRINCIPLES law 4
|
|
51
|
+
("white space does the structural work").
|
|
52
|
+
|
|
53
|
+
**Breathability / density**
|
|
54
|
+
|
|
55
|
+
| Check | Threshold | Why |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| body text wider than ~88ch | `> 88ch` | prose measure caps at 78ch; nimiq.com p90 ≈ 84ch |
|
|
58
|
+
| dense sections (text-ink ratio) | `> 18%` of a full-width band | nimiq.com pages run 5–12% page ink |
|
|
59
|
+
| off-scale spacing | values not on the curated scale | warn-and-snap, not fail |
|
|
60
|
+
| type-scale sprawl | `> 12` distinct text sizes | calm app ≈ 4; busy marketing ≈ 12 |
|
|
61
|
+
| colored / long / pill uppercase eyebrow | colored, >24 chars, or a pill | grey section labels are fine (rules 8, 17) |
|
|
62
|
+
|
|
63
|
+
**Depth / motion / form** (from the design recon)
|
|
64
|
+
|
|
65
|
+
| Check | Threshold |
|
|
66
|
+
|---|---|
|
|
67
|
+
| non-pill action buttons | a text button radius below a full pill (and not a nav trigger) |
|
|
68
|
+
| flat-fill colored button | a brand-colored button with no radial gradient |
|
|
69
|
+
| wrong gradient anchor | a linear-gradient, or a radial not at bottom-right / `100% 100%` |
|
|
70
|
+
| non-Nimiq easing | the Material `cubic-bezier(0.4,0,0.2,1)` on a button (use `cubic-bezier(0.25,0,0,1)`) |
|
|
71
|
+
| off-scale border-radius | not on `3 · 4 · 6 · 8 · 10 · 12` or a full pill / 50% |
|
|
72
|
+
| harsh near-black shadow | `rgba(0,0,0, > 0.22)` (Nimiq shadows are soft, low-alpha) |
|
|
73
|
+
| underlined links | a body `<a>` with `text-decoration: underline` (links are bold, no underline) |
|
|
74
|
+
| NIM address not in Fira Mono | a 4-char-grouped address rendered in a proportional font |
|
|
75
|
+
| gold-tinted UI icon | a non-logo icon tinted gold (gold = brand mark only) |
|
|
76
|
+
|
|
77
|
+
**Mobile (a second pass at 390px)**
|
|
78
|
+
|
|
79
|
+
| Check | Threshold |
|
|
80
|
+
|---|---|
|
|
81
|
+
| horizontal overflow | `scrollWidth > viewport` by > 4px |
|
|
82
|
+
| tap targets too small | a button / input / link-button under 30px (inline text links are exempt) |
|
|
83
|
+
| text smaller than 12px | any text element below 12px at mobile width |
|
|
84
|
+
|
|
85
|
+
The curated spacing scale (from `assets/css/modern/spacing.css`, desktop max of each step):
|
|
86
|
+
`8 · 12 · 16 · 24 · 32 · 40 · 48 · 72 · 80 · 96 · 144 · 200`. Sub-8px is treated as optical
|
|
87
|
+
nudging, not layout, and ignored. The radius scale: `3 · 4 · 6 · 8 · 10 · 12` plus full pills
|
|
88
|
+
(500/999/9999) and circles (50%).
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Exceptions are part of the spec
|
|
93
|
+
|
|
94
|
+
Calibration against real pages proved that a naive version of each rule flags Nimiq's own
|
|
95
|
+
sites. The carve-outs are load-bearing, not afterthoughts:
|
|
96
|
+
|
|
97
|
+
- **Social brand icons** — Discord/Telegram/X/YouTube/etc. colors are exempt from the palette
|
|
98
|
+
rule (you can't recolor a third-party logo). Reported separately as `exempt social-icon colors`.
|
|
99
|
+
- **Blue-tinted neutrals** — Nimiq grays carry a deliberate violet spin, so "neutral" is tested
|
|
100
|
+
by *saturation relative to lightness*, not a flat channel spread.
|
|
101
|
+
- **Secondary palette** — purple `#5F4B8B`, pink `#FA7268`, light-green `#88B04B`, brown
|
|
102
|
+
`#795548` (+ gradient starts) are brand colors, not violations.
|
|
103
|
+
- **Abbreviations** — `p.a.`, `e.g.`, `U.S.` don't count as title periods (internal-dot guard).
|
|
104
|
+
- **Translucent-only glass** — a near-opaque panel (≥85% bg) with a faint backdrop blur is a
|
|
105
|
+
solid surface, not glassmorphism. Only `10–85%` translucency + backdrop blur is flagged.
|
|
106
|
+
- **Grey section eyebrows** — short grey uppercase labels ("THE APPS", "TRUSTED BY") are the
|
|
107
|
+
brand's own pattern; only *colored*, *>24-char*, or *pill* uppercase is flagged.
|
|
108
|
+
- **Addresses & codes** — `NQ…` addresses and short alphanumeric codes (`L51C`) are uppercase by
|
|
109
|
+
nature and excluded from the eyebrow check.
|
|
110
|
+
- **Own-surface contrast** — blue/navy text is judged against the element's *own* background
|
|
111
|
+
first, so blue text on a light card inside a dark section isn't a false error; gradient-filled
|
|
112
|
+
CTAs (white text) are never flagged.
|
|
113
|
+
- **Inline links & nav triggers** — inline text links are exempt from the mobile tap-target check;
|
|
114
|
+
`nav`/`header` text buttons are exempt from the non-pill check.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Calibration (measured, not invented)
|
|
119
|
+
|
|
120
|
+
Thresholds were set by rendering real Nimiq pages at 1440px and measuring the envelope — the
|
|
121
|
+
same "measure reality" method that proved the no-em-dash rule. Verified outcome:
|
|
122
|
+
|
|
123
|
+
| Page | ERRORS | Note |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| nimiq.com home (reference) | **0** | the gate must never flag the brand's own site, even with the rule-20 contrast + depth/motion checks |
|
|
126
|
+
| nimiq.com/about | 0 | — |
|
|
127
|
+
| nimiq.tech | real findings | catches genuine em-dashes + a "Phase 2 · preview" colored eyebrow + 3 dense bands |
|
|
128
|
+
| positive control | **2** | a deliberate `#0582CA`/`#265DD7`-on-navy page errors at 3.63/2.61:1; `#0CA6FE` + white pass |
|
|
129
|
+
|
|
130
|
+
Re-run the calibration any time the rules change:
|
|
131
|
+
`node bin/nq.js lint https://www.nimiq.com/` **must stay at 0 errors.** If a new rule flags the
|
|
132
|
+
reference, the rule is wrong — fix the rule, not nimiq.com. (That principle just caught four
|
|
133
|
+
over-strict rules during the 2026-06-20 hardening, including a blue-on-navy false error that the
|
|
134
|
+
own-surface-bg fix resolved.)
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Exit codes & `--fix`
|
|
139
|
+
|
|
140
|
+
- Exit `1` if any ERROR; `0` otherwise (warnings never affect exit). Wire the exit code into a
|
|
141
|
+
pre-commit hook or CI step to make it a real gate.
|
|
142
|
+
- `--fix` (local files only) applies the safe **text** transforms — em/en dashes → comma,
|
|
143
|
+
strip trailing title periods — and reports what it changed. It never silently green-washes:
|
|
144
|
+
geometry fixes (measure width, spacing snap, glass → solid) are reported for a human/agent,
|
|
145
|
+
because rewriting layout unsupervised can break the page.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Roadmap
|
|
150
|
+
|
|
151
|
+
- **Render-mapped geometry autofix** — map a flagged rendered element back to its source line so
|
|
152
|
+
`--fix` can also constrain over-wide text and snap off-scale spacing, each behind a
|
|
153
|
+
re-render-and-verify check so a fix that breaks layout is rejected instead of shipped.
|
|
154
|
+
- **Layer 3 — optional aesthetic judgment (BYO-key).** A cheap vision pass for the irreducible
|
|
155
|
+
residue the metrics can't see ("is this busyness *justified*? does it feel Nimiq or generic
|
|
156
|
+
SaaS?"). Off by default; the deterministic layers above stay free, offline, and keyless.
|
|
157
|
+
- **`learnings.json` for lint** — same self-learning store as `nq audit`, so confirmed false
|
|
158
|
+
positives are remembered and suppressed across runs.
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# nimiq-branding-cli
|
|
2
2
|
|
|
3
3
|
Scaffold **pixel-accurate Nimiq-branded UI components** into any project — Vue 3 SFCs or plain
|
|
4
|
-
HTML/CSS — from a registry of
|
|
5
|
-
|
|
4
|
+
HTML/CSS — from a registry of 40 components (39 pixel-diffed against the real Nimiq apps before
|
|
5
|
+
they ship, plus 1 original brand composition), plus the team's real asset library (logos, icons,
|
|
6
|
+
flags, imagery). A weekly self-learning audit keeps it current with live Nimiq design — see
|
|
7
|
+
[AUDIT.md](AUDIT.md).
|
|
6
8
|
|
|
7
9
|
> Unofficial community project — see [NOTICE.md](NOTICE.md). All visuals are the Nimiq team's
|
|
8
10
|
> real shipped files or faithful ports of their open-source components, never hand-drawn
|
|
@@ -29,18 +31,52 @@ ln -s "$PWD/bin/nq.js" ~/.local/bin/nq # or: npm link
|
|
|
29
31
|
## Use
|
|
30
32
|
|
|
31
33
|
```
|
|
32
|
-
nq list # browse the
|
|
34
|
+
nq list # browse the 40-component registry
|
|
33
35
|
nq init --style modern # drop Nimiq design tokens into your project
|
|
34
36
|
nq add amount-input # copy a component (+ deps + CSS + real assets) into src/components
|
|
35
37
|
nq add account-header --html # plain HTML/CSS variant instead of Vue
|
|
36
38
|
nq assets search wallet # search 182 vendored files + 323 nimiq-icons + 422 hexagon flags
|
|
37
39
|
nq assets add icon:logos-nimiq-horizontal flag:cr-hexagon world-map
|
|
38
40
|
nq verify all # (repo dev) re-run pixel verification against references
|
|
41
|
+
nq audit # (repo dev) check the LIVE Nimiq upstreams for branding drift
|
|
42
|
+
nq sync-skill # (repo dev) regenerate the nimiq-ui skill block from index.json
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
Open `showcase.html` for the full component gallery and `supporting-elements.html` for the
|
|
42
46
|
wallet + marketing element demos.
|
|
43
47
|
|
|
48
|
+
## Fleet stack alignment (`nq align` / `nq new-app` / `nq hooks`)
|
|
49
|
+
|
|
50
|
+
Branding accuracy (`nq audit`/`nq verify`) keeps the UI matching Nimiq's design. `nq align`
|
|
51
|
+
keeps a whole **app** on the canonical Nimiq fleet stack — same verdict vocabulary
|
|
52
|
+
(`clean` / `safe-drift` / `risky-fail`).
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
nq new-app my-app # scaffold a CANONICAL app: Bun+Hono+bun:sqlite+vanilla PWA+
|
|
56
|
+
# @nimiq/style + inline rpc-block-scan settlement + Fly deploy
|
|
57
|
+
# kit + ci.yml + a stamped nimiq-stack.json + /health. Aligns clean.
|
|
58
|
+
nq new-app readonly --no-chain # informational app (chainApp:false → skip settlement/styling parity)
|
|
59
|
+
nq new-app pay --settlement rpc --deploy fly
|
|
60
|
+
# (nq new <name> still scaffolds a UI registry component, unchanged)
|
|
61
|
+
|
|
62
|
+
nq align # grade the app in cwd against the canonical fleet baseline
|
|
63
|
+
nq align --all ~/Projects # grade every app dir under a folder
|
|
64
|
+
nq align --fix # safe autofixes only (write/repair nimiq-stack.json)
|
|
65
|
+
nq align --fail-on=settlement,styling # nonzero exit for the pre-commit / CI gate
|
|
66
|
+
|
|
67
|
+
nq hooks install # git pre-commit gate + SessionStart banner + weekly GH Action
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**The load-bearing axis is SETTLEMENT.** The `@nimiq/core` light client never reaches
|
|
71
|
+
consensus on our hosts, so any `@nimiq/core/web` import or `Client.create(` /
|
|
72
|
+
`waitForConsensusEstablished(` in `src/` is a **HARD FAIL**. Chain reads must use the
|
|
73
|
+
rpc-block-scan path (the `nimiq-settlement` package). `@nimiq/core` is offline-crypto-only.
|
|
74
|
+
|
|
75
|
+
Each app declares a root `nimiq-stack.json` (schema: `schemas/nimiq-stack.v1.json`); the
|
|
76
|
+
canonical fleet baseline `nq align` grades against lives in `align/canonical.json`. Apps
|
|
77
|
+
that are intentionally off-stack (e.g. nimiq.tech, nimiq-ads, gateflo) set `"exempt": true`
|
|
78
|
+
and are reported but never failed.
|
|
79
|
+
|
|
44
80
|
## How pixel accuracy is enforced
|
|
45
81
|
|
|
46
82
|
Every registry component carries:
|
|
@@ -65,7 +101,10 @@ disabled) and diffs it against `reference.png` with pixelmatch. Components whose
|
|
|
65
101
|
| `onmax/nimiq-ui` | modern `nimiq-css` (oklch tokens, auto dark mode) |
|
|
66
102
|
| `references/screenshots/` | captured reference screenshots of live Nimiq properties |
|
|
67
103
|
|
|
68
|
-
Upstream clones live in `upstream/` (gitignored — re-clone with `git clone --depth 1`).
|
|
104
|
+
Upstream clones live in `upstream/` (gitignored — re-clone with `git clone --depth 1`). The exact
|
|
105
|
+
commits the registry was verified against are recorded in [`upstream-pins.json`](upstream-pins.json) —
|
|
106
|
+
the committed source of truth for "what we are current with". `nq audit` watches the live tips against
|
|
107
|
+
these pins; see [AUDIT.md](AUDIT.md).
|
|
69
108
|
|
|
70
109
|
## Repo layout
|
|
71
110
|
|
|
@@ -79,5 +118,9 @@ assets/
|
|
|
79
118
|
css/legacy/ vendored @nimiq/style (nq-* classes)
|
|
80
119
|
tokens.md design-token quick reference
|
|
81
120
|
scripts/verify.mjs pixel-diff harness (playwright + pixelmatch)
|
|
121
|
+
scripts/audit.mjs live-upstream branding-drift engine (nq audit)
|
|
122
|
+
scripts/sync-skill.mjs regenerates the nimiq-ui skill block from index.json
|
|
123
|
+
upstream-pins.json the upstream commits the registry is verified against
|
|
124
|
+
audit/learnings.json self-learning store: which upstream churn is benign vs branding
|
|
82
125
|
references/screenshots side-by-side reference captures of live Nimiq UIs
|
|
83
126
|
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "The CANONICAL Nimiq fleet baseline that `nq align` grades every app's nimiq-stack.json against. Derived from the proven shipping stack (SnapPOS RPC-settlement, nimiq-split reference manifest, TipJar/Beelink deploy kit). This file is the single committed source of truth for 'what a canonical chain app looks like'. Drift off these values is graded with the same vocabulary as nq audit: clean (matches), safe-drift (different but defensible — warn), risky-fail (load-bearing axis violated — fail).",
|
|
3
|
+
"canonicalVersion": "0.1.0",
|
|
4
|
+
"schemaVersion": 1,
|
|
5
|
+
"stack": {
|
|
6
|
+
"framework": "vanilla-pwa",
|
|
7
|
+
"server": "hono",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"build": "none",
|
|
10
|
+
"packageManager": "bun"
|
|
11
|
+
},
|
|
12
|
+
"styling": {
|
|
13
|
+
"source": "nimiq-ui"
|
|
14
|
+
},
|
|
15
|
+
"settlement": {
|
|
16
|
+
"pattern": "rpc-block-scan",
|
|
17
|
+
"lib": "nimiq-settlement",
|
|
18
|
+
"coreRole": "offline-crypto-only",
|
|
19
|
+
"acceptedPatterns": ["rpc-block-scan", "mock", "noop"],
|
|
20
|
+
"acceptedLibs": ["nimiq-settlement", "inline"],
|
|
21
|
+
"forbiddenPatterns": ["light-client"]
|
|
22
|
+
},
|
|
23
|
+
"deploy": {
|
|
24
|
+
"target": "fly",
|
|
25
|
+
"region": "ord",
|
|
26
|
+
"storage": "fly-volume",
|
|
27
|
+
"edge": {
|
|
28
|
+
"provider": "cloudflare",
|
|
29
|
+
"proxied": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"config": {
|
|
33
|
+
"tsconfig": "local-strict",
|
|
34
|
+
"lint": "none",
|
|
35
|
+
"fileSizeGuard": 800,
|
|
36
|
+
"ci": true
|
|
37
|
+
},
|
|
38
|
+
"settlementPackage": "nimiq-settlement",
|
|
39
|
+
"lightClient": {
|
|
40
|
+
"forbiddenImports": ["@nimiq/core/web"],
|
|
41
|
+
"forbiddenCalls": ["Client.create(", "waitForConsensusEstablished("],
|
|
42
|
+
"reason": "The @nimiq/core light client never reaches consensus on our hosts (WASM addEventListener bug, hangs in Bun/Node through 2.7.0). Chain reads MUST use the rpc-block-scan path via the nimiq-settlement package; @nimiq/core is offline-crypto-only (key/tx signing)."
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Self-learning store for nq audit. Each rule classifies a changed upstream path (\"<repo>:<path>\", glob: ** = any incl. /, * = any excl. /) as `ignore` (benign churn — never affects our visual ports), `token` (design-framework/token change — review nq init + tokens), or `branding` (visual source change). SAFETY: a changed file that a component lists in meta.json `source.files` is ALWAYS escalated to risky BEFORE these rules run, so broad `ignore` rules cannot mask a real component change. Unmatched paths are `unknown` → surfaced for triage; resolving one means appending a rule here with seenCount++ (that is the learning).",
|
|
3
|
+
"rules": [
|
|
4
|
+
{ "pattern": "*:**/*.spec.ts", "verdict": "ignore", "reason": "unit test", "seenCount": 0 },
|
|
5
|
+
{ "pattern": "*:**/*.test.ts", "verdict": "ignore", "reason": "unit test", "seenCount": 0 },
|
|
6
|
+
{ "pattern": "*:**/test/**", "verdict": "ignore", "reason": "test dir", "seenCount": 0 },
|
|
7
|
+
{ "pattern": "*:**/tests/**", "verdict": "ignore", "reason": "test dir", "seenCount": 0 },
|
|
8
|
+
{ "pattern": "*:**/__tests__/**", "verdict": "ignore", "reason": "test dir", "seenCount": 0 },
|
|
9
|
+
{ "pattern": "*:**/__mocks__/**", "verdict": "ignore", "reason": "test mocks", "seenCount": 0 },
|
|
10
|
+
{ "pattern": "*:**/*.stories.*", "verdict": "ignore", "reason": "storybook story", "seenCount": 0 },
|
|
11
|
+
{ "pattern": "*:package.json", "verdict": "ignore", "reason": "dependency manifest", "seenCount": 0 },
|
|
12
|
+
{ "pattern": "*:package-lock.json", "verdict": "ignore", "reason": "lockfile", "seenCount": 0 },
|
|
13
|
+
{ "pattern": "*:yarn.lock", "verdict": "ignore", "reason": "lockfile", "seenCount": 0 },
|
|
14
|
+
{ "pattern": "*:pnpm-lock.yaml", "verdict": "ignore", "reason": "lockfile", "seenCount": 0 },
|
|
15
|
+
{ "pattern": "*:.github/**", "verdict": "ignore", "reason": "CI config", "seenCount": 0 },
|
|
16
|
+
{ "pattern": "*:.husky/**", "verdict": "ignore", "reason": "git hooks", "seenCount": 0 },
|
|
17
|
+
{ "pattern": "*:.vscode/**", "verdict": "ignore", "reason": "editor config", "seenCount": 0 },
|
|
18
|
+
{ "pattern": "*:**/*.config.*", "verdict": "ignore", "reason": "build/tool config", "seenCount": 0 },
|
|
19
|
+
{ "pattern": "*:tsconfig*", "verdict": "ignore", "reason": "ts config", "seenCount": 0 },
|
|
20
|
+
{ "pattern": "*:README*", "verdict": "ignore", "reason": "docs", "seenCount": 0 },
|
|
21
|
+
{ "pattern": "*:CHANGELOG*", "verdict": "ignore", "reason": "docs", "seenCount": 0 },
|
|
22
|
+
{ "pattern": "*:**/*.md", "verdict": "ignore", "reason": "docs", "seenCount": 0 },
|
|
23
|
+
{ "pattern": "*:**/i18n/**", "verdict": "ignore", "reason": "translations", "seenCount": 0 },
|
|
24
|
+
{ "pattern": "*:**/*.po", "verdict": "ignore", "reason": "translations", "seenCount": 0 },
|
|
25
|
+
{ "pattern": "*:src/i18n/**", "verdict": "ignore", "reason": "translations", "seenCount": 0 },
|
|
26
|
+
{ "pattern": "*:src/lang/**", "verdict": "ignore", "reason": "translations", "seenCount": 0 },
|
|
27
|
+
{ "pattern": "*:**/*Sentry*", "verdict": "ignore", "reason": "telemetry, not branding", "seenCount": 0 },
|
|
28
|
+
{ "pattern": "*:**/*sentry*", "verdict": "ignore", "reason": "telemetry, not branding", "seenCount": 0 },
|
|
29
|
+
{ "pattern": "*:src/stores/**", "verdict": "ignore", "reason": "app state — visual ports live in the .vue/.css files in provenance", "seenCount": 0 },
|
|
30
|
+
{ "pattern": "*:src/lib/**", "verdict": "ignore", "reason": "app logic — not visual markup/style", "seenCount": 0 },
|
|
31
|
+
{ "pattern": "*:src/electron/**", "verdict": "ignore", "reason": "desktop shell", "seenCount": 0 },
|
|
32
|
+
{ "pattern": "*:src/composables/**", "verdict": "ignore", "reason": "app logic", "seenCount": 0 },
|
|
33
|
+
|
|
34
|
+
{ "pattern": "*:src/views/**", "verdict": "ignore", "reason": "app-flow pages (compose shared components) — we port shared components from src/components, not full views", "seenCount": 1, "learnedAt": "2026-06-16", "learnedFrom": "hub drift 9e9b653→cf2ea419 (Bitcoin/Ledger swap success views)" },
|
|
35
|
+
{ "pattern": "*:src/config/**", "verdict": "ignore", "reason": "network/app config", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
36
|
+
{ "pattern": "*:src/*.ts", "verdict": "ignore", "reason": "top-level entrypoint/logic (main/export/iframe/cashlink), not visual markup/style", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
37
|
+
{ "pattern": "*:types/**", "verdict": "ignore", "reason": "type declarations", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
38
|
+
{ "pattern": "*:patches/**", "verdict": "ignore", "reason": "dependency patches", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
39
|
+
{ "pattern": "*:public/**", "verdict": "ignore", "reason": "static/build assets (e.g. bundled bitcoin libs)", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
40
|
+
{ "pattern": "*:demos/**", "verdict": "ignore", "reason": "demo pages", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
41
|
+
{ "pattern": "hub:client/**", "verdict": "ignore", "reason": "hub client subpackage build", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
42
|
+
{ "pattern": "*:bitcoinjs-parts.js", "verdict": "ignore", "reason": "vendored BTC lib build artifact", "seenCount": 1, "learnedAt": "2026-06-16" },
|
|
43
|
+
|
|
44
|
+
{ "pattern": "nimiq-style:src/**", "verdict": "token", "reason": "design framework source — colors/typography/buttons/layout; review nq tokens + nq init", "seenCount": 0 },
|
|
45
|
+
{ "pattern": "nimiq-style:nimiq-style.min.css", "verdict": "token", "reason": "compiled framework css that nq init/add ship", "seenCount": 0 },
|
|
46
|
+
{ "pattern": "nimiq-ui:packages/nimiq-css/**", "verdict": "token", "reason": "modern @nimiq/css token source", "seenCount": 0 },
|
|
47
|
+
{ "pattern": "*:**/themes.scss", "verdict": "token", "reason": "theme variables feeding component ports", "seenCount": 0 },
|
|
48
|
+
{ "pattern": "*:**/variables.scss", "verdict": "token", "reason": "scss variables feeding component ports", "seenCount": 0 },
|
|
49
|
+
{ "pattern": "*:src/scss/**", "verdict": "token", "reason": "shared scss (themes/variables/mixins) feeding component ports", "seenCount": 0 }
|
|
50
|
+
]
|
|
51
|
+
}
|
package/bin/nq.js
CHANGED
|
@@ -25,9 +25,31 @@ Usage:
|
|
|
25
25
|
nq assets add <name...> Copy official asset(s) into ./nimiq/assets/
|
|
26
26
|
(icon:<name> extracts from nimiq-icons, flag:<cc> from nimiq-flags)
|
|
27
27
|
nq principles Print the Nimiq design principles — the soul of this tool
|
|
28
|
-
nq new <name> Scaffold a new
|
|
29
|
-
|
|
28
|
+
nq new <name> Scaffold a new REGISTRY component (principles checklist +
|
|
29
|
+
verification contract embedded)
|
|
30
|
+
nq new-app <name> Scaffold a CANONICAL Nimiq fleet app (Bun+Hono+bun:sqlite+
|
|
31
|
+
vanilla PWA + @nimiq/style + nimiq-settlement + Fly kit +
|
|
32
|
+
a stamped nimiq-stack.json + /health). Starts clean on align.
|
|
33
|
+
--no-chain chainApp:false (skip settlement + styling parity)
|
|
34
|
+
--settlement mock|rpc|noop settlement client (default mock)
|
|
35
|
+
--deploy fly|none deploy kit (default fly)
|
|
36
|
+
nq align [path] Grade an app's stack vs the canonical fleet baseline.
|
|
37
|
+
--all <dir> Grade every app dir under <dir>
|
|
38
|
+
--fix Safe autofixes only (write/repair nimiq-stack.json)
|
|
39
|
+
--fail-on settlement,styling Nonzero exit if a listed axis is risky-fail (the gate)
|
|
40
|
+
--quiet One-line drift banner (SessionStart) --json machine output
|
|
41
|
+
SETTLEMENT is load-bearing: any @nimiq/core/web import or
|
|
42
|
+
Client.create( / waitForConsensusEstablished( in src HARD FAILS.
|
|
43
|
+
nq hooks install [repo] Install the drift hooks: git pre-commit (align --fail-on),
|
|
44
|
+
SessionStart banner, weekly GH Action (--write drops the workflow)
|
|
30
45
|
nq verify <component|all> Render the html variant and diff against the reference PNG
|
|
46
|
+
nq lint <file.html|url> Render a page and enforce the brand rules + breathability.
|
|
47
|
+
--fix Auto-fix the safe text violations in a local file (dashes, title periods)
|
|
48
|
+
--json Machine-readable output
|
|
49
|
+
ERRORS (off-brand slop) fail; WARNINGS (density) advise. See LINT.md
|
|
50
|
+
nq audit [--skip-verify] Check the LIVE Nimiq upstreams for branding drift vs our
|
|
51
|
+
pinned registry; attribute drift to components; write a report
|
|
52
|
+
nq sync-skill Regenerate the nimiq-ui skill's registry block from index.json
|
|
31
53
|
nq help This message
|
|
32
54
|
|
|
33
55
|
All visual assets are the team's real shipped files (nimiq.com, wallet, hub,
|
|
@@ -52,6 +74,19 @@ function parseFlags(args) {
|
|
|
52
74
|
if (a === '--vue' || a === '--html') flags.variant = a.slice(2);
|
|
53
75
|
else if (a === '--out') flags.out = args[++i];
|
|
54
76
|
else if (a === '--style') flags.style = args[++i];
|
|
77
|
+
else if (a === '--fix') flags.fix = true;
|
|
78
|
+
else if (a === '--json') flags.json = true;
|
|
79
|
+
else if (a === '--quiet') flags.quiet = true;
|
|
80
|
+
else if (a === '--all') flags.all = (args[i + 1] && !args[i + 1].startsWith('--')) ? args[++i] : true;
|
|
81
|
+
else if (a === '--no-chain') flags.noChain = true;
|
|
82
|
+
else if (a.startsWith('--settlement=')) flags.settlement = a.slice('--settlement='.length);
|
|
83
|
+
else if (a === '--settlement') flags.settlement = args[++i];
|
|
84
|
+
else if (a.startsWith('--deploy=')) flags.deploy = a.slice('--deploy='.length);
|
|
85
|
+
else if (a === '--deploy') flags.deploy = args[++i];
|
|
86
|
+
else if (a.startsWith('--fail-on=')) flags.failOn = a.slice('--fail-on='.length);
|
|
87
|
+
else if (a === '--fail-on') flags.failOn = args[++i];
|
|
88
|
+
else if (a === '--check') flags.check = true;
|
|
89
|
+
else if (a === '--write') flags.write = true;
|
|
55
90
|
else rest.push(a);
|
|
56
91
|
}
|
|
57
92
|
return { flags, rest };
|
|
@@ -259,10 +294,10 @@ const CHECKLIST = [
|
|
|
259
294
|
'reproducible: plain HTML/CSS or standard Vue, passes nq verify',
|
|
260
295
|
];
|
|
261
296
|
|
|
262
|
-
async function
|
|
263
|
-
if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) throw new Error('nq new <kebab-case-name>');
|
|
297
|
+
async function cmdNewComponent(name, flags) {
|
|
298
|
+
if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) throw new Error('nq new-component <kebab-case-name>');
|
|
264
299
|
if (ROOT.includes('node_modules') || ROOT.includes('_npx')) {
|
|
265
|
-
throw new Error('nq new scaffolds a component INTO the registry repo — clone it first:\n git clone https://github.com/Andjroo111/nimiq-branding-cli\nthen run nq new from that checkout.');
|
|
300
|
+
throw new Error('nq new-component scaffolds a component INTO the registry repo — clone it first:\n git clone https://github.com/Andjroo111/nimiq-branding-cli\nthen run nq new-component from that checkout.');
|
|
266
301
|
}
|
|
267
302
|
const dir = join(ROOT, 'registry', 'components', name);
|
|
268
303
|
if (existsSync(dir)) throw new Error(`component "${name}" already exists`);
|
|
@@ -327,6 +362,37 @@ async function cmdNew(name, flags) {
|
|
|
327
362
|
Read the soul of the tool first: nq principles`);
|
|
328
363
|
}
|
|
329
364
|
|
|
365
|
+
async function cmdNew(name, flags) {
|
|
366
|
+
const { scaffoldApp } = await import(join(ROOT, 'scripts', 'scaffold.mjs'));
|
|
367
|
+
const r = await scaffoldApp(name, { noChain: flags.noChain, settlement: flags.settlement, deploy: flags.deploy });
|
|
368
|
+
console.log(`+ scaffolded canonical Nimiq app → ${r.dir}`);
|
|
369
|
+
console.log(` ${r.files.length} files · chainApp=${r.chain}${r.chain ? ` · settlement=${r.settlement}` : ''} · deploy=${r.deploy}`);
|
|
370
|
+
console.log(`\nNext:\n cd ${name}\n bun install\n bun run dev # http://localhost:3000 (try GET /health)\n nq align # should be clean on every axis\n nq hooks install # add the pre-commit settlement/styling gate`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function cmdHooks(sub, flags) {
|
|
374
|
+
const { installHooks, SESSION_START, WEEKLY_WORKFLOW } = await import(join(ROOT, 'scripts', 'hooks.mjs'));
|
|
375
|
+
if (sub === 'install') {
|
|
376
|
+
const r = await installHooks(rest[1], { write: !!flags.write });
|
|
377
|
+
for (const p of r.wrote) console.log(`+ ${p}`);
|
|
378
|
+
for (const p of r.printed) console.log(` ${p}`);
|
|
379
|
+
console.log(`\nSessionStart advisory — add to .claude/settings.json hooks.SessionStart:\n ${SESSION_START.trim().split('\n').pop()}`);
|
|
380
|
+
console.log(`\nWeekly GH Action: copy hooks/stack-align.yml into .github/workflows/ (or re-run with --write).`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (sub === 'show' || !sub) {
|
|
384
|
+
console.log('# git pre-commit (.git/hooks/pre-commit):\n');
|
|
385
|
+
const { PRE_COMMIT } = await import(join(ROOT, 'scripts', 'hooks.mjs'));
|
|
386
|
+
console.log(PRE_COMMIT);
|
|
387
|
+
console.log('# SessionStart advisory:\n');
|
|
388
|
+
console.log(SESSION_START);
|
|
389
|
+
console.log('# weekly GH Action (.github/workflows/stack-align.yml):\n');
|
|
390
|
+
console.log(WEEKLY_WORKFLOW);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
throw new Error(`nq hooks ${sub} — unknown (install | show)`);
|
|
394
|
+
}
|
|
395
|
+
|
|
330
396
|
async function cmdVerify(target) {
|
|
331
397
|
const { verify } = await import(join(ROOT, 'scripts', 'verify.mjs'));
|
|
332
398
|
const names = target === 'all' || !target
|
|
@@ -353,9 +419,25 @@ try {
|
|
|
353
419
|
case 'init': await cmdInit(flags); break;
|
|
354
420
|
case 'tokens': await cmdTokens(); break;
|
|
355
421
|
case 'principles': await cmdPrinciples(); break;
|
|
356
|
-
case 'new':
|
|
422
|
+
case 'new':
|
|
423
|
+
case 'new-component': await cmdNewComponent(rest[0], flags); break;
|
|
424
|
+
case 'new-app': await cmdNew(rest[0], flags); break;
|
|
425
|
+
case 'align': {
|
|
426
|
+
const { run } = await import(join(ROOT, 'scripts', 'align.mjs'));
|
|
427
|
+
await run(rest, flags);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case 'hooks': await cmdHooks(rest[0], flags); break;
|
|
357
431
|
case 'assets': await cmdAssets(rest[0], rest.slice(1), flags); break;
|
|
358
432
|
case 'verify': await cmdVerify(rest[0]); break;
|
|
433
|
+
case 'lint': {
|
|
434
|
+
const { lint } = await import(join(ROOT, 'scripts', 'lint.mjs'));
|
|
435
|
+
const r = await lint(rest[0], { fix: flags.fix, json: flags.json });
|
|
436
|
+
if (r.errorCount) process.exitCode = 1;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case 'audit': await import(join(ROOT, 'scripts', 'audit.mjs')); break;
|
|
440
|
+
case 'sync-skill': await import(join(ROOT, 'scripts', 'sync-skill.mjs')); break;
|
|
359
441
|
default: console.log(HELP);
|
|
360
442
|
}
|
|
361
443
|
} catch (err) {
|