stitch2 0.15.2 → 0.16.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/README.md CHANGED
@@ -72,7 +72,8 @@ file that already exists. From there the agent spends its effort on the design.
72
72
  ```
73
73
  - **Versions, variants and approval**: a screen has versions (v1, v2, …), and each version has variants, its
74
74
  layouts for mobile and desktop (`mobile-v3.html`, `desktop-v3.html`). The canvas groups them; you approve a
75
- version as a whole or one variant at a time, leave notes, and approving archives the previous approved layout.
75
+ version as a whole or one variant at a time, and leave notes. The approved version is the one current version:
76
+ approving it archives every earlier one, and only it gets implemented.
76
77
  - **DESIGN.md builder**: `stitch2 init` writes a complete DESIGN.md from a few choices (brand colour, modes,
77
78
  fonts, roundness, density), with colours checked for contrast; `stitch2 extract` measures one from an existing
78
79
  site or screens.
@@ -83,7 +84,7 @@ file that already exists. From there the agent spends its effort on the design.
83
84
  - **Skills** for agents (`skills/`): `stitch2` (the design workflow, principles, typography, components,
84
85
  versions), `stitch2-design-md` (building the design system), `stitch2-alternatives` (fresh-eyes versions and
85
86
  testing skill changes), `stitch2-consistency` (verifying and fixing
86
- consistency), `stitch2-i18n` (screens in every language), `stitch2-app-sync` (keeping the app and the designs in step: design first, then the app) and,
87
+ consistency), `stitch2-i18n` (screens in every language), `stitch2-copy` (writing the text: a brief, plain words, the reader's outcome), `stitch2-translate` (translating by meaning, with a glossary from the product, a review by error type and a check in context), `stitch2-app-sync` (keeping the app and the designs in step: design first, then the app) and,
87
88
  optional, `stitch2-import-from-google-stitch` (drafting screens in Google Stitch through its MCP server).
88
89
 
89
90
  ## Setup
@@ -253,7 +254,8 @@ layout first, then a shorter natural word, never a smaller type size.
253
254
  | `stitch2 i18n [filter] [--locale l] [--suggest] [--json]` | Message keys the source or a locale lacks, and screen text with no key |
254
255
  | `stitch2 consistency [kind…]` | The consistency report, `<design>/consistency/index.html` |
255
256
  | `stitch2 type [filter] [--json]` | The type audit |
256
- | `stitch2 screens [name] [--status s] [--json]` | Versions, statuses and notes |
257
+ | `stitch2 screens [name] [--status s] [--json] [--strict]` | Versions, statuses and notes; flags a screen whose current version is unclear |
258
+ | `stitch2 approve <path or filter…>` | Approve variants (the user's decision); every earlier version is archived |
257
259
  | `stitch2 tokens` | Tokens from DESIGN.md |
258
260
  | `stitch2 compare [screen…]` | The live app against the approved screens (`app` in the config) |
259
261
  | `stitch2 elements <entry>` | Bundle your components (`stitch2/elements`) for the screens |
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Approves screen variants, as the user does on the canvas: each becomes the current version of its screen on its
3
+ * device, and every earlier version there is archived. Agents run it when the user approves in words ("approve
4
+ * the v3 screens"), never on their own.
5
+ * Usage: stitch2 approve <path or filter…> (a path under the design folder, or part of one: "connect/", "-v3")
6
+ */
7
+ import { approve, listScreens } from './screens.js';
8
+ const filters = process.argv.slice(2).filter((a) => !a.startsWith('--'));
9
+ if (!filters.length) {
10
+ console.error('stitch2 approve <path or filter…>: which variants? ("home/mobile-v3", "connect/", "-v3")');
11
+ process.exit(1);
12
+ }
13
+ const targets = listScreens().filter((s) => s.kind === 'screen' && s.status !== 'archived' && filters.some((f) => s.path.includes(f)));
14
+ if (!targets.length) {
15
+ console.error(`No screen variant matches ${filters.join(', ')}.`);
16
+ process.exit(1);
17
+ }
18
+ // Oldest first, so approving several versions of one screen leaves the latest current.
19
+ targets.sort((a, b) => a.path.localeCompare(b.path, undefined, { numeric: true }));
20
+ for (const t of targets) {
21
+ const archived = approve(t.path);
22
+ const note = archived.length ? `; archived ${archived.map((s) => s.version).join(', ')}` : '';
23
+ console.log(`approved ${t.screen} ${t.version} ${t.device}${note}`);
24
+ }
package/dist/audit.js CHANGED
@@ -36,6 +36,9 @@ export async function audit(opts) {
36
36
  if (closed && !closed.querySelector(':scope > summary')?.contains(el))
37
37
  return false;
38
38
  const r = el.getBoundingClientRect();
39
+ // Text kept for screen readers only (Tailwind's sr-only: a 1px box, clipped away) is not drawn.
40
+ if (r.width <= 1 && r.height <= 1 && cs.position === 'absolute')
41
+ return false;
39
42
  return r.width > 0 && r.height > 0;
40
43
  };
41
44
  const opacity = (el) => {
package/dist/cli.js CHANGED
@@ -42,7 +42,8 @@ const COMMANDS = {
42
42
  'i18n-report.ts',
43
43
  '[filter] [--locale l] [--suggest] [--json] Keys screens use that a locale lacks, and screen text with no key',
44
44
  ],
45
- screens: ['list.ts', '[name] [--status s] [--json] Screen versions, statuses and notes'],
45
+ screens: ['list.ts', '[name] [--status s] [--json] [--strict] Screen versions, statuses, notes, and which version is current'],
46
+ approve: ['approve.ts', '<path or filter…> Approve variants for the user; earlier versions are archived'],
46
47
  localize: ['assets.ts', '[filter] Copy remote images a screen uses into its assets/ folder'],
47
48
  sandbox: [
48
49
  'sandbox.ts',
package/dist/list.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Lists every screen version, variant by variant (mobile, desktop), with its status and the reviewer's note, for agents (and anyone without the
3
3
  * canvas open). Approved versions are the reference; review versions wait for the user; notes are feedback.
4
- * Usage: stitch2 screens [screen name] [--status approved|review|draft|archived] [--json]
4
+ * Ends with what makes the current version unclear (versionProblems); --strict fails on it.
5
+ * Usage: stitch2 screens [screen name] [--status approved|review|draft|archived] [--json] [--strict]
5
6
  */
6
- import { listScreens } from './screens.js';
7
+ import { listScreens, versionProblems } from './screens.js';
7
8
  const args = process.argv.slice(2);
8
9
  const status = args.includes('--status') ? args[args.indexOf('--status') + 1] : undefined;
9
10
  const name = args.find((a, i) => !a.startsWith('--') && args[i - 1] !== '--status');
@@ -17,3 +18,11 @@ if (args.includes('--json'))
17
18
  else
18
19
  for (const s of rows)
19
20
  console.log(`${s.screen.padEnd(20)} ${s.version.padEnd(8)} ${s.device.padEnd(8)} ${s.status.padEnd(9)} ${s.path}${s.note ? `\n${' '.repeat(39)}note: ${s.note}` : ''}`);
21
+ const problems = versionProblems().filter((p) => !name || p.startsWith(`${name} `));
22
+ if (problems.length && !args.includes('--json')) {
23
+ console.log(`\nWhich version is current is unclear (${problems.length}):`);
24
+ for (const p of problems)
25
+ console.log(` ${p}`);
26
+ }
27
+ if (problems.length && args.includes('--strict'))
28
+ process.exit(1);
package/dist/screens.d.ts CHANGED
@@ -32,3 +32,17 @@ export declare const meta: (html: string, name: string) => string | undefined;
32
32
  export declare function listScreens(): Screen[];
33
33
  /** Sets (or removes, with undefined) <prefix>-* meta tags in a screen file, keeping the rest of it untouched. */
34
34
  export declare function writeMeta(path: string, values: Record<string, string | undefined>): void;
35
+ /**
36
+ * Approves a variant. Approval makes it the one current version of its screen on its device: every earlier
37
+ * version there (approved, in review or a draft) and any other approved one become archived, so nobody can mistake
38
+ * an older one for what is implemented. Later versions (a newer proposal) keep their status.
39
+ * Returns the variants it archived.
40
+ */
41
+ export declare function approve(path: string): Screen[];
42
+ /**
43
+ * What makes it unclear which version of a screen is current: two approved on one device, or an earlier version
44
+ * still in review or a draft next to an approved later one. One line each; empty when every screen is clear.
45
+ */
46
+ export declare function versionProblems(screens?: Screen[]): string[];
47
+ /** A version's number: 3 for "v3"; NaN for one without (a Stitch export, a trial). */
48
+ export declare const versionNumber: (s: Pick<Screen, 'version'>) => number;
package/dist/screens.js CHANGED
@@ -83,3 +83,52 @@ export function writeMeta(path, values) {
83
83
  }
84
84
  writeFileSync(file, html);
85
85
  }
86
+ /**
87
+ * Approves a variant. Approval makes it the one current version of its screen on its device: every earlier
88
+ * version there (approved, in review or a draft) and any other approved one become archived, so nobody can mistake
89
+ * an older one for what is implemented. Later versions (a newer proposal) keep their status.
90
+ * Returns the variants it archived.
91
+ */
92
+ export function approve(path) {
93
+ const all = listScreens();
94
+ const target = all.find((s) => s.path === path);
95
+ if (!target || target.kind !== 'screen')
96
+ throw new Error(`not a screen: ${path}`);
97
+ writeMeta(path, { [tag('status')]: 'approved' });
98
+ const n = versionNumber(target);
99
+ const outdated = all.filter((s) => s.path !== path &&
100
+ s.kind === 'screen' &&
101
+ s.screen === target.screen &&
102
+ s.device === target.device &&
103
+ s.status !== 'archived' &&
104
+ (s.status === 'approved' || versionNumber(s) < n));
105
+ for (const s of outdated)
106
+ writeMeta(s.path, { [tag('status')]: 'archived' });
107
+ return outdated;
108
+ }
109
+ /**
110
+ * What makes it unclear which version of a screen is current: two approved on one device, or an earlier version
111
+ * still in review or a draft next to an approved later one. One line each; empty when every screen is clear.
112
+ */
113
+ export function versionProblems(screens = listScreens()) {
114
+ const out = [];
115
+ const groups = new Map();
116
+ for (const s of screens) {
117
+ if (s.kind !== 'screen' || s.status === 'archived')
118
+ continue;
119
+ const key = `${s.screen} ${s.device}`;
120
+ groups.set(key, [...(groups.get(key) ?? []), s]);
121
+ }
122
+ for (const [key, list] of groups) {
123
+ const approved = list.filter((s) => s.status === 'approved');
124
+ if (approved.length > 1)
125
+ out.push(`${key}: ${approved.map((s) => s.version).join(' and ')} are all approved; approve the current one again`);
126
+ const current = Math.max(...approved.map(versionNumber).filter((n) => !Number.isNaN(n)));
127
+ for (const s of list)
128
+ if (s.status !== 'approved' && versionNumber(s) < current)
129
+ out.push(`${key}: ${s.version} is still ${s.status} although v${current} is approved; archive it (${s.path})`);
130
+ }
131
+ return out.sort();
132
+ }
133
+ /** A version's number: 3 for "v3"; NaN for one without (a Stitch export, a trial). */
134
+ export const versionNumber = (s) => Number(/^v(\d+)$/.exec(s.version)?.[1] ?? Number.NaN);
package/dist/serve.js CHANGED
@@ -28,7 +28,7 @@ function inlineTailwind(html, file) {
28
28
  return `<style type="text/tailwindcss">${readFileSync(css, 'utf8').replace(/\s*\n\s*/g, ' ')}</style>`;
29
29
  });
30
30
  }
31
- import { listScreens, ROOT, STATUSES, tag, writeMeta } from './screens.js';
31
+ import { listScreens, ROOT, STATUSES, tag, writeMeta, approve } from './screens.js';
32
32
  /**
33
33
  * The consistency report covers every screen that is not archived and the components; the app comparison covers
34
34
  * the approved screens. A report is stale once one of those files is newer than it (a status change counts: it
@@ -131,14 +131,9 @@ export function startServer(port) {
131
131
  if (note !== undefined)
132
132
  values[tag('note')] = note.trim() || undefined;
133
133
  writeMeta(path, values);
134
- // One approved version per screen and device: approving archives the previous one.
135
- if (status === 'approved')
136
- for (const s of all)
137
- if (s.path !== path &&
138
- s.screen === target.screen &&
139
- s.device === target.device &&
140
- s.status === 'approved')
141
- writeMeta(s.path, { [tag('status')]: 'archived' });
134
+ // One current version per screen and device: approving archives every earlier one (screens.ts).
135
+ if (status === 'approved' && target.kind === 'screen')
136
+ approve(path);
142
137
  json(listScreens());
143
138
  }
144
139
  catch (e) {
package/dist/typecheck.js CHANGED
@@ -30,6 +30,9 @@ export function collectText() {
30
30
  const r = el.getBoundingClientRect();
31
31
  if (!r.width || !r.height)
32
32
  continue;
33
+ // Text for screen readers only (Tailwind's sr-only: a 1px box, clipped away) is not drawn.
34
+ if (r.width <= 1 && r.height <= 1 && cs.position === 'absolute')
35
+ continue;
33
36
  let opacity = 1;
34
37
  for (let e = el; e; e = e.parentElement)
35
38
  opacity *= Number(getComputedStyle(e).opacity);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitch2",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "A design canvas, layout and type checks, component consistency and screen versioning for agent-built UI, on Google's DESIGN.md format.",
5
5
  "license": "MIT",
6
6
  "author": "ifokeev",
@@ -46,7 +46,8 @@ References, read before the first screen:
46
46
  variant). Assemble it from the components; plain markup only where none
47
47
  applies, with token classes, `.type-<level>` classes for text, the spacing scale, and the states DESIGN.md
48
48
  defines. When the config has `i18n`, mark every product string with its message key (`data-t`, see
49
- **stitch2-i18n**). Never add classes to a component to change how it looks, and never write a colour value (hex,
49
+ **stitch2-i18n**). Write the text as **stitch2-copy** says (the reader's outcome, plain words, the product's terms),
50
+ and translate it with **stitch2-translate**. Never add classes to a component to change how it looks, and never write a colour value (hex,
50
51
  `rgb()`, `hsl()`): only DESIGN.md's colours, as token classes or `var(--<prefix>-<name>)`.
51
52
  5. **Check** `stitch2 check <path> --shots --strict`. Fix every error (overflow, wrapped labels, content under
52
53
  fixed bars, fixed bars overlapping each other, tap targets, contrast, cramped text) and every warning about type (`type-*`), hand-built
@@ -65,7 +66,8 @@ References, read before the first screen:
65
66
  6. **Look and critique.** View the render in `<design>/renders/` (crop the first screenful). Score it with the
66
67
  rubric in principles.md, fix what fails, repeat 5–6 at most three times.
67
68
  7. **Hand over.** Set the version's status to `review`, point the user to it on the canvas, and report what
68
- changed, how you answered their notes, and any rubric item you could not satisfy. The user approves.
69
+ changed, how you answered their notes, and any rubric item you could not satisfy. The user approves; only then
70
+ does it go into the app (the approved version is the one current version: see screens.md).
69
71
 
70
72
  ## Commands
71
73
 
@@ -79,7 +81,8 @@ References, read before the first screen:
79
81
  | `i18n [filter] [--locale l] [--suggest]` | Message keys screens use that the source or a locale lacks, and text with no key (stitch2-i18n) |
80
82
  | `consistency [kind…]` | Every shared component cropped from every screen, next to the catalog's version |
81
83
  | `type [filter] [--json]` | Every text style a screen uses and the type level it matches |
82
- | `screens [name] [--status s] [--json]` | Every version with its status and the user's note |
84
+ | `screens [name] [--status s] [--json] [--strict]` | Every version with its status and the user's note, and anything that makes the current version unclear |
85
+ | `approve <path or filter…>` | Approves variants for the user (only on their word); every earlier version of the screen on that device is archived |
83
86
  | `compare [screen…]` | The live app against the approved versions: missing or extra components, order, looks; a side-by-side report |
84
87
  | `elements <entry>` | Bundles the app's components, turned into elements with `stitch2/elements`, for the screens |
85
88
  | `tokens` | `tokens.css` (colours, `.type-*` classes) and `tailwind.tokens.js` from DESIGN.md |
@@ -35,8 +35,13 @@ and build on it. Meta tag names below use the project's prefix (`<p>`, from `sti
35
35
  |---|---|---|
