@nurix/etna 0.20.34 → 0.20.35

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "748e1019e5d8",
2
+ "fingerprint": "1099ec325647",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.20.5",
4
+ "version": "3.20.6",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -29,5 +29,5 @@
29
29
  "origin": "https://cdn.nustack.io",
30
30
  "track": "latest"
31
31
  },
32
- "digest": "00beb7af3c87"
32
+ "digest": "8aae863291c5"
33
33
  }
@@ -38,7 +38,7 @@ Steps 1–3 produce *inputs*; step 4 is one continuous whole-canvas act — neve
38
38
  | --- | --- | --- |
39
39
  | 1 Intake | Brief → recorded choices: one identity package, axis overrides, blueprints, domain corpus, the primitives switch; ends with the preview block | [references/intake.md](references/intake.md) |
40
40
  | 2 Structure | The object pass: entities → surfaces → archetypes → states, via the UX-pattern layer | [references/object-pass.md](references/object-pass.md) |
41
- | 3 Content | The believable book: demo data from the corpus, chart choices from the selection engine | [references/content.md](references/content.md) |
41
+ | 3 Content | The believable book: demo data from the corpus, chart choices from the selection engine, every authored string | [references/content.md](references/content.md) · [references/microcopy.md](references/microcopy.md) |
42
42
  | 4 Compose | One mind, whole canvas: chrome, blueprints, the package's design.md in view throughout | [references/compose.md](references/compose.md) |
43
43
  | 5 Ship gate | Tier-1 binary predicates fail closed; Tier-2 tells flag, never gate | [references/ship-gate.md](references/ship-gate.md) |
44
44
 
@@ -76,6 +76,7 @@ What enters context, when. Nothing loads whole except where this table says so.
76
76
  | `references/ux-patterns/<pass>.md` (B2) | The one pass file (prose + its decision table) the current step-2 decision needs | all passes at once |
77
77
  | Corpora | `search_catalog` (family `corpus`) → `get_entry` — body, invariants, people, companies in one payload, at step 3 | other domains |
