@formicoidea/labre-framework-wardley 0.39.3 → 0.41.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/dist/actions.d.ts CHANGED
@@ -4,6 +4,35 @@ import type { BlockStdScope } from '@formicoidea/labre-core/std';
4
4
  import { type GfxController } from '@formicoidea/labre-core/std/gfx';
5
5
  import { type WardleyExportBoard } from './export.js';
6
6
  import { type WardleyAreaShape, type WardleyArtefactKind } from './presets.js';
7
+ /**
8
+ * Standalone creation/activation actions for the Wardley toolbox — the
9
+ * BEHAVIOUR layer, shared by every surface. They only depend on the
10
+ * {@link GfxController}.
11
+ *
12
+ * They no longer emit telemetry: since PF3 the single emission point is the
13
+ * command registry's `runCommand` (`docs/adr/0008`), which is the only function
14
+ * every surface goes through. `WardleyActionSource` is gone with it — the
15
+ * segment/module discrimination it carried is now `CommandInvocation.surface`.
16
+ */
17
+ /**
18
+ * The three axis words a translated variant's background is seeded with —
19
+ * declared once, so the resolver below and the manifest
20
+ * (`../translations.ts`) read the very same fallback.
21
+ */
22
+ export declare const WARDLEY_AXIS_SEED: {
23
+ readonly opportunity: {
24
+ readonly key: "com.labre.wardley.seed.axis-opportunity";
25
+ readonly fallback: "Opportunity";
26
+ };
27
+ readonly benefit: {
28
+ readonly key: "com.labre.wardley.seed.axis-benefit";
29
+ readonly fallback: "Benefit";
30
+ };
31
+ readonly investment: {
32
+ readonly key: "com.labre.wardley.seed.axis-investment";
33
+ readonly fallback: "Investment";
34
+ };
35
+ };
7
36
  /**
8
37
  * The single-circle node flavours: one connectable ellipse + a label to its
9
38
  * right, grouped. The glyph itself (anchor silhouette, ecosystem hatching,
package/dist/actions.js CHANGED
@@ -11,7 +11,7 @@ import { WARDLEY_SVG_IMPORT } from './interchange.js';
11
11
  import { wardleyBoardFrom, wardleySafeFilename, } from './export.js';
12
12
  import { WARDLEY_OWM_EXPORT, WARDLEY_OWM_IMPORT } from './interchange.js';
13
13
  import { HANDLE_SIZE, INERTIA_SIZE, LABEL_GAP, LINK_GREY, LINK_STROKE_WIDTH, PORTER_DEFAULT_LETTER, WARDLEY_RED, } from './node/consts.js';
14
- import { wardleyAreaBox, wardleyAreaProps, wardleyCanonicalBox, wardleyHandleBox, wardleyHandleProps, wardleyInertiaProps, WARDLEY_LABEL_H, WARDLEY_LABEL_W, wardleyLabelProps, wardleyMarketDotBoxes, wardleyMarketDotProps, wardleyMarketLinkPairs, wardleyMarketLinkProps, WARDLEY_NODE_LABEL, WARDLEY_NODE_SIZE, wardleyNodeProps, wardleyPorterArrowProps, wardleyPorterArrows, wardleyPorterLetterProps, } from './presets.js';
14
+ import { wardleyAreaBox, wardleyAreaProps, wardleyCanonicalBox, wardleyHandleBox, wardleyHandleProps, wardleyInertiaProps, WARDLEY_LABEL_H, WARDLEY_LABEL_W, wardleyLabelProps, wardleyMarketDotBoxes, wardleyMarketDotProps, wardleyMarketLinkPairs, wardleyMarketLinkProps, WARDLEY_NODE_LABEL, wardleyNodeLabelKey, WARDLEY_NODE_SIZE, wardleyNodeProps, wardleyPorterArrowProps, wardleyPorterArrows, wardleyPorterLetterProps, } from './presets.js';
15
15
  import { WARDLEY_ROLE } from './roles.js';
16
16
  /**
17
17
  * Standalone creation/activation actions for the Wardley toolbox — the
@@ -24,44 +24,62 @@ import { WARDLEY_ROLE } from './roles.js';
24
24
  * segment/module discrimination it carried is now `CommandInvocation.surface`.
25
25
  */