36
36
  | `draft` | Work in progress | The agent, while working |
37
37
  | `review` | Finished, waiting for the user ("Needs review") | The agent, when done |
38
- | `approved` | The reference: build on it, match it, implement it | The user only |
39
- | `archived` | History: superseded or rejected; hidden on the canvas | The user; approving a variant archives the previously approved variant of the same device |
38
+ | `approved` | The current version: the reference to build on and match, and the only one the app implements | The user only (on the canvas, or in words: then the agent runs `stitch2 approve`) |
39
+ | `archived` | Outdated: superseded or rejected; hidden on the canvas | Approval: approving a variant archives every earlier version of that screen on that device, whatever its status |
40
+
41
+ **One current version.** For each screen and device there is at most one approved variant, and it is what the app
42
+ shows. Approving one archives every earlier version there (approved, in review or a draft), so nothing older can be
43
+ mistaken for the current one; a later version, a newer proposal, keeps its status. `stitch2 screens` ends with
44
+ anything that breaks this (two approved, an older version still in review); `--strict` fails on it.
40
45
 
41
46
  The canvas shows each version with its variants side by side under one heading, and one status for the version:
42
47
  needs review while any variant does. **Approve version** approves every variant in review at once; a single
@@ -47,8 +52,12 @@ variant can still be approved, archived or noted on its own.
47
52
  1. Before designing, run `stitch2 screens` (a name or `--status approved` narrows it) and open the approved
