super-ux 0.29.0 → 0.30.0
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 +72 -0
- package/README.md +35 -0
- package/bin/super-ux.js +28 -5
- package/cursor/rules/brand-voice.mdc +37 -0
- package/package.json +2 -1
- package/plugins/super-ux/scripts/brand_lint.py +957 -0
- package/plugins/super-ux/scripts/ux_doctor.py +46 -0
- package/templates/brand/README.md +53 -0
- package/templates/brand/channels.md +129 -0
- package/templates/brand/facts.md +36 -0
- package/templates/brand/locale.md +68 -0
- package/templates/brand/strings.md +41 -0
- package/templates/brand/terminology.md +45 -0
- package/templates/brand/voice.md +58 -0
- package/templates/claude-rule.md +20 -0
|
@@ -166,6 +166,44 @@ def fix(ux: Path, d: dict) -> list[str]:
|
|
|
166
166
|
return done
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
def brand_contract_state(root: Path) -> list[str]:
|
|
170
|
+
"""The brand pack's contract version, or why there is nothing to report.
|
|
171
|
+
|
|
172
|
+
Same blind spot as the chain's: a pack written to an old contract is
|
|
173
|
+
internally consistent, so `brand_lint.py` stays quiet about it. Only a
|
|
174
|
+
marker comparison notices, which is why the doctor reads it too.
|
|
175
|
+
"""
|
|
176
|
+
brand = root / "docs" / "brand"
|
|
177
|
+
if not brand.is_dir():
|
|
178
|
+
return []
|
|
179
|
+
versions: dict[str, str] = {}
|
|
180
|
+
unmarked: list[str] = []
|
|
181
|
+
for path in sorted(brand.rglob("*.md")):
|
|
182
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
183
|
+
found = re.search(r"^Contract:\s*brand-contract\s*(v\d+)\s*$", text, re.M)
|
|
184
|
+
rel = path.relative_to(brand).as_posix()
|
|
185
|
+
if found:
|
|
186
|
+
versions[rel] = found.group(1)
|
|
187
|
+
else:
|
|
188
|
+
unmarked.append(rel)
|
|
189
|
+
out = []
|
|
190
|
+
distinct = set(versions.values())
|
|
191
|
+
if len(distinct) > 1:
|
|
192
|
+
out.append(
|
|
193
|
+
"docs/brand: mixed contract versions -- "
|
|
194
|
+
+ ", ".join(f"{k} {v}" for k, v in sorted(versions.items()))
|
|
195
|
+
)
|
|
196
|
+
elif distinct and distinct != {"v1"}:
|
|
197
|
+
out.append(
|
|
198
|
+
f"docs/brand: written to brand-contract {distinct.pop()}, current is v1"
|
|
199
|
+
)
|
|
200
|
+
if unmarked:
|
|
201
|
+
out.append(
|
|
202
|
+
"docs/brand: no contract marker on " + ", ".join(unmarked[:4])
|
|
203
|
+
)
|
|
204
|
+
return out
|
|
205
|
+
|
|
206
|
+
|
|
169
207
|
def main() -> int:
|
|
170
208
|
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
171
209
|
ux = find_ux_dir(args[0] if args else None)
|
|
@@ -188,6 +226,14 @@ def main() -> int:
|
|
|
188
226
|
return 0
|
|
189
227
|
|
|
190
228
|
problems = report(ux, d)
|
|
229
|
+
|
|
230
|
+
brand = brand_contract_state(ux.parent.parent)
|
|
231
|
+
if brand:
|
|
232
|
+
print("\nBrand pack:")
|
|
233
|
+
for line in brand:
|
|
234
|
+
print(f" {line}")
|
|
235
|
+
problems = True
|
|
236
|
+
|
|
191
237
|
if "--fix" in sys.argv:
|
|
192
238
|
applied = fix(ux, d)
|
|
193
239
|
print("\nApplied:" if applied else "\nNothing to apply automatically.")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
|
|
3
|
+
# docs/brand/ — how this product speaks
|
|
4
|
+
|
|
5
|
+
`docs/ux/` decides **what the product does**. This folder decides **how it
|
|
6
|
+
speaks** — in the interface, on the landing page, in the store listing, in a
|
|
7
|
+
post. One voice, many registers.
|
|
8
|
+
|
|
9
|
+
Owned by the `brand-voice` skill. Written by `copywriting`. Checked by
|
|
10
|
+
`python3 docs/brand/lint.py` and by `/ux-audit copy`.
|
|
11
|
+
|
|
12
|
+
| File | Holds |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `voice.md` | the identity: pack, five axes, narrative, invariants |
|
|
15
|
+
| `terminology.md` | our words, banned words, entity and tier names |
|
|
16
|
+
| `facts.md` | canonical numbers and proof — the only source of a figure |
|
|
17
|
+
| `channels.md` | one record per surface: register, limits, bans |
|
|
18
|
+
| `strings.md` | interface string registry → `file:line` → scenario |
|
|
19
|
+
| `locales/<code>.md` | per-locale delta |
|
|
20
|
+
|
|
21
|
+
## Sources
|
|
22
|
+
|
|
23
|
+
**Fill this in before anything else.** The linter scans nothing outside these
|
|
24
|
+
paths, and refuses to report a clean run over a surface it never read
|
|
25
|
+
(`B006`). Delete the keys this project does not have — a declared-but-absent
|
|
26
|
+
source is worse than an omitted one.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Sources:
|
|
30
|
+
ui: src/**/*.{ts,tsx,js,jsx,vue,svelte}
|
|
31
|
+
marketing: content/**/*.{md,mdx}
|
|
32
|
+
store: store/{ios,android}/*.md
|
|
33
|
+
robots: public/robots.txt
|
|
34
|
+
locales: src/locales/*.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`ui` and `marketing` also classify findings — several checks apply to only
|
|
38
|
+
one of the two.
|
|
39
|
+
|
|
40
|
+
## The hard rule
|
|
41
|
+
|
|
42
|
+
Any change to public-facing text updates this folder in the same change, and
|
|
43
|
+
`python3 docs/brand/lint.py` exits clean before the work is called done.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
| Command | Does |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `/brand` | status across every file, then one recommended action |
|
|
50
|
+
| `/brand-init` | pick a voice pack and calibrate it to this product |
|
|
51
|
+
| `/brand-update` | recalibrate after positioning or personas changed |
|
|
52
|
+
| `/brand-lint` | run the linter |
|
|
53
|
+
| `/copy` | write or edit copy for a named surface |
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
|
|
3
|
+
# Channels
|
|
4
|
+
|
|
5
|
+
One record per surface. `Register` is deltas against the five axes in
|
|
6
|
+
`voice.md` — a register moves the axes, never the invariants.
|
|
7
|
+
|
|
8
|
+
`Forbidden` always carries both halves, even when one is `none`. Platform
|
|
9
|
+
physics and brand choice written on one line become indistinguishable within
|
|
10
|
+
a quarter, and then nobody can tell which is safe to revisit when the
|
|
11
|
+
platform changes.
|
|
12
|
+
|
|
13
|
+
Delete the surfaces this product does not have. Do not rename one: the
|
|
14
|
+
linter and the audit address surfaces by the names in the contract.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Product surfaces
|
|
19
|
+
|
|
20
|
+
### primary action
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Register: <humor -1>
|
|
24
|
+
Format: <verb phrase naming the outcome; one primary per screen>
|
|
25
|
+
Limits: <24 characters>
|
|
26
|
+
Forbidden: physics: none | brand: <"Submit", "OK", bare nouns>
|
|
27
|
+
CTA: <this surface is the CTA>
|
|
28
|
+
Proof: none
|
|
29
|
+
Locales: <coefficient applies to the character limit>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### error
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Register: humor -3, density +1, distance -1
|
|
36
|
+
Format: what happened, what was not affected, one next step
|
|
37
|
+
Limits: <…>
|
|
38
|
+
Forbidden: physics: none | brand: humor, "unexpected", blame, bare codes
|
|
39
|
+
CTA: the recovery action
|
|
40
|
+
Proof: none
|
|
41
|
+
Locales: <…>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### empty state
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Register: <unchanged>
|
|
48
|
+
Format: what belongs here, why it is worth it, the one starting action
|
|
49
|
+
Limits: <…>
|
|
50
|
+
Forbidden: physics: none | brand: apology, "Nothing here yet" alone
|
|
51
|
+
CTA: one
|
|
52
|
+
Proof: none
|
|
53
|
+
Locales: <…>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### paywall and upgrade
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Register: humor -3, distance +1
|
|
60
|
+
Format: what is sold, what it costs, what happens at term end, how to leave
|
|
61
|
+
Limits: <…>
|
|
62
|
+
Forbidden: physics: none | brand: humor, manufactured urgency, hidden terms
|
|
63
|
+
CTA: one primary, one way out
|
|
64
|
+
Proof: one sourced figure
|
|
65
|
+
Locales: <legal differences per locale>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### destructive confirm
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Register: humor -3
|
|
72
|
+
Format: object, consequence, reversibility; verb repeats the action
|
|
73
|
+
Limits: <…>
|
|
74
|
+
Forbidden: physics: none | brand: humor, "OK" as the confirming verb
|
|
75
|
+
CTA: the destructive verb
|
|
76
|
+
Proof: none
|
|
77
|
+
Locales: <…>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
<add the remaining product surfaces this product has: loading,
|
|
81
|
+
success/toast, onboarding, billing and receipts, settings and legal,
|
|
82
|
+
transactional email and push, docs and help>
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Marketing surfaces
|
|
87
|
+
|
|
88
|
+
### landing hero
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Register: confidence +1, density -1
|
|
92
|
+
Format: one headline, one subhead, one primary action
|
|
93
|
+
Limits: title 60, meta description 160
|
|
94
|
+
Forbidden: physics: none | brand: superlatives with no facts.md row
|
|
95
|
+
CTA: one, verb plus outcome
|
|
96
|
+
Proof: one number, sourced
|
|
97
|
+
Locales: <coefficient applies to both limits>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### X
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Register: density +1, distance -1
|
|
104
|
+
Format: one idea; threads 5-12 posts, each standing alone
|
|
105
|
+
Limits: 280
|
|
106
|
+
Forbidden: physics: link in body suppresses reach; >2 hashtags penalised;
|
|
107
|
+
editing within 30 minutes resets distribution
|
|
108
|
+
| brand: <engagement bait, fake urgency>
|
|
109
|
+
CTA: <in the first reply, not the post>
|
|
110
|
+
Proof: <one figure, sourced>
|
|
111
|
+
Locales: <…>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Reddit
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Register: distance -2, humor +1
|
|
118
|
+
Format: a statement, not a headline; the comments are the content
|
|
119
|
+
Limits: <per subreddit>
|
|
120
|
+
Forbidden: physics: <subreddit rules outrank everything here>
|
|
121
|
+
| brand: undisclosed affiliation, CTA of any kind
|
|
122
|
+
CTA: none
|
|
123
|
+
Proof: <first-hand only>
|
|
124
|
+
Locales: <…>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
<add the remaining marketing surfaces this product uses: landing body,
|
|
128
|
+
pricing, blog, changelog, LinkedIn, HN and Product Hunt, App Store,
|
|
129
|
+
Google Play, ads, lifecycle email>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
|
|
3
|
+
# Facts
|
|
4
|
+
|
|
5
|
+
**The only source of any figure in public copy.** A number on a public
|
|
6
|
+
surface with no row here is `B030` and blocks. A row with no `Source`, or one
|
|
7
|
+
past its `Review by`, is `B031` and warns.
|
|
8
|
+
|
|
9
|
+
A missing fact is reported, never invented to close a gap.
|
|
10
|
+
|
|
11
|
+
| Fact | Value | Source | Checked | Review by | Public |
|
|
12
|
+
|---|---|---|---|---|---|
|
|
13
|
+
| <what the number counts> | <448> | <api/catalog.json> | <2026-08-05> | <2026-11-05> | <yes> |
|
|
14
|
+
| <internal figure> | <$3.10> | <billing export> | <2026-08-05> | <2026-11-05> | <no> |
|
|
15
|
+
|
|
16
|
+
`Public: no` marks figures that exist and must never be quoted — internal
|
|
17
|
+
margins, unreleased counts, anything under embargo. The linter treats
|
|
18
|
+
quoting one as the same failure as quoting a number that does not exist.
|
|
19
|
+
|
|
20
|
+
## Proof that is not a number
|
|
21
|
+
|
|
22
|
+
Testimonials, awards, certifications and press. Same rules: attributed,
|
|
23
|
+
dated, and re-checked.
|
|
24
|
+
|
|
25
|
+
| Claim | Attribution | Source | Checked | Review by | Public |
|
|
26
|
+
|---|---|---|---|---|---|
|
|
27
|
+
| <…> | <name, role, company> | <where it was said> | <YYYY-MM-DD> | <YYYY-MM-DD> | <yes> |
|
|
28
|
+
|
|
29
|
+
## Required disclaimers
|
|
30
|
+
|
|
31
|
+
Text that must appear alongside specific claims — regulatory, contractual, or
|
|
32
|
+
promised. Locale differences live in `locales/<code>.md`.
|
|
33
|
+
|
|
34
|
+
| Claim it attaches to | Required text |
|
|
35
|
+
|---|---|
|
|
36
|
+
| <…> | <…> |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
Locale: <de>
|
|
3
|
+
Primary: <no>
|
|
4
|
+
Address form: <Sie>
|
|
5
|
+
Length coefficient: <1.30>
|
|
6
|
+
Humor: <-1 from base>
|
|
7
|
+
Never translated: <product name, entity names, tier names>
|
|
8
|
+
Keywords: <own research — see below>
|
|
9
|
+
Reviewed by: <name of someone who reads this language daily, or `unreviewed`>
|
|
10
|
+
|
|
11
|
+
# Locale delta
|
|
12
|
+
|
|
13
|
+
Copy this file to `locales/<code>.md`, one per declared locale. The invariant
|
|
14
|
+
half of the voice does not appear here — it is in `voice.md` and holds in
|
|
15
|
+
every language. This file carries only what is decided again.
|
|
16
|
+
|
|
17
|
+
## Address form
|
|
18
|
+
|
|
19
|
+
<The single choice, applied everywhere. This decision changes how the brand
|
|
20
|
+
is perceived more than any word choice, so it is made once and recorded, not
|
|
21
|
+
per string.>
|
|
22
|
+
|
|
23
|
+
## Dead idioms
|
|
24
|
+
|
|
25
|
+
Idiom and wordplay from the primary locale that does not survive. The
|
|
26
|
+
replacement does the same **job**; it is not a translation of the joke.
|
|
27
|
+
|
|
28
|
+
| Primary | Replacement | Job it does |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| <"ship it"> | <"raus damit"> | <permission to stop polishing> |
|
|
31
|
+
|
|
32
|
+
## Keywords
|
|
33
|
+
|
|
34
|
+
Researched in this market, never translated from the primary. The
|
|
35
|
+
translation of a high-volume English term is routinely a term with no volume,
|
|
36
|
+
while the term people actually type is a different word.
|
|
37
|
+
|
|
38
|
+
| Term | Volume | Where it is used |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| <…> | <…> | <…> |
|
|
41
|
+
|
|
42
|
+
## Legal differences
|
|
43
|
+
|
|
44
|
+
Required copy this locale has and others do not. Treated as required strings:
|
|
45
|
+
a missing one is a defect even when parity is otherwise complete.
|
|
46
|
+
|
|
47
|
+
| Requirement | Text or reference |
|
|
48
|
+
|---|---|
|
|
49
|
+
| <Impressum> | <…> |
|
|
50
|
+
| <VAT-inclusive pricing> | <…> |
|
|
51
|
+
|
|
52
|
+
## Length notes
|
|
53
|
+
|
|
54
|
+
Surfaces where the coefficient bites hardest in this locale — usually
|
|
55
|
+
buttons, tab labels, store title and subtitle. Recorded here so the
|
|
56
|
+
constraint reaches the primary-locale original, where it can still be
|
|
57
|
+
designed around.
|
|
58
|
+
|
|
59
|
+
| Surface | Primary limit | Effective limit | Note |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| <App Store title> | <30> | <23> | <…> |
|
|
62
|
+
|
|
63
|
+
## Parity
|
|
64
|
+
|
|
65
|
+
Computed by the linter against `strings.md`; not maintained by hand. Below
|
|
66
|
+
the threshold in `voice.md` it warns (`B071`) with the percentage. A partial
|
|
67
|
+
locale that declares itself is a known state; one that does not is a surprise
|
|
68
|
+
for a user who does not speak the fallback.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
|
|
3
|
+
# Interface strings
|
|
4
|
+
|
|
5
|
+
A **decision registry**, not a message catalog. It holds no translations and
|
|
6
|
+
does not replace i18n keys. It records which strings have been reconciled
|
|
7
|
+
with the voice, which scenario each serves, and where each one lives.
|
|
8
|
+
|
|
9
|
+
This is what makes "one action, two names" (`B020`) checkable at all. Without
|
|
10
|
+
it, that defect is only findable by reading the entire interface — which is
|
|
11
|
+
why it survives in every product that has not written one down.
|
|
12
|
+
|
|
13
|
+
Populated by an inventory sweep at init, not by hand.
|
|
14
|
+
|
|
15
|
+
| Key | Text (primary) | Location | Scenario | Status |
|
|
16
|
+
|---|---|---|---|---|
|
|
17
|
+
| <action.project.publish> | <Publish> | <src/ui/ProjectBar.tsx:47> | <SCN-014> | <agreed> |
|
|
18
|
+
|
|
19
|
+
## Columns
|
|
20
|
+
|
|
21
|
+
- **Key** — dot-separated, stable, names the action rather than the screen.
|
|
22
|
+
Two rows sharing a `Key` with different `Text` is `B020`.
|
|
23
|
+
- **Text (primary)** — the string in the primary locale. Other locales live
|
|
24
|
+
in the project's own i18n files; parity is computed against this column.
|
|
25
|
+
- **Location** — `file:line`. A location that no longer resolves is `B023`.
|
|
26
|
+
- **Scenario** — the `SCN-NNN` this string serves. A string serving no
|
|
27
|
+
scenario is a question for `ux-scenarios`, not a copy problem.
|
|
28
|
+
- **Status** — `agreed` · `proposed` · `drifted` · `orphan`.
|
|
29
|
+
|
|
30
|
+
## Statuses
|
|
31
|
+
|
|
32
|
+
| Status | Means |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `agreed` | reconciled with the voice and approved |
|
|
35
|
+
| `proposed` | written, not yet approved |
|
|
36
|
+
| `drifted` | the code no longer matches this row (`B021`) |
|
|
37
|
+
| `orphan` | in the registry, no longer in the code |
|
|
38
|
+
|
|
39
|
+
A string found in code with no row here is `B022` — a warning, not an error,
|
|
40
|
+
so adopting this registry on an existing product does not block from day one.
|
|
41
|
+
It becomes an error only once someone agrees the row.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
|
|
3
|
+
# Terminology
|
|
4
|
+
|
|
5
|
+
The dictionary the linter reads. Column one of **Product terms** and
|
|
6
|
+
**Banned** drives `B010` and `B011`; **Entity and tier names** drives `B012`.
|
|
7
|
+
|
|
8
|
+
## Product terms — always
|
|
9
|
+
|
|
10
|
+
Where the product has its own word, the generic one is a defect, not a
|
|
11
|
+
synonym.
|
|
12
|
+
|
|
13
|
+
| Our term | Never write | Applies to |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| <Run> | <Execution, Job> | <what the product performs> |
|
|
16
|
+
|
|
17
|
+
## Entity and tier names — exact spelling
|
|
18
|
+
|
|
19
|
+
One spelling, everywhere: interface, marketing, billing, support, docs.
|
|
20
|
+
|
|
21
|
+
| Name | Wrong forms seen |
|
|
22
|
+
|---|---|
|
|
23
|
+
| <Pro> | <PRO, Pro plan, pro> |
|
|
24
|
+
|
|
25
|
+
## Banned
|
|
26
|
+
|
|
27
|
+
Seeded at init from weak verbs, hedging chains and the marker vocabulary in
|
|
28
|
+
`ai-tells.md`. Calibration adds what is specific to this product.
|
|
29
|
+
|
|
30
|
+
| Word or phrase | Why | Use instead |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| leverage | filler verb | use |
|
|
33
|
+
| seamless | claims what it cannot show | name the step that disappeared |
|
|
34
|
+
| utilize | longer word, same meaning | use |
|
|
35
|
+
| robust | hides the number | the number |
|
|
36
|
+
| <…> | <…> | <…> |
|
|
37
|
+
|
|
38
|
+
## Glossary
|
|
39
|
+
|
|
40
|
+
Terms this product uses in a specific sense. If a term is here, it is
|
|
41
|
+
grounded before it is leaned on.
|
|
42
|
+
|
|
43
|
+
| Term | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| <…> | <…> |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Contract: brand-contract v1
|
|
2
|
+
Voice pack: <pack id from voice-packs.md, or `custom`>
|
|
3
|
+
Locales: <en (primary)>
|
|
4
|
+
Locale parity threshold: 80%
|
|
5
|
+
Derived-from: <PER-NN, JTBD-NN from docs/ux/foundation.md — or `inferred`>
|
|
6
|
+
Status: draft
|
|
7
|
+
Last calibrated: <YYYY-MM-DD>
|
|
8
|
+
|
|
9
|
+
# Voice
|
|
10
|
+
|
|
11
|
+
The pack was the starting position. This file is the truth.
|
|
12
|
+
|
|
13
|
+
## Axes
|
|
14
|
+
|
|
15
|
+
The five axes are fixed. A project sharpens the wording; it does not add or
|
|
16
|
+
remove an axis, because `channels.md` expresses every register as a delta
|
|
17
|
+
against exactly these five.
|
|
18
|
+
|
|
19
|
+
| Axis | The product IS | The product IS NOT |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Confidence | <…> | <…> |
|
|
22
|
+
| Register | <…> | <…> |
|
|
23
|
+
| Distance | <…> | <…> |
|
|
24
|
+
| Humor | <…> | <…> |
|
|
25
|
+
| Density | <…> | <…> |
|
|
26
|
+
|
|
27
|
+
## Narrative
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Hero: <who the reader is, in the role this product meets them in>
|
|
31
|
+
Enemy: <the state of the world that costs them — not a competitor>
|
|
32
|
+
Product role: <instrument | guide | weapon>
|
|
33
|
+
Promise: <one line, checkable against a row in facts.md>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Invariant in every language
|
|
37
|
+
|
|
38
|
+
What survives translation. Breaking one of these is a finding on any surface,
|
|
39
|
+
in any locale.
|
|
40
|
+
|
|
41
|
+
- <e.g. never hedges>
|
|
42
|
+
- <e.g. never claims a number that is not in facts.md>
|
|
43
|
+
|
|
44
|
+
## Reconsidered per locale
|
|
45
|
+
|
|
46
|
+
Conventions rather than character. Each is decided again in
|
|
47
|
+
`locales/<code>.md`.
|
|
48
|
+
|
|
49
|
+
- address form
|
|
50
|
+
- humor level
|
|
51
|
+
- idiom and wordplay
|
|
52
|
+
- <…>
|
|
53
|
+
|
|
54
|
+
## Failure mode
|
|
55
|
+
|
|
56
|
+
Copied from the pack, kept here so the audit can look for it by name.
|
|
57
|
+
|
|
58
|
+
<the degenerate form this voice collapses into when overdone>
|
package/templates/claude-rule.md
CHANGED
|
@@ -29,3 +29,23 @@
|
|
|
29
29
|
- Use `/ux` as the entry point; skills: `ux-foundation`, `ux-flows`
|
|
30
30
|
(flows + Figma mockups), `ux-scenarios` for maintenance, `ux-audit` for
|
|
31
31
|
evidence-backed verification. Full map: the plugin's system-map reference.
|
|
32
|
+
|
|
33
|
+
## Brand voice — hard rule (super-ux)
|
|
34
|
+
|
|
35
|
+
- `docs/brand/` is the source of truth for how the product speaks:
|
|
36
|
+
`voice.md` (axes, narrative, invariants), `terminology.md` (our words and
|
|
37
|
+
the banned ones), `facts.md` (the only source of any public figure),
|
|
38
|
+
`channels.md` (one record per surface), `strings.md` (the interface string
|
|
39
|
+
registry), `locales/<code>.md`.
|
|
40
|
+
- Any change to public-facing text — an interface string, a landing page, a
|
|
41
|
+
post, a store listing, an ad, an email — updates `docs/brand/` in the SAME
|
|
42
|
+
change. A new string with no registry row is drift, not a detail.
|
|
43
|
+
- **Never quote a number that has no row in `facts.md`,** and never invent a
|
|
44
|
+
fact, statistic, quote or expert to fill a gap. Report the gap instead.
|
|
45
|
+
- **One action keeps one name** across button, confirmation, toast, history,
|
|
46
|
+
notification and accessible name. Search `strings.md` before naming one.
|
|
47
|
+
- **No humor, exclamation marks or emoji** on error, destructive confirm,
|
|
48
|
+
billing or paywall surfaces — in any voice.
|
|
49
|
+
- Run `python3 docs/brand/lint.py` after any text change and before calling
|
|
50
|
+
work done. It must exit clean; wire it into CI or pre-commit alongside the
|
|
51
|
+
UX linter so copy drift cannot merge.
|