26
26
  /**
27
- * Per-variant default label overrides applied at creation (all remain editable
28
- * afterwards via the inline editor / toggles). The gradient itself is driven by
29
- * `variant` in the renderer.
30
- *
31
- * TODO(PF2 follow-up): these write ENGLISH PROSE into the document. Since PF2
32
- * the ten label props default to `undefined` precisely so the declaration's
33
- * i18n keys are reachable — but a map created as `opportunity` or `benefit`
34
- * lands with "Opportunity" / "Benefit" / "Investment" already persisted as if
35
- * the user had typed them, so those three are un-localisable for the life of
36
- * the document. Only `classic` and `evolution-gradient` are fully localisable
37
- * today.
38
- *
39
- * The fix is to make the variant part of the declaration — one axis/end-label
40
- * set per variant, each naming its own `labelKey` — rather than a bag of prop
41
- * overrides applied at creation. Out of scope here (it changes what a variant
42
- * IS). This is now the ONLY copy: the four background templates are derived
43
- * from these four commands, so the table goes away in one place.
27
+ * The three axis words a translated variant's background is seeded with —
28
+ * declared once, so the resolver below and the manifest
29
+ * (`../translations.ts`) read the very same fallback.
44
30
  */
45
- const BACKGROUND_VARIANT_DEFAULTS = {
46
- classic: {},
47
- // The Y axis becomes "Opportunity"; phase labels keep the classic defaults.
31
+ export const WARDLEY_AXIS_SEED = {
48
32
  opportunity: {
49
- yAxisTitle: 'Opportunity',
50
- showVisibilityLabels: false,
51
- showCornerLabels: false,
33
+ key: 'com.labre.wardley.seed.axis-opportunity',
34
+ fallback: 'Opportunity',
52
35
  },
53
- // The Y axis splits into Benefit (top) / Investment (bottom) around a zero
54
- // line drawn by the renderer.
55
36
  benefit: {
56
- yAxisTitle: '',
57
- visibilityHigh: 'Benefit',
58
- visibilityLow: 'Investment',
59
- showCornerLabels: false,
37
+ key: 'com.labre.wardley.seed.axis-benefit',
38
+ fallback: 'Benefit',
39
+ },
40
+ investment: {
41
+ key: 'com.labre.wardley.seed.axis-investment',
42
+ fallback: 'Investment',
60
43
  },
61
- // Keeps the classic labels (Value Chain / Uncharted / Industrialized…); only
62
- // the grey gradient differs.
63
- 'evolution-gradient': {},
64
44
  };
45
+ /**
46
+ * Per-variant default label overrides applied at creation (all remain editable
47
+ * afterwards via the inline editor / toggles). The gradient itself is driven by
48
+ * `variant` in the renderer.
49
+ *
50
+ * Resolved through the translation seam since the i18n pass (#278): the ten
51
+ * label props still default to `undefined` on the declaration itself, but
52
+ * `opportunity` and `benefit` are the two variants whose Y axis is PROSE the
53
+ * gesture bakes into the document at creation — `WARDLEY_AXIS_SEED` above is
54
+ * what a host's catalogue is offered for the three words, resolved HERE, once,
55
+ * exactly like every other seed in this file. `classic` and
56
+ * `evolution-gradient` keep the classic labels (Value Chain / Uncharted /
57
+ * Industrialized…), which are declaration `labelKey`s and were already fully
58
+ * localisable — this function names no override for them.
59
+ */
60
+ function backgroundVariantDefaults(gfx) {
61
+ const axis = (def) => translateKey(gfx.std, def.key, def.fallback);
62
+ return {
63
+ classic: {},
64
+ // The Y axis becomes "Opportunity"; phase labels keep the classic defaults.
65
+ opportunity: {
66
+ yAxisTitle: axis(WARDLEY_AXIS_SEED.opportunity),
67
+ showVisibilityLabels: false,
68
+ showCornerLabels: false,
69
+ },
70
+ // The Y axis splits into Benefit (top) / Investment (bottom) around a zero
71
+ // line drawn by the renderer.
72
+ benefit: {
73
+ yAxisTitle: '',
74
+ visibilityHigh: axis(WARDLEY_AXIS_SEED.benefit),
75
+ visibilityLow: axis(WARDLEY_AXIS_SEED.investment),
76
+ showCornerLabels: false,
77
+ },
78
+ // Keeps the classic labels (Value Chain / Uncharted / Industrialized…); only
79
+ // the grey gradient differs.
80
+ 'evolution-gradient': {},
81
+ };
82
+ }
65
83
  // The label box, now owned by `presets.ts` — the local names stay so the four
66
84
  // placements below keep reading as they did.
67
85
  const LABEL_H = WARDLEY_LABEL_H;
@@ -128,7 +146,7 @@ export function createWardleyBackground(gfx, variant = 'classic') {
128
146
  // declaration decides, and the toolbar toggle takes over from there.
129
147
  resizeEnabled: WARDLEY_BACKGROUND.geometry.resizable,
130
148
  variant,
131
- ...BACKGROUND_VARIANT_DEFAULTS[variant],
149
+ ...backgroundVariantDefaults(gfx)[variant],
132
150
  xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
133
151
  });