48
53
  versions of the screens next to yours.
49
54
  2. A note is the user's feedback: address every point in your next version or edit, and say how.
50
- 3. Never set `approved` yourself; never build on an archived version unless asked.
51
- 4. Only approved versions get implemented in the product.
55
+ 3. Never approve on your own and never build on an archived version unless asked. When the user approves in words
56
+ ("approve the v3 screens"), run `stitch2 approve <path or filter…>` rather than editing status tags: it archives the
57
+ earlier versions as the canvas does.
58
+ 4. **Implement only approved versions.** A version in review or a draft is not built in the app, not even partly. If
59
+ the user asks to implement one that is not approved yet, that request is their approval: run `stitch2 approve` on it
60
+ first and say so. If the app needs something no approved version shows, design it and hand it over first.
52
61
  5. When the user pastes a reference copied from the canvas, such as `home mobile v2
53
62
  (design/screens/home/mobile-v2.html)`, open that file: it names exactly the version they mean.
54
63
  6. A reference to an element adds its source line, its tag and its text: `home mobile v1
@@ -59,7 +68,7 @@ variant can still be approved, archived or noted on its own.
59
68
 
60
69
  ## Implementing an approved version
61
70
 
62
- Build the screen in the app from the same components the screens use, in the version's order, with the app's
71
+ Check first with `stitch2 screens <screen>` that the version is approved and is the only approved one on its device. Build the screen in the app from the same components the screens use, in the version's order, with the app's
63
72
  own data, strings and behaviour; keep the app's tests passing. Then run `stitch2 compare <screen>` with the app
64
73
  running: fix every `missing` component and look at the side-by-side report. `extra` and `order` warnings are
65
74
  fine when the app needs them (a desktop-only control, a frame drawn by layout); say so when you hand over. When
@@ -19,7 +19,10 @@ a component rebuilt by hand. The checks can only judge what the canvas draws, so
19
19
  approved screen if the change is visible there; a catalog entry for a new variant or state), and run
20
20
  `stitch2 check <screens> --strict`. Look at the screens yourself: the check finds overlaps and overflow, not
21
21
  ugliness.
22
- 3. **Approval**: the user approves the new versions (or the catalog change) as for any design.
22
+ 3. **Approval**: the user approves the new versions (or the catalog change) as for any design. Nothing goes into
23
+ the app before this: a version in review is not implemented, not even partly. When the user asks to implement
24
+ one in review, run `stitch2 approve` on it first; that archives the earlier versions, so the approved one is the
25
+ single current version and the app matches it.
23
26
  4. **App**: the app picks the component up; wire it, then run `stitch2 compare <screen>`.
24
27
 
25
28
  Fixing a bug found in the app follows the same path: reproduce it on the canvas (a screen or catalog state that
@@ -62,7 +65,8 @@ Read the findings as:
62
65
 
63
66
  ## Before you call it done
64
67
 
65
- 1. `stitch2 check --strict` passes on the screens you touched (archived screens excepted).
66
- 2. `stitch2 compare` has no errors for the screens you touched; warnings are explained (a kept feature) or fixed.
67
- 3. The user has seen the canvas versions of anything that changed how a component looks.
68
+ 1. Every screen you implemented is approved, and `stitch2 screens --strict` passes: one current version each.
69
+ 2. `stitch2 check --strict` passes on the screens you touched (archived screens excepted).
70
+ 3. `stitch2 compare` has no errors for the screens you touched; warnings are explained (a kept feature) or fixed.
71
+ 4. The user has seen the canvas versions of anything that changed how a component looks.
68
72
 
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: stitch2-copy
3
+ description: Write the words on screens and landing pages so they say what the product does for the reader, in plain language a translator can carry into any language. Use when writing or rewriting a screen's text, a landing page, onboarding, empty states or buttons, when the user says the copy is weak, generic, too technical or "not selling", and before translating (stitch2-translate starts from this source).
4
+ ---
5
+
6
+ # Copy with stitch2
7
+
8
+ The words are part of the design. A screen can pass every layout check and still fail because its text is vague,
9
+ written for the wrong reader, or impossible to translate well. This skill writes the **source** text; translating
10
+ it is stitch2-translate.
11
+
12
+ ## 1. The brief: design/COPY.md
13
+
14
+ Write it once per product, before the text, and keep it next to DESIGN.md. It is short:
15
+
16
+ - **Reader.** Who opens this page and what they want done today, in their words ("know what to lift and log it
17
+ without fuss"), not in the product's ("a training management platform"). A page has one main reader; others
18
+ (developers, self-hosters, partners) get their own section further down, written for them.
19
+ - **Promise.** One sentence: what the product does for that reader. Every headline on the page serves it.
20
+ - **Proof.** Real, specific facts that back the promise: numbers, names, what happens on screen. No round fake
21
+ numbers.
22
+ - **Voice.** Three words for how it sounds, and what it is not ("calm, direct, practical; never hype, never
23
+ slang").
24
+ - **Words.** The product's terms, one per concept, as the UI uses them (*set*, *routine*, *program*), and the words
25
+ it never uses. stitch2-translate turns this into a glossary per language.
26
+ - **Section intents.** For a landing page, one line per section: what the reader should understand or do after
27
+ it. The intent travels with the strings to the translators.
28
+
29
+ ## 2. Writing rules
30
+
31
+ Sources: Google developer style guide (*Write for a global audience*), Nielsen Norman Group (people scan: 79% of
32
+ users scan, 16% read word by word), Mozilla's marketing style guides, Material and Apple writing guidance.
33
+
34
+ - **Lead with the reader's outcome**, then how the product gets there. "Know what to lift today" before "a
35
+ progression engine".
36
+ - **Concrete beats clever.** Name the thing on the screen ("last time's weight sits next to every set") instead of
37
+ an abstraction ("data-driven insights"). A headline should still make sense with the page's design removed.
38
+ - **One idea per sentence; short sentences.** They scan better and translate better.
39
+ - **Plain words.** *use*, not *leverage*; *start*, not *commence*. No idioms, puns, phrasal verbs or cultural
40
+ references in the source: the translator has to rebuild them, and usually cannot.
41
+ - **Second person, active voice, present tense.** "You log a set", "gymgym fills in the weight".
42
+ - **Sentence case** for headings and buttons. A button is a verb and its object ("Start training", "Send
43
+ answers").
44
+ - **No hype.** Not *seamless, powerful, ultimate, revolutionary, effortless, journey, unlock, supercharge*; no
45
+ exclamation marks. Proof replaces adjectives.
46
+ - **Technical words only for the reader who uses them.** Licences, hosting, protocols and stack belong in the
47
+ section for developers, and even there say what they mean for the reader ("your data stays in your database")
48
+ before naming the technology.
49
+ - **Write for translation.** Keep each sentence whole in one string (never split a sentence across keys or build
50
+ it by concatenation); use placeholders for numbers and names, with plural forms; avoid words with two meanings
51
+ in context (*set* the noun vs the verb). See stitch2-i18n for keys and placeholders.
52
+
53
+ ### A landing page
54
+
55
+ - **Hero**: an eyebrow (what it is, or its strongest fact), a title of up to about eight words with the outcome,
56
+ a lead of one or two sentences with how and why to trust it, one primary action and at most one secondary.
57
+ - **Sections**: eyebrow = the topic (one or two words); title = the benefit; lead = how it works, in product
58
+ terms. Feature cards: title = what the reader gets, text = what happens on screen.
59
+ - **Lists** (checklists, "also included"): parallel grammar, each item a short claim the reader can check.
60
+ - **Calls to action** repeat the same verb for the same action across the page.
61
+
62
+ ### In the product
63
+
64
+ Labels are nouns, buttons are verbs, hints say what happens next, empty states say what this place is for and
65
+ offer the first action, errors say what happened and what to do. Reuse the app's existing words before inventing
66
+ new ones.
67
+
68
+ ## 3. Process
69
+
70
+ 1. Write or update the brief.
71
+ 2. **Read the strings in order.** `stitch2 i18n <screen> --json` lists a screen's keys; read the source text top
72
+ to bottom as one story, without the design. Mark every sentence that is vague, technical for the reader,
73
+ repeats another, or would not survive translation.
74
+ 3. Rewrite in the source messages (the app's files: one copy of every string). Keep keys whose meaning stays;
75
+ a string whose meaning changes gets its new text under the same key only if every place that uses it wants
76
+ the new meaning.
77
+ 4. Self-review with the checklist below, then `stitch2 check <screen> --shots` for fit.
78
+ 5. **Cold read.** Give a fresh agent only the page's strings (no brief, no design) and ask: what is this product,
79
+ who is it for, what would you do next? If the answer misses the promise, the page does not say it yet. A
80
+ `stitch2 sandbox` folder keeps the fresh agent from seeing your reasoning.
81
+ 6. Then translate with stitch2-translate: the brief's section intents are its context.
82
+
83
+ ## Checklist
84
+
85
+ - [ ] The hero says what the product does for the main reader, in their words.
86
+ - [ ] Every section title is a benefit; every lead says how, concretely.
87
+ - [ ] No hype words, no exclamation marks, no idioms or puns.
88
+ - [ ] Technical terms only in the section for the readers who use them.
89
+ - [ ] One term per concept, the same as the UI.
90
+ - [ ] Sentences short, whole in one string, with placeholders for numbers.
91
+ - [ ] Buttons are verb + object, the same verb for the same action.
92
+ - [ ] The cold read names the promise.
93
+
@@ -110,6 +110,12 @@ Use logical properties so layouts flip by themselves: `ms-*`/`me-*`, `ps-*`/`pe-
110
110
  `text-start`, `rounded-s-*`. Icons that point (back, next, chevrons, progress) get `rtl:-scale-x-100`;