78
78
  | `data/voices/<id>.md` | The package-bound voice (the package's §9 names it), at step 3 | the voices folder |
79
+ | [references/microcopy.md](references/microcopy.md) | Whenever strings are authored or rewritten — step 3, and an improve run's copy pass | — |
79
80
  | `data/chrome/<recipe>.md` | Recipes for surfaces in the structure doc, at step 4 | recipes for absent surfaces |
80
81
  | [components/index.md](components/index.md) | Always available — the component dictionary, both switch positions and both provenances; a category's API reference on touch, `nurix` mode only | the folder whole; category references in `native` mode |
81
82
  | `data/blueprints/<archetype>.md` | The chosen archetype's file at step 4 shape-choice; the chosen shape thereafter | all archetypes |
@@ -112,7 +113,7 @@ skills/design/
112
113
  ├── SKILL.md # this router
113
114
  ├── components/ # component dictionary: @nurix/components + hand-picked externals
114
115
  ├── scripts/ # validate-design-md.mjs · select-chart.mjs
115
- ├── references/ # the five steps + ship gate + existing apps + emission + foundations
116
+ ├── references/ # the five steps + ship gate + existing apps + emission + foundations + microcopy
116
117
  │ └── ux-patterns/ # B2 decision passes (one file per pass, prose + table)
117
118
  └── data/ # the embedded families: voices/ chrome/ blueprints/ checklists/
118
119
  # elevation/ motion/ iconography.md · tells.csv · charts/ (census)
@@ -14,7 +14,7 @@ Fetch the chosen domain's corpus — `search_catalog` (family `corpus`) to find
14
14
 
15
15
  ## Voice
16
16
 
17
- Load the package-bound voice pack (`data/voices/<id>.md` — the package's §9 names it). All authored strings — buttons, empty states, errors, tooltips, confirmations — speak in that voice, with tone varying by situation per the pack's tone map.
17
+ Load the package-bound voice pack (`data/voices/<id>.md` — the package's §9 names it) and [microcopy.md](microcopy.md). All authored strings — buttons, empty states, errors, tooltips, confirmations, stat cards, server messages the interface shows — speak in that voice, with tone varying by situation per the pack's tone map, and take the form and mechanics `microcopy.md` sets for their element.
18
18
 
19
19
  ## Chart choice
20
20
 
@@ -14,7 +14,7 @@ Whole-canvas authority holds, under the **preservation contract**:
14
14
  2. **Preserve/introduce declaration** precedes any change: what stays, what enters, in one block.
15
15
  3. **The never-silently-change list** holds regardless of authority: routes and slugs, nav labels, form field names and IDs, brand marks, legal copy. The no-jails law is about *our* generated files; a user's existing brand and IA are theirs. Changing anything on this list requires the user's explicit ask.
16
16
 
17
- Improve runs then proceed through the standard steps as scoped: intake (against the incumbent — the package choice may be "extract", below), structure only where surfaces change, content only where data is fake, compose with the finishing pass, and the ship gate on what was touched.
17
+ Improve runs then proceed through the standard steps as scoped: intake (against the incumbent — the package choice may be "extract", below), structure only where surfaces change, content only where data is fake or the ask is the wording (the copy pass, `microcopy.md` § The copy pass on an existing app), compose with the finishing pass, and the ship gate on what was touched.
18
18
 
19
19
  ## Extract — reading an identity package out of a foreign UI
20
20
 
@@ -0,0 +1,126 @@
1
+ # Microcopy — the words on the screen
2
+
3
+ Every string a person reads inside an application: labels, buttons, headings, stat cards, table headers, form help, empty, loading and error states, toasts, confirmations, tooltips, status chips, and the messages a server sends back for the interface to show. The package's voice pack (`data/voices/<id>.md`, named by the design.md's §9) sets the personality and tone map; this file sets the rules every voice obeys. **On a conflict, the voice wins on register and this file wins on mechanics** — a voice may be brisk or warm, but no voice puts a period on a button or an error code in a sentence.
4
+
5
+ Distilled from the Mailchimp Content Style Guide (styleguide.mailchimp.com, CC BY-NC 4.0: voice and tone, writing principles, grammar and mechanics, web elements, writing about people, accessibility, translation) and the documentation skill's style guide (noun-phrase labels, present tense, the stability test), adapted to operator interfaces.
6
+
7
+ ## Principles
8
+
9
+ Every rule below follows from these; cite them by number when you record a copy decision.
10
+
11
+ 1. **Clear before clever.** A string exists so a person can act. Personality is allowed only where it costs no clarity, and never in an error.
12
+ 2. **The work, in the user's words — never the machine.** A string that would change when a table, route, service, error code, environment variable or function is renamed is mechanism leaking into the interface (the documentation skill's stability test, applied to the screen). "Check that the Worker is running" and "BAD_CREDENTIALS: …" are the code talking; "Couldn't reach the server. Retry in a moment." is the product talking.
13
+ 3. **Labels name, actions command, messages explain.** The grammatical form is set by the element, never by taste: a label is a noun phrase with no verb and no pronoun; an action is an imperative verb with its object; a message is full sentences. Mixing them is the commonest source of copy that reads "weird".
14
+ 4. **One concept, one word.** The product's glossary binds every surface: if the record is a *candidate* on one page it is never an *applicant*, *person* or *profile* on the next, and a status is spelled the same in the chip, the filter and the toast. Never vary a term for elegance.
15
+ 5. **Present tense, current state.** Copy describes what is, now. No narration of change ("moved to", "now supports", "new!"), no temporal hedges ("currently", "for now", "still") unless the state genuinely waits on a known event, and no talk about the product's own progress.
16
+ 6. **Tone follows the reader's state.** Voice is constant; tone dials with the situation (§ Tone map). Calm and specific when something fails, brief when something succeeds, plain when something is destroyed.
17
+ 7. **Every message answers the next question.** What happened, why — when the reason is known and useful — and what to do now. An error with no way forward is unfinished.
18
+ 8. **Short, never cryptic.** Cut every word that carries nothing ("successfully", "please note", "in order to"); keep the small words that carry grammar ("the", "to", "that"), because a fragment stripped of them is ambiguous and translates badly. Front-load the word the reader scans for.
19
+ 9. **People are people.** Singular *they* for anyone whose pronouns are unknown; no disability idioms; no blame ("You entered an invalid date" → "Enter a date after today"); no hype, no exclamation in failure, no condescension.
20
+ 10. **Readable without the screen.** Copy never depends on position or colour ("the button on the right", "the red items"); link text names its destination; a label stays visible — a placeholder is never the label.
21
+
22
+ ## Elements
23
+
24
+ Each element has one grammatical form (principle 3). Sentence case everywhere — page titles, navigation, buttons and headings included — because one case scheme cannot drift and reads calmer in a dense interface; proper nouns and product names keep their own casing.
25
+
26
+ | Element | Form | Rules | ❌ → ✅ |
27
+ | --- | --- | --- | --- |
28
+ | Page title | Noun phrase | Names the place or the record; matches its navigation entry word for word | "Manage your jobs here" → "Jobs" |
29
+ | Section heading | Noun phrase | Names the contents; a question only when the section answers one the user asked | "Here's what needs you" → "Your queue" |
30
+ | Navigation item, tab | Noun | The place, not an action; plural for a collection; never "My …" beside "Your …" | "View reports" → "Reports" |
31
+ | Table column header | Noun phrase, 1–3 words | Units live in the header, never in every cell; no "#" or "No." for counts | "How long in stage (days)" → "Days in stage" |
32
+ | Stat card | Label + numeral + optional caption + optional action | The **label** names what is counted, scope and threshold included; the **value** is a numeral, and zero is `0`; the **caption** adds one fact the number cannot carry (the oldest item's age, a trend, a target) and never restates the value in words; the **action** is a verb and its destination | "0 · Screenings to review · Nothing waiting" → "Awaiting screening · 0"; "Waiting too long · In one stage 13 days or more" → "Stalled 14+ days · 3 · oldest 21 days" |
33
+ | Button, menu action | Verb + object | The specific verb ("Publish job", never "Submit" or "OK"); the confirm button repeats the trigger's verb; no end punctuation; an ellipsis only when the action opens a further step that asks for input | "Submit" → "Send offer"; "Yes" → "Delete 3 candidates" |
34
+ | Link | The destination or the action | Never "click here", never a bare "Learn more" without its object; the article stays outside the link | "Click here for the audit log" → "Open the audit log" |
35
+ | Form label | Noun phrase | Always visible; mark whichever of required/optional is the minority, the same way on every form | "Please enter your email" → "Email" |
36
+ | Help text | One sentence | Below the field; states format, limit or consequence — never repeats the label | "Your password" → "At least 12 characters" |
37
+ | Placeholder | An example value, or nothing | Never instructions, never the label | "Enter email here" → "name@company.com" |
38
+ | Inline validation | Instruction | At the field; says what to enter, not what was wrong | "Invalid email" → "Enter an email like name@company.com" |
39
+ | Error (banner, toast, page) | Title + sentence | Title: "Couldn't {verb} {object}"; body: the reason in the user's terms, then the next step; codes and stack detail only behind a details/copy affordance for support | "Error 500: INTERNAL" → "Couldn't save the job. Retry in a moment — your changes are still here." |
40
+ | Permission refusal | Title + sentence | Says what the person can't do and who can; never a lecture, never "Access denied" alone | "403 Forbidden" → "Only admins can change integrations. Ask an admin for access." |
41
+ | Empty state | Heading + one sentence + at most one action | Distinguish the three causes, each with its own copy: **first use** (what will appear and how to start), **filtered to nothing** (say so and offer to clear filters), **failed to load** (that is an error, not emptiness) | "No data" → "No candidates yet" / "Candidates appear here when someone applies or you add one." / [Add candidate] |
42
+ | Loading | Noun phrase, or nothing | Silent under a second; past that, name what is loading; never "Please wait" | "Please wait…" → "Loading candidates…" |
43
+ | Success toast | Past-tense statement | Names the object and the outcome; offers Undo where the action is reversible; no "successfully", no exclamation | "Job was successfully published!" → "Job published" |
44
+ | Confirmation dialog | Question title + consequence + verb buttons | Title names the action and its object; body states what is lost and whether it can be undone; buttons are the verb and "Cancel"; never "Are you sure?" | "Are you sure?" [Yes] [No] → "Delete 3 candidates?" / "Their applications and notes are deleted too. This can't be undone." / [Delete candidates] [Cancel] |
45
+ | Tooltip | One sentence or noun phrase | Supplementary only — nothing a person needs is hidden in it; describes what the control does | "Click to toggle" → "Collapse the sidebar" |
46
+ | Status chip, badge | 1–2 words | Adjective or past participle from the glossary's status vocabulary, identical wherever the status appears | "In Progress", "in-progress", "Ongoing" → "In progress" |
47
+ | Count and pagination | Numeral + noun | "12 candidates", "1–25 of 340"; singular and plural both handled | "Showing results: 12" → "12 candidates" |
48
+ | Notification, email from the product | Subject names the event | Who did what to which object; the body links straight to it | "Update" → "Priya submitted feedback on Ana Silva" |
49
+
50
+ ## Mechanics
51
+
52
+ | Matter | Rule |
53
+ | --- | --- |
54
+ | Person | Sentences may address the user as "you". Labels contain no pronouns. The product refers to itself rarely; "we" only when a human team acts (support, review). A system failure is phrased as the event ("Couldn't send the invite"), never as "We encountered an error". |
55
+ | Contractions | In sentences, yes ("can't", "you'll") — they read human. Never in labels or buttons. |
56
+ | Numbers | Numerals always, 1–9 included. Thousands separators. Abbreviate (`1.2k`) only where space forces it, and never in a value someone acts on. Percent as `%`. Ranges with an en dash (`3–5 days`). Currency per locale; a code (`USD`) wherever more than one currency can appear. |
57
+ | Dates and times | Relative for the recent past ("5 min ago", "Yesterday"), absolute beyond a week ("12 Oct", the year added only when it differs from the current one). A time zone wherever two people could read the same time differently. The package's formatting foundations decide the exact shapes. |
58
+ | Punctuation | No end punctuation on labels, buttons, headings, chips or single-fragment tooltips; full sentences end with a period. Exclamation points never in errors or warnings and at most one, rarely, in success. Em dash: one per message at most, never as a joint between clauses. Oxford comma. Curly quotes. No ampersand unless it is part of a name. |
59
+ | Abbreviations | Spell out on first use unless universal to the audience (URL, PDF, ID, CSV). Internal acronyms and code names never appear. |
60
+ | Truncation | Truncate text at the end with the full value on hover; never truncate a number, a date or the distinguishing end of an identifier. |
61
+ | Inclusion | Singular *they*; neutral role nouns; age, disability and health only when relevant, and then person-first unless the person prefers otherwise; no idioms built on disability ("blind spot", "falls on deaf ears"). |
62
+ | Translation-ready | Subject–verb–object sentences, active voice; no idioms, slang or puns; repeat the noun rather than lean on an ambiguous "it"; no strings assembled from fragments, because word order differs by language; leave room for 30% longer text. |
63
+
64
+ ### Word choices
65
+
66
+ Pick one of each pair for the whole product and never mix them.
67
+
68
+ | Use | Not | Because |
69
+ | --- | --- | --- |
70
+ | Sign in / sign out (verb), sign-in (noun) | log in, login, log on | One pair across every surface |
71
+ | Select, choose | click, tap | Device-neutral |
72
+ | Create (a new record) | add, make, new | *Add* is for attaching something that already exists |
73
+ | Add (attach existing) | create, assign | Keeps *create* meaning "new record" |
74
+ | Delete (destroys) | remove | *Remove* detaches and leaves the record; the two must never be swapped |
75
+ | Remove (detaches) | delete | As above |
76
+ | Archive (hides, reversible) | delete | Tells the person it can come back |
77
+ | Cancel (abandons an action) | close, dismiss | *Close* only dismisses something with nothing to lose |
78
+ | Save (keeps changes) | submit, apply | *Send*, *Publish*, *Invite* when the action reaches someone else |
79
+ | Set up (verb), setup (noun) | — | Verb and noun differ |
80
+ | Email | e-mail, E-mail | — |
81
+ | Can't, couldn't | unable to, failed to | Plain, human, shorter |
82
+
83
+ ## Tone map
84
+
85
+ Voice holds; tone moves with the reader's state (principle 6). The voice pack's own tone map refines these defaults for its register.
86
+
87
+ | Situation | Reader's state | Tone | Pattern |
88
+ | --- | --- | --- | --- |
89
+ | Routine labels and actions | Focused, scanning | Neutral, brisk | Noun phrases and verbs; nothing extra |
90
+ | First use, empty | Orienting | Encouraging, factual | What appears here and the one way to start |
91
+ | Success | Satisfied, moving on | Brief | Past tense, object named, Undo if reversible |
92
+ | Error the person can fix | Mildly frustrated | Calm, specific | What to enter or change, at the place it applies |
93
+ | Failure that isn't theirs | Frustrated, blocked | Accountable, steady | What failed, that their work is safe if it is, what to do (retry, wait), no internals |
94
+ | Destructive confirmation | Cautious | Plain, explicit | The object, the loss, whether it can be undone — the plainness is the safeguard |
95
+ | Warning before a consequence | Attentive | Direct | The consequence first, then the choice |
96
+ | Permission refusal | Blocked, possibly annoyed | Respectful | What they can't do, who can, how to ask |
97
+ | Long wait | Uncertain | Reassuring | What is happening and, if known, how long |
98
+ | Milestone | Pleased | Warm, never hyped | One sentence of acknowledgement, no fanfare |
99
+
100
+ ## The copy pass on an existing app
101
+
102
+ When the ask is to fix an app's wording, run this as the improve step's content scope (`existing-apps.md`). An explicit ask to correct the copy lifts the never-silently-change protection from navigation labels; legal copy stays protected unless the ask names it.
103
+
104
+ 1. **Inventory by surface, rendered.** Read each surface's strings in the source and drive the surface, so conditional strings (empty, error, refused, loading) are seen as well as the happy path.
105
+ 2. **Fix the glossary first.** List the domain nouns, the status vocabulary and the verbs the app uses; choose one word per concept (principle 4) and record the list in `product.md` under its vocabulary, where every later string is checked against it.
106
+ 3. **Rewrite by element.** Take each string's element from § Elements and apply its form and rules; the server messages that reach the interface are in scope, because a person reads them.
107
+ 4. **Keep the contract honest.** A test that asserts a string is updated with it — the copy is the promise it checks, so a changed string is an intended behaviour change, never a weakened test. A feature document that quotes promised wording is updated in the same change.
108
+ 5. **Sweep for the tells** below before calling the pass done.
109
+
110
+ ### Copy tells
111
+
112
+ Each is a near-certain defect; fix it or record why it stays.
113
+
114
+ | Tell | Principle |
115
+ | --- | --- |
116
+ | "Successfully", "Please", "Oops", "Uh oh", "Whoops" | 8, 1 |
117
+ | "Are you sure?", "Click here", a bare "Learn more", "Submit", "OK" as a confirm button | 3, 10 |
118
+ | "Invalid …", "Error:" as a prefix, an error code or an exception name inside a sentence | 2, 7 |
119
+ | An exclamation point in an error or a warning | 6 |
120
+ | Title Case On A Button Or Heading; a period on a label or button | Elements |
121
+ | "Currently", "for now", "new", "now supports", "has been moved" | 5 |
122
+ | A service, table, route, environment variable or library named in user-facing copy | 2 |
123
+ | Two words for one concept across surfaces, or one status spelled two ways | 4 |
124
+ | A stat caption that restates its value in words ("0 · Nothing waiting") | Elements |
125
+ | A placeholder doing a label's job; help text repeating its label | 10 |
126
+ | A sentence assembled from fragments joined at runtime | Mechanics |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.34",
3
+ "version": "0.20.35",
4
4
  "description": "Bootstrap a project with a Nurix harness — writes the committed contract (CLAUDE.md, AGENTS.md, .claude/harness.json, settings.json, the mount hook) and mounts the harness itself, gitignored, from a bundled payload; sessions keep it current from the CDN.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,5 +46,5 @@
46
46
  "agent",
47
47
  "plugin"
48
48
  ],
49
- "etnaFingerprint": "748e1019e5d8"
49
+ "etnaFingerprint": "1099ec325647"
50
50
  }