134
152
  finish(gfx, id);
@@ -145,7 +163,12 @@ export function createWardleyNode(gfx, kind) {
145
163
  const { w } = WARDLEY_NODE_SIZE[kind];
146
164
  const { centerX: cx, centerY: cy } = gfx.viewport;
147
165
  const nodeId = addNode(surface, kind, cx, cy);
148
- const labelId = addLabel(surface, WARDLEY_NODE_LABEL[kind], cx + w / 2 + LABEL_GAP, cy - LABEL_H / 2);
166
+ const labelId = addLabel(surface,
167
+ // Resolved HERE, once — the prompt a node nobody has named still carries,
168
+ // exactly like a BPMN node's caption. `gfx.std` is how this file reaches
169
+ // the translation seam: these creation sites take `GfxController`, not
170
+ // `BlockStdScope`, directly (`WardleyView` reads the same member).
171
+ translateKey(gfx.std, wardleyNodeLabelKey(kind), WARDLEY_NODE_LABEL[kind]), cx + w / 2 + LABEL_GAP, cy - LABEL_H / 2);
149
172
  finish(gfx, group(gfx, [nodeId, labelId]));
150
173
  }
151
174
  /** Create an inertia bar (filled black rect). */
@@ -178,7 +201,7 @@ export function createWardleyPipeline(gfx) {
178
201
  // `centerAnchorOnly` so connectors attach to its center only.
179
202
  const handleId = gfx.surface.addElement(wardleyHandleProps({ xywh: wardleyHandleBox(cx, cy) }));
180
203
  // Label centered horizontally on the pipeline, sitting ABOVE the handle.
181
- const labelId = addLabel(gfx.surface, WARDLEY_NODE_LABEL.pipeline, cx - 60, top - d / 2 - LABEL_H - LABEL_GAP, 'center');
204
+ const labelId = addLabel(gfx.surface, translateKey(gfx.std, wardleyNodeLabelKey('pipeline'), WARDLEY_NODE_LABEL.pipeline), cx - 60, top - d / 2 - LABEL_H - LABEL_GAP, 'center');
182
205
  // Nested groups: (handle + label), then (body + that group).
183
206
  const innerId = group(gfx, [handleId, labelId]);
184
207
  finish(gfx, group(gfx, [bodyId, innerId]));
@@ -202,7 +225,7 @@ export function createWardleyMarket(gfx) {
202
225
  const dotIds = wardleyMarketDotBoxes(cx, cy).map(xywh => surface.addElement(wardleyMarketDotProps({ xywh })));
203
226
  // Triangle: 3 attached connectors (auto-route center-to-center, clipped).
204
227
  const connIds = wardleyMarketLinkPairs(dotIds).map(([a, b]) => surface.addElement(wardleyMarketLinkProps(a, b)));
205
- const labelId = addLabel(surface, WARDLEY_NODE_LABEL.market, cx + R + LABEL_GAP, cy - LABEL_H / 2);
228
+ const labelId = addLabel(surface, translateKey(gfx.std, wardleyNodeLabelKey('market'), WARDLEY_NODE_LABEL.market), cx + R + LABEL_GAP, cy - LABEL_H / 2);
206
229
  finish(gfx, group(gfx, [circleId, ...dotIds, ...connIds, labelId]));
207
230
  }
208
231
  /**
@@ -263,7 +286,7 @@ export function createWardleyAccelerator(gfx, kind) {
263
286
  const { centerX: cx, centerY: cy } = gfx.viewport;
264
287
  const nodeId = addNode(surface, kind, cx, cy);
265
288
  const rightwards = kind === 'accelerator';
266
- const labelId = addLabel(surface, WARDLEY_NODE_LABEL[kind],
289
+ const labelId = addLabel(surface, translateKey(gfx.std, wardleyNodeLabelKey(kind), WARDLEY_NODE_LABEL[kind]),
267
290
  // A label box is a fixed LABEL_W wide whatever it reads, so a right-aligned
268
291
  // one has to start a box-width before the edge the words must end on.
269
292
  rightwards ? cx + w / 2 + LABEL_GAP : cx - w / 2 - LABEL_GAP - LABEL_W, cy - LABEL_H / 2, rightwards ? 'left' : 'right', FontWeight.SemiBold);
package/dist/consts.d.ts CHANGED
@@ -65,7 +65,14 @@ export declare const AXIS_LABELS: {
65
65
  visibilityHigh: string;
66
66
  visibilityLow: string;
67
67
  };
68
- /** Colors (light theme, faithful to the reference renderer / mockup C). */
68
+ /**
69
+ * Colors (light theme, faithful to the reference renderer / mockup C).
70
+ *
71
+ * The neutrals are the shared notation scale, of which Wardley is the
72
+ * reference: the values are unchanged, they are simply read from the one place
73
+ * every framework reads them from. The first phase band is the scale's band
74
+ * tint; the three after it are Wardley's own graded wash.
75
+ */
69
76
  export declare const COLORS: {
70
77
  card: string;
71
78
  cardBorder: string;
package/dist/consts.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
1
2
  /**
2
3
  * Visual constants for the Wardley map background.
3
4
  *
@@ -51,13 +52,20 @@ export const AXIS_LABELS = {
51
52
  visibilityHigh: 'Visible',
52
53
  visibilityLow: 'Invisible',
53
54
  };
54
- /** Colors (light theme, faithful to the reference renderer / mockup C). */
55
+ /**
56
+ * Colors (light theme, faithful to the reference renderer / mockup C).
57
+ *
58
+ * The neutrals are the shared notation scale, of which Wardley is the
59
+ * reference: the values are unchanged, they are simply read from the one place
60
+ * every framework reads them from. The first phase band is the scale's band
61
+ * tint; the three after it are Wardley's own graded wash.
62
+ */
55
63
  export const COLORS = {
56
- card: '#ffffff',
57
- cardBorder: '#e3e2e4',
58
- axis: '#3b3d42',
59
- divider: '#9aa0a6',
60
- label: '#6b7280',
64
+ card: NOTATION_NEUTRALS.cardFill,
65
+ cardBorder: NOTATION_NEUTRALS.cardBorder,
66
+ axis: NOTATION_NEUTRALS.frameInk,
67
+ divider: NOTATION_NEUTRALS.divider,
68
+ label: NOTATION_NEUTRALS.label,
61
69
  band: ['#f7faff', '#eef4fb', '#e6eef8', '#dde8f4'],
62
70
  };
63
71
  export const FONT_FAMILY = 'Inter, sans-serif';
package/dist/import.d.ts CHANGED
@@ -1,5 +1,33 @@
1
1
  import type { InterchangeImportContext, InterchangeImportResult } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { OWM_SCOPE, WARDLEY_OWM_FORMAT_ID } from './export.js';
3
+ /**
4
+ * The OWM import remarks whose wording is FIXED, as `[key, English]` pairs.
5
+ *
6
+ * A reader is a pure function of text (mirroring `.bpmn`'s own, ADR 0012 P3)
7
+ * and has no `std`, so it cannot ask the host's catalogue for anything: it
8
+ * declares the key on the note and `reportInterchangeImport` resolves it when
9
+ * it draws the report (`InterchangeNote.messageKey`). The English string
10
+ * stays here and stays the fallback.
11
+ *
12
+ * Every remark's fallback is the whole sentence a call site builds — a
13
+ * `{{name}}` / `{{count}}` hole is filled by `messageParams`
14
+ * (`InterchangeNote.messageParams`) at the call site, never by minting a new
15
+ * key for every value the file happens to carry. Plural wording stays
16
+ * grammatically neutral in the fallback (`'{{count}} line(s)…'`) — the
17
+ * host's job, the same rule BPMN's own remarks follow
18
+ * (`gfx/bpmn/src/import.ts`).
19
+ *
20
+ * Contributed to the manifest by `./translations.ts`.
21
+ */
22
+ export declare const WARDLEY_OWM_IMPORT_REMARKS: {
23
+ readonly unreadableCoordinatesInvented: readonly ["com.labre.wardley.import.remark.unreadable-coordinates", "its coordinates were unreadable, so it was placed where a map with no coordinates places things. The file did not say where it goes."];
24
+ readonly noCoordinatesInvented: readonly ["com.labre.wardley.import.remark.no-coordinates", "the file gives it no coordinates, so it was placed where a map with no coordinates places things. The file did not say where it goes."];
25
+ readonly duplicateName: readonly ["com.labre.wardley.import.remark.duplicate-name", "this name is declared more than once. Both artefacts are on the map, and every link naming it means the first — OWM identifies a component by its name."];
26
+ readonly linkEmptyEnd: readonly ["com.labre.wardley.import.remark.link-empty-end", "a link names nothing on one of its ends, so the arrow it asks for runs to no artefact and is invisible on the canvas."];
27
+ readonly linkUndeclaredEnd: readonly ["com.labre.wardley.import.remark.link-undeclared-end", "a link names this, and no statement in the file declares it. The arrow is in the document and runs to no artefact, so it is invisible on the canvas."];
28
+ readonly unreadableCoordinatesLine: readonly ["com.labre.wardley.import.remark.unreadable-coordinates-line", "line {{line}} declares coordinates this reader cannot make a number of, so they were not used."];
29
+ readonly carriedConstruct: readonly ["com.labre.wardley.import.remark.carried-construct", "{{count}} `{{kind}}` line(s) kept verbatim on the map and written back on the next export. Nothing on this canvas draws them."];
30
+ };
3
31
  /**
4
32
  * An OnlineWardleyMaps (OWM) DSL document, read as a Wardley map — the inverse
5
33
  * of `export.ts` on the vocabulary Labre draws, and an honest accounting of
package/dist/import.js CHANGED
@@ -4,6 +4,55 @@ import { OWM_DEFAULT_MAP_HEIGHT, OWM_DEFAULT_MAP_WIDTH, OWM_LABEL_HEIGHT, OWM_LA
4
4
  import { HANDLE_SIZE, LABEL_GAP, LABEL_FONT_SIZE, LINK_GREY, LINK_STROKE_WIDTH, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, ECOSYSTEM_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, WARDLEY_RED, } from './node/consts.js';
5
5
  import { WARDLEY_NODE_SIZE, wardleyNodeProps } from './presets.js';
6
6
  import { WARDLEY_ROLE } from './roles.js';
7
+ /**
8
+ * The OWM import remarks whose wording is FIXED, as `[key, English]` pairs.
9
+ *
10
+ * A reader is a pure function of text (mirroring `.bpmn`'s own, ADR 0012 P3)
11
+ * and has no `std`, so it cannot ask the host's catalogue for anything: it
12
+ * declares the key on the note and `reportInterchangeImport` resolves it when
13
+ * it draws the report (`InterchangeNote.messageKey`). The English string
14
+ * stays here and stays the fallback.
15
+ *
16
+ * Every remark's fallback is the whole sentence a call site builds — a
17
+ * `{{name}}` / `{{count}}` hole is filled by `messageParams`
18
+ * (`InterchangeNote.messageParams`) at the call site, never by minting a new
19
+ * key for every value the file happens to carry. Plural wording stays
20
+ * grammatically neutral in the fallback (`'{{count}} line(s)…'`) — the
21
+ * host's job, the same rule BPMN's own remarks follow
22
+ * (`gfx/bpmn/src/import.ts`).
23
+ *
24
+ * Contributed to the manifest by `./translations.ts`.
25
+ */
26
+ export const WARDLEY_OWM_IMPORT_REMARKS = {
27
+ unreadableCoordinatesInvented: [
28
+ 'com.labre.wardley.import.remark.unreadable-coordinates',
29
+ 'its coordinates were unreadable, so it was placed where a map with no coordinates places things. The file did not say where it goes.',
30
+ ],
31
+ noCoordinatesInvented: [
32
+ 'com.labre.wardley.import.remark.no-coordinates',
33
+ 'the file gives it no coordinates, so it was placed where a map with no coordinates places things. The file did not say where it goes.',
34
+ ],
35
+ duplicateName: [
36
+ 'com.labre.wardley.import.remark.duplicate-name',
37
+ 'this name is declared more than once. Both artefacts are on the map, and every link naming it means the first — OWM identifies a component by its name.',
38
+ ],
39
+ linkEmptyEnd: [
40
+ 'com.labre.wardley.import.remark.link-empty-end',
41
+ 'a link names nothing on one of its ends, so the arrow it asks for runs to no artefact and is invisible on the canvas.',
42
+ ],
43
+ linkUndeclaredEnd: [
44
+ 'com.labre.wardley.import.remark.link-undeclared-end',
45
+ 'a link names this, and no statement in the file declares it. The arrow is in the document and runs to no artefact, so it is invisible on the canvas.',
46
+ ],
47
+ unreadableCoordinatesLine: [
48
+ 'com.labre.wardley.import.remark.unreadable-coordinates-line',
49
+ 'line {{line}} declares coordinates this reader cannot make a number of, so they were not used.',
50
+ ],
51
+ carriedConstruct: [
52
+ 'com.labre.wardley.import.remark.carried-construct',
53
+ '{{count}} `{{kind}}` line(s) kept verbatim on the map and written back on the next export. Nothing on this canvas draws them.',
54
+ ],
55
+ };
7
56
  /**
8
57
  * An OnlineWardleyMaps (OWM) DSL document, read as a Wardley map — the inverse
9
58
  * of `export.ts` on the vocabulary Labre draws, and an honest accounting of
@@ -261,12 +310,13 @@ export function importWardleyOwm(source, context = {}) {
261
310
  carriedLines.push(line);
262
311
  carriedKinds.set(kind, (carriedKinds.get(kind) ?? 0) + 1);
263
312
  };
264
- const inventedNote = (element, name, why) => {
313
+ const inventedNote = (element, name, why, whyKey) => {
265
314
  notes.push({
266
315
  kind: 'invented-layout',
267
316
  sourceId: name,
268
317
  element,
269
318
  message: why,
319
+ ...(whyKey ? { messageKey: whyKey } : {}),
270
320
  });
271
321
  };
272
322
  /**
@@ -297,12 +347,16 @@ export function importWardleyOwm(source, context = {}) {
297
347
  kind: 'warning',
298
348
  sourceId: name,
299
349
  element,
350
+ messageKey: WARDLEY_OWM_IMPORT_REMARKS.unreadableCoordinatesLine[0],
300
351
  message: `line ${lineIndex + 1} declares coordinates this reader cannot make a number of, so they were not used.`,
352
+ messageParams: { line: lineIndex + 1 },
301
353
  });
302
354
  }
303
355
  inventedNote(element, name, bracket.present
304
- ? 'its coordinates were unreadable, so it was placed where a map with no coordinates places things. The file did not say where it goes.'
305
- : 'the file gives it no coordinates, so it was placed where a map with no coordinates places things. The file did not say where it goes.');
356
+ ? WARDLEY_OWM_IMPORT_REMARKS.unreadableCoordinatesInvented[1]
357
+ : WARDLEY_OWM_IMPORT_REMARKS.noCoordinatesInvented[1], bracket.present
358
+ ? WARDLEY_OWM_IMPORT_REMARKS.unreadableCoordinatesInvented[0]
359
+ : WARDLEY_OWM_IMPORT_REMARKS.noCoordinatesInvented[0]);
306
360
  return { ...place, invented: true };
307
361
  }
308
362
  inventedNote(element, name, visibility === undefined
@@ -467,7 +521,9 @@ export function importWardleyOwm(source, context = {}) {
467
521
  notes.push({
468
522
  kind: 'carried',
469
523
  element: kind,
470
- message: `${count} \`${kind}\` line${count === 1 ? '' : 's'} kept verbatim on the map and written back on the next export. Nothing on this canvas draws ${count === 1 ? 'it' : 'them'}.`,
524
+ messageKey: WARDLEY_OWM_IMPORT_REMARKS.carriedConstruct[0],
525
+ message: `${count} \`${kind}\` line(s) kept verbatim on the map and written back on the next export. Nothing on this canvas draws them.`,
526
+ messageParams: { count, kind },
471
527
  });
472
528
  }
473
529
  /* ── A name declared twice ────────────────────────────────────────── */
@@ -478,7 +534,8 @@ export function importWardleyOwm(source, context = {}) {
478
534
  kind: 'warning',
479
535
  sourceId: statement.name,
480
536
  element: statement.keyword,
481
- message: 'this name is declared more than once. Both artefacts are on the map, and every link naming it means the first — OWM identifies a component by its name.',
537
+ message: WARDLEY_OWM_IMPORT_REMARKS.duplicateName[1],
538
+ messageKey: WARDLEY_OWM_IMPORT_REMARKS.duplicateName[0],
482
539
  });
483
540
  }