111
111
  icons that show objects (a dumbbell, a clock) and numbers, charts and media controls do not flip.
112
112
 
113
+ ## Writing and translating the words
114
+
115
+ This skill makes every language fit. What the text says, and how well each language says it, is
116
+ **stitch2-copy** (the source) and **stitch2-translate** (meaning, not words: a glossary from the app, the
117
+ language's conventions, a review pass by error type, then this skill's check in context).
118
+
113
119
  ## Hand over
114
120
 
115
121
  Tell the user which languages you checked, what you changed to make each fit (layout, word, component), and
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: stitch2-translate
3
+ description: Translate a product's screens and landing pages by meaning, as a native writer of each language would put it, not word for word — with a glossary taken from the product, the language's own conventions, a review pass that marks errors by type and severity, and a check in context. Use when adding or updating translations, when a language reads like a translation (calques, wrong terms, odd tone), when the user complains about a language, or after stitch2-copy changes the source.
4
+ ---
5
+
6
+ # Translating with stitch2
7
+
8
+ A good translation says what the source *means* and has the effect it is meant to have, in words a native
9
+ speaker would choose. "Avoid literal translation at all costs" (Mozilla's localization style guide). For product
10
+ UI that means the product's own terms, every time; for marketing it means transcreation: the headline may change
11
+ its words entirely to keep its point.
12
+
13
+ Machine and model translation in one pass is fluent but literal. It does better when it prepares like a human
14
+ translator does and then revises against named errors: preparing keywords, topic and examples first (MAPS, He et
15
+ al. 2023, arXiv:2305.04118), translating, then estimating errors and refining (TEaR, Feng et al. 2024,
16
+ arXiv:2402.16379), with errors marked by MQM type and severity (GEMBA-MQM, Kocmi & Federmann 2023,
17
+ arXiv:2310.13988), and with separate roles for translator, localizer and reviewer (TransAgents, Wu et al. 2024,
18
+ arXiv:2405.11804). This skill is that process.
19
+
20
+ ## 0. Before translating
21
+
22
+ - **The source is good.** If the source is vague or full of idioms, fix it first (stitch2-copy): a translation
23
+ cannot be better than what it says.
24
+ - **The brief** (design/COPY.md): reader, promise, voice, section intents. Every string is translated knowing
25
+ what it is for.
26
+
27
+ ## 1. Prepare, per language
28
+
29
+ 1. **Glossary from the product.** Before writing a word, read how the app already says the core concepts in this
30
+ language (the locale's messages: set, rep, rest, workout, routine, program, history…; search the message files
31
+ for the source terms). Write them into design/COPY.md under the language. The landing page uses the app's
32
+ words; when the app's own terms collide (one word for two concepts), fix the collision in the app first and
33
+ note the decision.
34
+ 2. **Language conventions** (references/locales.md): form of address used by the app (detect it from existing
35
+ strings: *du*/*Sie*, *tu*/*vous*, *ты*/*вы*), quotation marks, spaces before punctuation, dashes, number
36
+ formatting, capitalization.
37
+ 3. **Context per string.** Where it appears (hero title, button, chip, checklist item), how much room it has
38
+ (`stitch2 i18n <screen> --json` lists keys per screen; the renders show the space), and the intent from the
39
+ brief.
40
+
41
+ ## 2. Draft as a native writer
42
+
43
+ - Read the whole section in the source first; translate the section, not string by string.
44
+ - Ask, as Mozilla's guide does: what does this mean, what is the author trying to say, how would I say that in my
45
+ language? Then write it the way a good local fitness, finance or tech site (whatever the product is) would.
46
+ - Headlines and slogans: transcreate. Keep the point and the length class (short stays short), drop the source's
47
+ wording when it does not work. Write two or three options and keep the best.
48
+ - UI strings: the glossary term, the language's usual verb for the action ("Войти", "Anmelden"), the same form of
49
+ address everywhere.
50
+ - Keep placeholders, plural categories (CLDR) and markup slots; never concatenate.
51
+ - Units, numbers and dates come from the locale; product names, brands and code stay as written.
52
+
53
+ ## 3. Review: mark errors, then fix
54
+
55
+ Review as a **separate pass**, ideally a separate agent that sees the source, the draft, the glossary and the
56
+ conventions, but not the drafter's reasoning. Mark every problem with an MQM category and severity
57
+ (references/review.md), for example:
58
+
59
+ ```
60
+ site.hero.title ru "Знайте, что поднимать." fluency/unnatural (calque of "Know what to lift") major
61
+ ```
62
+
63
+ The questions that catch most errors:
64
+
65
+ - Would a native copywriter ever write this sentence? If it only makes sense to someone who knows the English, it
66
+ is a calque.
67
+ - Does it say exactly what the source means, nothing added, nothing lost?
68
+ - Is every product concept the glossary term? Is the form of address the app's?
69
+ - Punctuation, quotes, spaces and numbers as the language writes them?
70
+
71
+ Fix every critical and major error and review again, until a pass finds none. A back-translation can confirm the
72
+ meaning; it is never a target for style.
73
+
74
+ ## 4. In context
75
+
76
+ 1. `stitch2 check <screen> --locales <l> --shots`: nothing cut, wrapped badly or overlapping (stitch2-i18n has the
77
+ fixes, in order).
78
+ 2. Read the render top to bottom in the language. Headlines that broke onto an orphan word, a term that changed
79
+ between two sections, a tone that changed: fix in the messages.
80
+
81
+ ## 5. Hand over
82
+
83
+ Say which languages you reviewed in context, the terminology decisions (with the collisions you fixed), and which
84
+ strings deserve a native speaker's read before a launch: headlines and anything legal. Be honest about the
85
+ languages you are less sure of.
86
+
87
+ ## References
88
+
89
+ - references/locales.md: conventions per language.
90
+ - references/review.md: the error types and severities, and the review output.
91
+
@@ -0,0 +1,40 @@
1
+ # Conventions per language
2
+
3
+ The defaults for consumer products; when the product already uses another form of address, keep the product's.
4
+ Numbers, dates and units come from `Intl` in the app; in prose, write them the way the language does.
5
+
6
+ | Language | Address | Quotes | Punctuation and spacing | Numbers | Notes |
7
+ |---|---|---|---|---|---|
8
+ | English (en) | you | “ ” | sentence case; serial comma by house style | 1,324.5 | the source: plain, no idioms |
9
+ | German (de) | du in consumer apps, Sie in banking and B2B | „ “ | nouns capitalised; compounds closed (Pausentimer) | 1.324,5 | avoid Denglisch where a German word is usual; keep accepted loanwords (Coach, Workout) |
10
+ | French (fr) | tu or vous by product; be consistent | « » with narrow no-break spaces | narrow no-break space before ; : ! ? and inside « » | 1 324,5 | sentence case; avoid anglicismes (séance, série, répétition) |
11
+ | Spanish (es) | tú | « » or “ ” | ¿ ¡ open questions and exclamations | 1324,5 / 1.324,5 | neutral Spanish for Spain and Latin America; avoid regional slang |
12
+ | Italian (it) | tu | « » or “ ” | sentence case | 1.324,5 | allenamento, serie, ripetizioni, recupero |
13
+ | Portuguese, Portugal (pt) | tu (or você) by product | « » or “ ” | sentence case | 1324,5 | ginásio, ecrã, telemóvel, registar — never the Brazilian forms |
14
+ | Portuguese, Brazil (pt-BR) | você | “ ” | sentence case | 1.324,5 | academia, tela, celular, registrar |
15
+ | Polish (pl) | ty (imperative 2nd person singular) | „ ” | sentence case | 1324,5 | seria, powtórzenia, przerwa; avoid noun chains |
16
+ | Turkish (tr) | sen (or siz) by product | “ ” | sentence case; suffixes after numbers and names with an apostrophe (Strong’dan) | 1.324,5 | vowel harmony in suffixes after placeholders needs care: prefer phrasing without a suffix on {name} |
17
+ | Russian (ru) | вы (lowercase) | «ёлочки», „лапки“ inside | spaced em dash —; no Title Case; ё where it matters | 1 324,5 (no-break space) | lifter vocabulary: подход, повторение (повтор), отдых, разминка, жим лёжа; no calques (see below) |
18
+ | Ukrainian (uk) | ви | « » | apostrophe ’ inside words (м’язи) | 1 324,5 | not Russian with Ukrainian letters: own vocabulary (підхід, повторення, відпочинок, тренування) |
19
+ | Hungarian (hu) | te in consumer apps | „ ” | sentence case | 1 324,5 | word order differs a lot: rebuild the sentence |
20
+ | Arabic (ar) | neutral second person, masculine as default where unavoidable | « » or ” “ | Arabic comma ، and question mark ؟; right to left | Western digits by product choice | Modern Standard Arabic, plain; mirror arrows |
21
+ | Hindi (hi) | आप | “ ” | । as full stop | 1,324.5 (Indian grouping for large numbers) | gym words are often English in Devanagari (सेट, रेप्स, वर्कआउट): use what Indian fitness apps use |
22
+ | Thai (th) | neutral, no polite particles in UI | “ ” | no spaces between words; a space ends a phrase | 1,324.5 | short; avoid long noun phrases |
23
+ | Korean (ko) | 해요체 (polite informal) in consumer apps | “ ” | spaces between words (띄어쓰기) | 1,324.5 | consistent ending style across the product |
24
+ | Chinese, Simplified (zh) | 你 | “ ” | full-width punctuation ,。:;?! | 1,324.5 | a space between Chinese and Latin letters or numbers is house style: keep the product's |
25
+
26
+ ## Calques to catch (Russian examples; every language has its own)
27
+
28
+ | Source | Literal (wrong) | Natural |
29
+ |---|---|---|
30
+ | Know what to lift. | Знайте, что поднимать. | Всегда знаете, какой вес брать. |
31
+ | Keep every rep. | Сохраните каждое повторение. | Ни один подход не потеряется. |
32
+ | A coach that reads your training. | Тренер, который читает ваши тренировки. | Тренер, который знает ваши тренировки. |
33
+ | Get gymgym | Получить gymgym | Как начать |
34
+ | Use ours, or run your own. | Пользуйтесь нашим или запустите свой. | Пользуйтесь нашим сервером или разверните свой. |
35
+ | at a glance | с первого взгляда | всё на одном экране |
36
+
37
+ Signs of a calque: the sentence keeps English word order; a possessive on every noun (ваши, ваш, вашего); a verb
38
+ that only makes sense in English (*read* training, *keep* reps); nouns where the language prefers a verb; an
39
+ English idiom kept word for word.
40
+
@@ -0,0 +1,30 @@
1
+ # Review: error types and severity
2
+
3
+ Based on the MQM (Multidimensional Quality Metrics) typology, cut down to what product text needs. Mark each
4
+ error on the string it is in.
5
+
6
+ | Category | Subtypes | Example |
7
+ |---|---|---|
8
+ | Accuracy | mistranslation, omission, addition, untranslated | "rest timer" as "остаток времени" |
9
+ | Fluency | grammar, spelling, unnatural (calque), inconsistency within the string | "Знайте, что поднимать" |
10
+ | Terminology | not the glossary term, two terms for one concept | routine as "программа" here and "тренировка" there |
11
+ | Style | register or form of address unlike the product, tone unlike the brief, wordy | "вы" in a "ты" product; marketing hype |
12
+ | Locale conventions | punctuation, quotes, spacing, numbers, dates, capitalization | "Hello : world" spacing in fr, Title Case in ru |
13
+ | Design | does not fit, breaks a line badly, wrong plural form | (stitch2 check finds most) |
14
+
15
+ Severity:
16
+
17
+ - **critical**: says something false or harmful (wrong price, wrong action on a button, legal or medical
18
+ mistake), or unreadable.
19
+ - **major**: a reader notices: unnatural, wrong term, wrong tone, meaning shifted.
20
+ - **minor**: a careful reader notices: punctuation, a slightly better word exists.
21
+
22
+ Output, one line per error, then a verdict:
23
+
24
+ ```
25
+ <key> <locale> "<text>" <category>/<subtype> <severity> <why, and the fix>
26
+ verdict: <locale> n critical, n major, n minor
27
+ ```
28
+
29
+ A language ships when its last pass has no critical or major errors.
30
+