484
541
  declared.add(statement.name);
@@ -504,7 +561,8 @@ export function importWardleyOwm(source, context = {}) {
504
561
  notes.push({
505
562
  kind: 'warning',
506
563
  element: 'link',
507
- message: 'a link names nothing on one of its ends, so the arrow it asks for runs to no artefact and is invisible on the canvas.',
564
+ message: WARDLEY_OWM_IMPORT_REMARKS.linkEmptyEnd[1],
565
+ messageKey: WARDLEY_OWM_IMPORT_REMARKS.linkEmptyEnd[0],
508
566
  });
509
567
  continue;
510
568
  }
@@ -514,7 +572,8 @@ export function importWardleyOwm(source, context = {}) {
514
572
  kind: 'warning',
515
573
  sourceId: end,
516
574
  element: 'link',
517
- message: 'a link names this, and no statement in the file declares it. The arrow is in the document and runs to no artefact, so it is invisible on the canvas.',
575
+ message: WARDLEY_OWM_IMPORT_REMARKS.linkUndeclaredEnd[1],
576
+ messageKey: WARDLEY_OWM_IMPORT_REMARKS.linkUndeclaredEnd[0],
518
577
  });
519
578
  }
520
579
  }
package/dist/legend.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  import { type WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
2
+ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
2
3
  import type { BlockStdScope } from '@formicoidea/labre-core/std';
3
- /** A box in the panel, relative to the panel's top-left, plus its words. */
4
+ /** Every {@link LEGEND_DESC} wording, for `translations.ts`'s manifest. */
5
+ export declare const WARDLEY_LEGEND_DESC_WORDINGS: readonly ChromeWording[];
6
+ /** Every {@link LEGEND_GRADIENT} caption, for `translations.ts`'s manifest. */
7
+ export declare const WARDLEY_LEGEND_GRADIENT_WORDINGS: readonly ChromeWording[];
8
+ /** The legend box's own title. */
9
+ export declare const WARDLEY_LEGEND_TITLE: ChromeWording;
10
+ /** The five-forces panel's own title. */
11
+ export declare const WARDLEY_LEGEND_PORTER_TITLE: ChromeWording;
12
+ /** Every {@link PORTER_FORCES} wording, plus the caption, for the manifest. */
13
+ export declare const WARDLEY_LEGEND_PORTER_WORDINGS: readonly ChromeWording[];
14
+ /** A box in the panel, relative to the panel's top-left, plus its wording. */
4
15
  interface PorterPanelBox {
5
16
  xywh: [number, number, number, number];
6
- label: string;
17
+ label: ChromeWording;
7
18
  }
8
19
  interface PorterPanelLayout {
9
20
  w: number;
@@ -24,6 +35,13 @@ interface PorterPanelLayout {
24
35
  * they surround — is checkable rather than eyeballed.
25
36
  */
26
37
  export declare function porterPanelLayout(w: number): PorterPanelLayout;
38
+ /**
39
+ * Every wording this file writes onto the canvas, for `translations.ts`'s
40
+ * manifest contribution — the auto-legend's own title, its rows' captions,
41
+ * the gradient blocks and the Porter panel, all as `seed` (written into the
42
+ * document once, at the moment a legend is generated).
43
+ */
44
+ export declare const WARDLEY_LEGEND_WORDINGS: readonly ChromeWording[];
27
45
  /**
28
46
  * Build a "Legend" group from real, editable elements (white rect frame +
29
47
  * "Legend" text + one row of [real component glyph + description text] per