@formicoidea/labre-framework-cynefin 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.
@@ -1,7 +1,19 @@
1
+ import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
1
2
  /**
2
3
  * Visual constants for the Liminal Cynefin diagram, reproduced from the official
3
4
  * SVG (viewBox 0 0 1080 777). All geometry is authored in that fixed reference
4
5
  * space and scaled uniformly to the element bounds by the renderer.
6
+ *
7
+ * ## i18n
8
+ *
9
+ * Every word painted on the diagram — domain headings and subheadings, the
10
+ * Probe/Sense/Respond decision lines, the teal annotations, the small
11
+ * exaptation sub-labels and the two central markers — carries a `…Key`
12
+ * alongside its English text, resolved by `element-renderer.ts` through the
13
+ * `CanvasRenderer` it is handed at paint time (the 4th argument every
14
+ * `ElementRenderer` receives, `renderer.std`). The letters "A" and "C" on the
15
+ * two markers are notation, not words, and are deliberately NOT keyed (PO
16
+ * decision) — only their spelled-out NAMES ("Aporia", "Confusion") are.
5
17
  */
6
18
  export declare const REF_W = 1080;
7
19
  export declare const REF_H = 777;
@@ -32,48 +44,60 @@ export declare const HATCHES: ReadonlyArray<readonly [number, number, number, nu
32
44
  export declare const DASH_RECTS: ReadonlyArray<readonly [number, number, number, number]>;
33
45
  /**
34
46
  * The four domain blocks. Each has a heading (h1) and, when descriptions are
35
- * shown, a subheading (h2) and three decision lines whose lead word is bold.
36
- * All three text levels share the block's left `x`.
47
+ * shown, a subheading (h2) and three decision lines whose lead word is bold
48
+ * (English, no host) or drawn whole (translated). All three text levels share
49
+ * the block's left `x`.
37
50
  */
38
51
  export interface DomainBlock {
39
- heading: string;
52
+ heading: ChromeWording;
40
53
  /** Left edge shared by heading, subheading and body lines. */
41
54
  x: number;
42
55
  /** Heading (h1) baseline. */
43
56
  hy: number;
44
- subheading: string;
57
+ subheading: ChromeWording;
45
58
  /** Subheading (h2) baseline. */
46
59
  sy: number;
47
- /** Decision lines: bold lead word + remainder, with their baseline. */
60
+ /** Decision lines: bold lead word + remainder + the full sentence's key. */
48
61
  lines: ReadonlyArray<{
49
62
  lead: string;
50
63
  rest: string;
64
+ wording: ChromeWording;
51
65
  y: number;
52
66
  }>;
53
67
  }
54
68
  export declare const DOMAINS: ReadonlyArray<DomainBlock>;
55
- /** Teal annotation labels (centered): [text, x, y]. */
56
- export declare const TEAL_LABELS: ReadonlyArray<readonly [string, number, number]>;
57
- /** Small exaptation sub-labels (centered): [text, x, y]. */
58
- export declare const SMALL_LABELS: ReadonlyArray<readonly [string, number, number]>;
69
+ /** Teal annotation labels (centered): [wording, x, y]. */
70
+ export declare const TEAL_LABELS: ReadonlyArray<readonly [ChromeWording, number, number]>;
71
+ /** Small exaptation sub-labels (centered): [wording, x, y]. */
72
+ export declare const SMALL_LABELS: ReadonlyArray<readonly [ChromeWording, number, number]>;
59
73
  /**
60
74
  * The two central markers — Aporia (A) and Confusion (C) — each a big glyph and
61
75
  * a name, with an optional teal note ("prepare to exit"). All centered.
76
+ *
77
+ * `letter` is the notation itself ("A" / "C") and is deliberately NOT keyed
78
+ * (PO decision) — only the spelled-out `name` is.
62
79
  */
63
80
  export interface Marker {
64
81
  letter: string;
65
82
  /** Big glyph position. */
66
83
  lx: number;
67
84
  ly: number;
68
- name: string;
85
+ name: ChromeWording;
69
86
  /** Name (body) position. */
70
87
  nx: number;
71
88
  ny: number;
72
- /** Optional teal note position + text. */
89
+ /** Optional teal note position + wording. */
73
90
  note?: {
74
- text: string;
91
+ wording: ChromeWording;
75
92
  x: number;
76
93
  y: number;
77
94
  };
78
95
  }
79
96
  export declare const MARKERS: ReadonlyArray<Marker>;
97
+ /**
98
+ * Every wording this file paints, for `translations.ts`'s manifest
99
+ * contribution. Deduplicated by key (`iterate` and the two shared lines are
100
+ * each listed once), which is what `mergeTranslationEntries` needs to see one
101
+ * entry per key rather than two identical ones.
102
+ */
103
+ export declare const CYNEFIN_CANVAS_WORDINGS: readonly ChromeWording[];
@@ -2,6 +2,17 @@
2
2
  * Visual constants for the Liminal Cynefin diagram, reproduced from the official
3
3
  * SVG (viewBox 0 0 1080 777). All geometry is authored in that fixed reference
4
4
  * space and scaled uniformly to the element bounds by the renderer.
5
+ *
6
+ * ## i18n
7
+ *
8
+ * Every word painted on the diagram — domain headings and subheadings, the
9
+ * Probe/Sense/Respond decision lines, the teal annotations, the small
10
+ * exaptation sub-labels and the two central markers — carries a `…Key`
11
+ * alongside its English text, resolved by `element-renderer.ts` through the
12
+ * `CanvasRenderer` it is handed at paint time (the 4th argument every
13
+ * `ElementRenderer` receives, `renderer.std`). The letters "A" and "C" on the
14
+ * two markers are notation, not words, and are deliberately NOT keyed (PO
15
+ * decision) — only their spelled-out NAMES ("Aporia", "Confusion") are.
5
16
  */
6
17
  export const REF_W = 1080;
7
18
  export const REF_H = 777;
@@ -84,94 +95,318 @@ export const DASH_RECTS = [
84
95
  [998, 314, 11.9, 168.9],
85
96
  [1019, 310, 11.8, 167.3],
86
97
  ];
98
+ /* ── i18n: headings, subheadings and decision lines ───────────────────── */
99
+ const HEADING_COMPLEX = [
100
+ 'com.labre.cynefin-estuarine.cynefin.heading.complex',
101
+ 'Complex',
102
+ ];
103
+ const HEADING_COMPLICATED = [
104
+ 'com.labre.cynefin-estuarine.cynefin.heading.complicated',
105
+ 'Complicated',
106
+ ];
107
+ const HEADING_CHAOTIC = [
108
+ 'com.labre.cynefin-estuarine.cynefin.heading.chaotic',
109
+ 'Chaotic',
110
+ ];
111
+ const HEADING_CLEAR = [
112
+ 'com.labre.cynefin-estuarine.cynefin.heading.clear',
113
+ 'Clear',
114
+ ];
115
+ const SUBHEADING_ADAPTIVE = [
116
+ 'com.labre.cynefin-estuarine.cynefin.subheading.adaptive-system',
117
+ 'Adaptive system',
118
+ ];
119
+ /** Shared by Complicated and Clear — the same word, one key. */
120
+ const SUBHEADING_ORDERED = [
121
+ 'com.labre.cynefin-estuarine.cynefin.subheading.ordered-system',
122
+ 'Ordered system',
123
+ ];
124
+ const SUBHEADING_UNORDERED = [
125
+ 'com.labre.cynefin-estuarine.cynefin.subheading.un-ordered-system',
126
+ 'Un-ordered system',
127
+ ];
128
+ /**
129
+ * The ten distinct decision sentences (two are shared verbatim between two
130
+ * domains — "Sense how the context reacts", "Sense the context with
131
+ * analytical methods" — one key each, reused rather than duplicated).
132
+ *
133
+ * The fallback is the FULL sentence (`lead` + `rest` concatenated): the
134
+ * renderer draws `lead` bold and `rest` roman as two `fillText` calls only
135
+ * while NO host answered (byte-identical to before these keys existed); once
136
+ * a host resolves the key, the whole sentence is drawn as one run — a
137
+ * translation is not guaranteed to keep the same first word, so splitting it
138
+ * at the English boundary would be a guess.
139
+ */
140
+ const LINE_PROBE = [
141
+ 'com.labre.cynefin-estuarine.cynefin.line.probe',
142
+ 'Probe the context with parallel experiments',
143
+ ];
144
+ const LINE_SENSE_REACTS = [
145
+ 'com.labre.cynefin-estuarine.cynefin.line.sense-reacts',
146
+ 'Sense how the context reacts',
147
+ ];
148
+ const LINE_RESPOND_AMPLIFY = [
149
+ 'com.labre.cynefin-estuarine.cynefin.line.respond-amplify',
150
+ 'Respond by amplifying positive experiments',
151
+ ];
152
+ const LINE_SENSE_ANALYTICAL = [
153
+ 'com.labre.cynefin-estuarine.cynefin.line.sense-analytical',
154
+ 'Sense the context with analytical methods',
155
+ ];
156
+ const LINE_ANALYSE = [
157
+ 'com.labre.cynefin-estuarine.cynefin.line.analyse',
158
+ 'Analyse observations',
159
+ ];
160
+ const LINE_RESPOND_MANY_SOLUTIONS = [
161
+ 'com.labre.cynefin-estuarine.cynefin.line.respond-many-solutions',
162
+ 'Respond by applying one of many good solutions',
163
+ ];
164
+ const LINE_ACT = [
165
+ 'com.labre.cynefin-estuarine.cynefin.line.act',
166
+ 'Act on the context to stabilize (it or yourself)',
167
+ ];
168
+ const LINE_RESPOND_REACT = [
169
+ 'com.labre.cynefin-estuarine.cynefin.line.respond-react',
170
+ 'Respond by re-acting',
171
+ ];
172
+ const LINE_CATEGORIZE = [
173
+ 'com.labre.cynefin-estuarine.cynefin.line.categorize',
174
+ 'Categorize observations',
175
+ ];
176
+ const LINE_RESPOND_PRACTICES = [
177
+ 'com.labre.cynefin-estuarine.cynefin.line.respond-practices',
178
+ 'Respond by applying tried and true practices',
179
+ ];
87
180
  export const DOMAINS = [
88
181
  {
89
- heading: 'Complex',
182
+ heading: HEADING_COMPLEX,
90
183
  x: 37,
91
184
  hy: 31,
92
- subheading: 'Adaptive system',
185
+ subheading: SUBHEADING_ADAPTIVE,
93
186
  sy: 53,
94
187
  lines: [
95
- { lead: 'Probe', rest: ' the context with parallel experiments', y: 71 },
96
- { lead: 'Sense', rest: ' how the context reacts', y: 90 },
97
- { lead: 'Respond', rest: ' by amplifying positive experiments', y: 109 },
188
+ {
189
+ lead: 'Probe',
190
+ rest: ' the context with parallel experiments',
191
+ wording: LINE_PROBE,
192
+ y: 71,
193
+ },
194
+ {
195
+ lead: 'Sense',
196
+ rest: ' how the context reacts',
197
+ wording: LINE_SENSE_REACTS,
198
+ y: 90,
199
+ },
200
+ {
201
+ lead: 'Respond',
202
+ rest: ' by amplifying positive experiments',
203
+ wording: LINE_RESPOND_AMPLIFY,
204
+ y: 109,
205
+ },
98
206
  ],
99
207
  },
100
208
  {
101
- heading: 'Complicated',
209
+ heading: HEADING_COMPLICATED,
102
210
  x: 779,
103
211
  hy: 31,
104
- subheading: 'Ordered system',
212
+ subheading: SUBHEADING_ORDERED,
105
213
  sy: 53,
106
214
  lines: [
107
- { lead: 'Sense', rest: ' the context with analytical methods', y: 71 },
108
- { lead: 'Analyse', rest: ' observations', y: 90 },
215
+ {
216
+ lead: 'Sense',
217
+ rest: ' the context with analytical methods',
218
+ wording: LINE_SENSE_ANALYTICAL,
219
+ y: 71,
220
+ },
221
+ { lead: 'Analyse', rest: ' observations', wording: LINE_ANALYSE, y: 90 },
109
222
  {
110
223
  lead: 'Respond',
111
224
  rest: ' by applying one of many good solutions',
225
+ wording: LINE_RESPOND_MANY_SOLUTIONS,
112
226
  y: 109,
113
227
  },
114
228
  ],
115
229
  },
116
230
  {
117
- heading: 'Chaotic',
231
+ heading: HEADING_CHAOTIC,
118
232
  x: 37,
119
233
  hy: 587,
120
- subheading: 'Un-ordered system',
234
+ subheading: SUBHEADING_UNORDERED,
121
235
  sy: 609,
122
236
  lines: [
123
237
  {
124
238
  lead: 'Act',
125
239
  rest: ' on the context to stabilize (it or yourself)',
240
+ wording: LINE_ACT,
126
241
  y: 627,
127
242
  },
128
- { lead: 'Sense', rest: ' how the context reacts', y: 646 },
129
- { lead: 'Respond', rest: ' by re-acting', y: 665 },
243
+ {
244
+ lead: 'Sense',
245
+ rest: ' how the context reacts',
246
+ wording: LINE_SENSE_REACTS,
247
+ y: 646,
248
+ },
249
+ {
250
+ lead: 'Respond',
251
+ rest: ' by re-acting',
252
+ wording: LINE_RESPOND_REACT,
253
+ y: 665,
254
+ },
130
255
  ],
131
256
  },
132
257
  {
133
- heading: 'Clear',
258
+ heading: HEADING_CLEAR,
134
259
  x: 779,
135
260
  hy: 587,
136
- subheading: 'Ordered system',
261
+ subheading: SUBHEADING_ORDERED,
137
262
  sy: 609,
138
263
  lines: [
139
- { lead: 'Sense', rest: ' the context with analytical methods', y: 627 },
140
- { lead: 'Categorize', rest: ' observations', y: 646 },
264
+ {
265
+ lead: 'Sense',
266
+ rest: ' the context with analytical methods',
267
+ wording: LINE_SENSE_ANALYTICAL,
268
+ y: 627,
269
+ },
270
+ {
271
+ lead: 'Categorize',
272
+ rest: ' observations',
273
+ wording: LINE_CATEGORIZE,
274
+ y: 646,
275
+ },
141
276
  {
142
277
  lead: 'Respond',
143
278
  rest: ' by applying tried and true practices',
279
+ wording: LINE_RESPOND_PRACTICES,
144
280
  y: 665,
145
281
  },
146
282
  ],
147
283
  },
148
284
  ];
149
- /** Teal annotation labels (centered): [text, x, y]. */
285
+ /* ── i18n: teal annotations and small exaptation sub-labels ────────────── */
286
+ const TEAL_ITERATE = [
287
+ 'com.labre.cynefin-estuarine.cynefin.teal.iterate',
288
+ 'iterate',
289
+ ];
290
+ const TEAL_STRATEGY_BY_DESIGN = [
291
+ 'com.labre.cynefin-estuarine.cynefin.teal.strategy-by-design',
292
+ 'strategy by design',
293
+ ];
294
+ const TEAL_RADICAL_INNOVATION = [
295
+ 'com.labre.cynefin-estuarine.cynefin.teal.radical-innovation',
296
+ 'radical innovation',
297
+ ];
298
+ const TEAL_BY_DESIGN = [
299
+ 'com.labre.cynefin-estuarine.cynefin.teal.by-design',
300
+ 'by design',
301
+ ];
302
+ const TEAL_EXTREME_REPURPOSING = [
303
+ 'com.labre.cynefin-estuarine.cynefin.teal.extreme-repurposing',
304
+ 'extreme repurposing',
305
+ ];
306
+ const TEAL_GOOD_PRACTICE = [
307
+ 'com.labre.cynefin-estuarine.cynefin.teal.good-practice',
308
+ 'good practice',
309
+ ];
310
+ const TEAL_BEST_PRACTICE = [
311
+ 'com.labre.cynefin-estuarine.cynefin.teal.best-practice',
312
+ 'best practice',
313
+ ];
314
+ /** Teal annotation labels (centered): [wording, x, y]. */
150
315
  export const TEAL_LABELS = [
151
- ['iterate', 510, 16],
152
- ['iterate', 533, 230],
153
- ['strategy by design', 257, 225],
154
- ['radical innovation', 268, 390],
155
- ['by design', 268, 406],
156
- ['extreme repurposing', 217, 496],
157
- ['good practice', 814, 196],
158
- ['best practice', 751, 459],
159
- ];
160
- /** Small exaptation sub-labels (centered): [text, x, y]. */
316
+ [TEAL_ITERATE, 510, 16],
317
+ [TEAL_ITERATE, 533, 230],
318
+ [TEAL_STRATEGY_BY_DESIGN, 257, 225],
319
+ [TEAL_RADICAL_INNOVATION, 268, 390],
320
+ [TEAL_BY_DESIGN, 268, 406],
321
+ [TEAL_EXTREME_REPURPOSING, 217, 496],
322
+ [TEAL_GOOD_PRACTICE, 814, 196],
323
+ [TEAL_BEST_PRACTICE, 751, 459],
324
+ ];
325
+ const SMALL_DISPOSITIONAL = [
326
+ 'com.labre.cynefin-estuarine.cynefin.small.dispositional-exaptation',
327
+ 'dispositional exaptation',
328
+ ];
329
+ const SMALL_STIMULATED = [
330
+ 'com.labre.cynefin-estuarine.cynefin.small.stimulated-exaptation',
331
+ 'stimulated exaptation',
332
+ ];
333
+ const SMALL_STRESS_BASED = [
334
+ 'com.labre.cynefin-estuarine.cynefin.small.stress-based-exaptation',
335
+ 'stress-based exaptation',
336
+ ];
337
+ /** Small exaptation sub-labels (centered): [wording, x, y]. */
161
338
  export const SMALL_LABELS = [
162
- ['dispositional exaptation', 257, 239],
163
- ['stimulated exaptation', 268, 419],
164
- ['stress-based exaptation', 217, 510],
339
+ [SMALL_DISPOSITIONAL, 257, 239],
340
+ [SMALL_STIMULATED, 268, 419],
341
+ [SMALL_STRESS_BASED, 217, 510],
342
+ ];
343
+ /* ── i18n: the two central markers ──────────────────────────────────── */
344
+ const MARKER_APORIA_NAME = [
345
+ 'com.labre.cynefin-estuarine.cynefin.marker.aporia',
346
+ 'Aporia',
347
+ ];
348
+ const MARKER_CONFUSION_NAME = [
349
+ 'com.labre.cynefin-estuarine.cynefin.marker.confusion',
350
+ 'Confusion',
351
+ ];
352
+ const MARKER_APORIA_NOTE = [
353
+ 'com.labre.cynefin-estuarine.cynefin.marker.aporia-note',
354
+ 'prepare to exit',
165
355
  ];
166
356
  export const MARKERS = [
167
357
  {
168
358
  letter: 'A',
169
359
  lx: 444,
170
360
  ly: 334,
171
- name: 'Aporia',
361
+ name: MARKER_APORIA_NAME,
172
362
  nx: 447,
173
363
  ny: 349,
174
- note: { text: 'prepare to exit', x: 449, y: 366 },
364
+ note: { wording: MARKER_APORIA_NOTE, x: 449, y: 366 },
365
+ },
366
+ {
367
+ letter: 'C',
368
+ lx: 531,
369
+ ly: 419,
370
+ name: MARKER_CONFUSION_NAME,
371
+ nx: 529,
372
+ ny: 436,
175
373
  },
176
- { letter: 'C', lx: 531, ly: 419, name: 'Confusion', nx: 529, ny: 436 },
374
+ ];
375
+ /**
376
+ * Every wording this file paints, for `translations.ts`'s manifest
377
+ * contribution. Deduplicated by key (`iterate` and the two shared lines are
378
+ * each listed once), which is what `mergeTranslationEntries` needs to see one
379
+ * entry per key rather than two identical ones.
380
+ */
381
+ export const CYNEFIN_CANVAS_WORDINGS = [
382
+ HEADING_COMPLEX,
383
+ HEADING_COMPLICATED,
384
+ HEADING_CHAOTIC,
385
+ HEADING_CLEAR,
386
+ SUBHEADING_ADAPTIVE,
387
+ SUBHEADING_ORDERED,
388
+ SUBHEADING_UNORDERED,
389
+ LINE_PROBE,
390
+ LINE_SENSE_REACTS,
391
+ LINE_RESPOND_AMPLIFY,
392
+ LINE_SENSE_ANALYTICAL,
393
+ LINE_ANALYSE,
394
+ LINE_RESPOND_MANY_SOLUTIONS,
395
+ LINE_ACT,
396
+ LINE_RESPOND_REACT,
397
+ LINE_CATEGORIZE,
398
+ LINE_RESPOND_PRACTICES,
399
+ TEAL_ITERATE,
400
+ TEAL_STRATEGY_BY_DESIGN,
401
+ TEAL_RADICAL_INNOVATION,
402
+ TEAL_BY_DESIGN,
403
+ TEAL_EXTREME_REPURPOSING,
404
+ TEAL_GOOD_PRACTICE,
405
+ TEAL_BEST_PRACTICE,
406
+ SMALL_DISPOSITIONAL,
407
+ SMALL_STIMULATED,
408
+ SMALL_STRESS_BASED,
409
+ MARKER_APORIA_NAME,
410
+ MARKER_CONFUSION_NAME,
411
+ MARKER_APORIA_NOTE,
177
412
  ];
@@ -7,6 +7,22 @@ import type { CynefinElementModel } from '@formicoidea/labre-core/model';
7
7
  * (heading + Probe/Sense/Respond decisions), the teal annotation labels and the
8
8
  * central Aporia (A) / Confusion (C) markers. Drawn in the fixed reference space
9
9
  * and scaled uniformly to the element bounds.
10
+ *
11
+ * ## i18n
12
+ *
13
+ * Every word above is a `ChromeWording` (`./consts.ts`), resolved through the
14
+ * `CanvasRenderer` this function is handed as its 4th argument — the same
15
+ * `renderer.std` `framework-background/renderer.ts` reads for a declared
16
+ * background. `renderer` is optional (absent in a bare unit test that calls
17
+ * this function with three arguments), in which case every wording reads its
18
+ * English fallback, byte-identical to before these keys existed.
19
+ *
20
+ * The three-line decisions are the one wrinkle: with no host they are drawn as
21
+ * they always were — a bold lead word plus the roman remainder, two
22
+ * `fillText` calls — because that split is known to be exactly the English
23
+ * text. Once a host answers, the WHOLE sentence is drawn as one run: a
24
+ * translation is not guaranteed to keep the same first word, so guessing where
25
+ * to split it would be inventing typography the translator never asked for.
10
26
  */
11
27
  export declare const cynefin: ElementRenderer<CynefinElementModel>;
12
28
  export declare const CynefinRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
@@ -1,4 +1,5 @@
1
1
  import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { TranslationProvider, translateKey, } from '@formicoidea/labre-core/shared/services';
2
3
  import { FONT_FAMILY, refScale } from '../utils.js';
3
4
  import { COLORS, DARK_BACK_PATHS, DARK_FRONT_PATHS, DASH_RECTS, DOMAINS, HATCHES, MARKERS, REF_H, REF_W, SMALL_LABELS, TEAL_LABELS, TEAL_PATH, TEAL_WIDTH, } from './consts.js';
4
5
  /**
@@ -8,8 +9,29 @@ import { COLORS, DARK_BACK_PATHS, DARK_FRONT_PATHS, DASH_RECTS, DOMAINS, HATCHES
8
9
  * (heading + Probe/Sense/Respond decisions), the teal annotation labels and the
9
10
  * central Aporia (A) / Confusion (C) markers. Drawn in the fixed reference space
10
11
  * and scaled uniformly to the element bounds.
12
+ *
13
+ * ## i18n
14
+ *
15
+ * Every word above is a `ChromeWording` (`./consts.ts`), resolved through the
16
+ * `CanvasRenderer` this function is handed as its 4th argument — the same
17
+ * `renderer.std` `framework-background/renderer.ts` reads for a declared
18
+ * background. `renderer` is optional (absent in a bare unit test that calls
19
+ * this function with three arguments), in which case every wording reads its
20
+ * English fallback, byte-identical to before these keys existed.
21
+ *
22
+ * The three-line decisions are the one wrinkle: with no host they are drawn as
23
+ * they always were — a bold lead word plus the roman remainder, two
24
+ * `fillText` calls — because that split is known to be exactly the English
25
+ * text. Once a host answers, the WHOLE sentence is drawn as one run: a
26
+ * translation is not guaranteed to keep the same first word, so guessing where
27
+ * to split it would be inventing typography the translator never asked for.
11
28
  */
12
- export const cynefin = (model, ctx, matrix) => {
29
+ export const cynefin = (model, ctx, matrix, renderer) => {
30
+ const tr = (wording) => renderer ? translateKey(renderer.std, ...wording) : wording[1];
31
+ // The host's catalogue, or `null` with no host — used to decide whether a
32
+ // decision line is drawn bold-lead/roman-rest (English, exactly as before)
33
+ // or as one translated run. See the docstring above.
34
+ const hasHost = Boolean(renderer?.std.getOptional(TranslationProvider));
13
35
  const [, , w, h] = model.deserializedXYWH;
14
36
  const cx = w / 2;
15
37
  const cy = h / 2;
@@ -67,50 +89,61 @@ export const cynefin = (model, ctx, matrix) => {
67
89
  ctx.fillStyle = COLORS.heading;
68
90
  ctx.font = `700 30px ${FONT_FAMILY}`;
69
91
  for (const d of DOMAINS)
70
- ctx.fillText(d.heading, d.x, d.hy);
92
+ ctx.fillText(tr(d.heading), d.x, d.hy);
71
93
  ctx.textAlign = 'center';
72
94
  for (const m of MARKERS) {
73
95
  ctx.fillStyle = COLORS.body;
74
96
  ctx.font = `700 38px ${FONT_FAMILY}`;
97
+ // The letter itself is notation, not a word — never translated.
75
98
  ctx.fillText(m.letter, m.lx, m.ly);
76
99
  ctx.font = `13.5px ${FONT_FAMILY}`;
77
- ctx.fillText(m.name, m.nx, m.ny);
100
+ ctx.fillText(tr(m.name), m.nx, m.ny);
78
101
  }
79
102
  }
80
103
  // ── Explanatory text: subheadings, decisions, annotations, notes ────
81
104
  if (model.showDescriptions) {
82
- // Subheadings (h2) + bold-lead decision lines
105
+ // Subheadings (h2) + decision lines
83
106
  ctx.textAlign = 'left';
84
107
  for (const d of DOMAINS) {
85
108
  ctx.fillStyle = COLORS.heading;
86
109
  ctx.font = `700 15px ${FONT_FAMILY}`;
87
- ctx.fillText(d.subheading, d.x, d.sy);
110
+ ctx.fillText(tr(d.subheading), d.x, d.sy);
88
111
  ctx.fillStyle = COLORS.body;
89
- for (const { lead, rest, y } of d.lines) {
90
- ctx.font = `700 13.5px ${FONT_FAMILY}`;
91
- ctx.fillText(lead, d.x, y);
92
- const leadW = ctx.measureText(lead).width;
93
- ctx.font = `13.5px ${FONT_FAMILY}`;
94
- ctx.fillText(rest, d.x + leadW, y);
112
+ for (const { lead, rest, wording, y } of d.lines) {
113
+ if (hasHost) {
114
+ // A translated sentence: one run, no assumed bold-lead boundary.
115
+ ctx.font = `13.5px ${FONT_FAMILY}`;
116
+ ctx.fillText(tr(wording), d.x, y);
117
+ }
118
+ else {
119
+ // No host: exactly the bold-lead / roman-rest split that shipped
120
+ // before these keys existed.
121
+ ctx.font = `700 13.5px ${FONT_FAMILY}`;
122
+ ctx.fillText(lead, d.x, y);
123
+ const leadW = ctx.measureText(lead).width;
124
+ ctx.font = `13.5px ${FONT_FAMILY}`;
125
+ ctx.fillText(rest, d.x + leadW, y);
126
+ }
95
127
  }
96
128
  }
97
129
  ctx.textAlign = 'center';
98
130
  // Teal annotation labels
99
131
  ctx.fillStyle = COLORS.teal;
100
132
  ctx.font = `700 15px ${FONT_FAMILY}`;
101
- for (const [t, x, y] of TEAL_LABELS)
102
- ctx.fillText(t, x, y);
133
+ for (const [wording, x, y] of TEAL_LABELS)
134
+ ctx.fillText(tr(wording), x, y);
103
135
  // Small exaptation sub-labels
104
136
  ctx.fillStyle = COLORS.body;
105
137
  ctx.font = `10.5px ${FONT_FAMILY}`;
106
- for (const [t, x, y] of SMALL_LABELS)
107
- ctx.fillText(t, x, y);
138
+ for (const [wording, x, y] of SMALL_LABELS) {
139
+ ctx.fillText(tr(wording), x, y);
140
+ }
108
141
  // Marker notes ("prepare to exit")
109
142
  ctx.fillStyle = COLORS.teal;
110
143
  ctx.font = `700 15px ${FONT_FAMILY}`;
111
144
  for (const m of MARKERS) {
112
145
  if (m.note)
113
- ctx.fillText(m.note.text, m.note.x, m.note.y);
146
+ ctx.fillText(tr(m.note.wording), m.note.x, m.note.y);
114
147
  }
115
148
  }
116
149
  };
@@ -1,5 +1,7 @@
1
1
  import { type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
2
2
  import { type TemplateResult } from 'lit';
3
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
4
+ export declare const CYNEFIN_TOOLBAR_WORDINGS: readonly ChromeWording[];
3
5
  export declare const cynefinToolbarConfig: {
4
6
  readonly actions: [{
5
7
  id: string;
@@ -52,6 +52,25 @@ const LiminalIcon = html `<svg
52
52
  >
53
53
  <path d="M5 17 C9 6 15 6 19 7" />
54
54
  </svg>`;
55
+ /** The three toolbar tooltips this file used to hard-code as English literals. */
56
+ const TITLES_TOGGLE = [
57
+ 'com.labre.cynefin-estuarine.cynefin.toolbar.titles',
58
+ 'Show / hide titles',
59
+ ];
60
+ const DESCRIPTIONS_TOGGLE = [
61
+ 'com.labre.cynefin-estuarine.cynefin.toolbar.descriptions',
62
+ 'Show / hide explanatory text',
63
+ ];
64
+ const LIMINAL_LINE_TOGGLE = [
65
+ 'com.labre.cynefin-estuarine.cynefin.toolbar.liminal-line',
66
+ 'Show / hide liminal line',
67
+ ];
68
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
69
+ export const CYNEFIN_TOOLBAR_WORDINGS = [
70
+ TITLES_TOGGLE,
71
+ DESCRIPTIONS_TOGGLE,
72
+ LIMINAL_LINE_TOGGLE,
73
+ ];
55
74
  function booleanToggle(id, tooltip, icon, prop) {
56
75
  return {
57
76
  id,
@@ -81,9 +100,9 @@ function booleanToggle(id, tooltip, icon, prop) {
81
100
  export const cynefinToolbarConfig = {
82
101
  actions: [
83
102
  booleanToggle('a.toggle-resize', BOARD_RESIZE_TOGGLE, ResizeIcon, 'resizeEnabled'),
84
- booleanToggle('b.toggle-titles', 'Show / hide titles', TitlesIcon, 'showTitles'),
85
- booleanToggle('c.toggle-descriptions', 'Show / hide explanatory text', DescIcon, 'showDescriptions'),
86
- booleanToggle('d.toggle-liminal', 'Show / hide liminal line', LiminalIcon, 'showLiminalLine'),
103
+ booleanToggle('b.toggle-titles', TITLES_TOGGLE, TitlesIcon, 'showTitles'),
104
+ booleanToggle('c.toggle-descriptions', DESCRIPTIONS_TOGGLE, DescIcon, 'showDescriptions'),
105
+ booleanToggle('d.toggle-liminal', LIMINAL_LINE_TOGGLE, LiminalIcon, 'showLiminalLine'),
87
106
  ],
88
107
  when: ctx => ctx.getSurfaceModelsByType(CynefinElementModel).length > 0,
89
108
  };
@@ -1,3 +1,4 @@
1
+ import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
1
2
  /**
2
3
  * Visual constants for the Estuarine framework map, reproduced from the official
3
4
  * SVG (viewBox 0 0 690 801). All geometry is authored in that fixed reference
@@ -7,6 +8,12 @@
7
8
  * letterboxed (see `EstuarineFit` in `./element-renderer.ts`). The e axis is
8
9
  * vertical & double-headed (energy), the t axis horizontal & single-headed
9
10
  * (time only flows one way).
11
+ *
12
+ * The three curve legends below (`LABELS`) carry a `wording`, resolved by
13
+ * `element-renderer.ts` through the `CanvasRenderer` it is handed at paint
14
+ * time — see `cynefin/consts.ts` for the identical mechanism next door. The
15
+ * italic `e` / `t` axis LETTERS (`AXIS_LABELS`) are notation, not words, and
16
+ * are deliberately NOT keyed (PO decision).
10
17
  */
11
18
  export declare const REF_W = 690;
12
19
  export declare const REF_H = 801;
@@ -50,24 +57,26 @@ export declare const COUNTERFACTUAL_WIDTH = 5.5;
50
57
  /** Volatile: red boundary descending along the left, bulging right. */
51
58
  export declare const VOLATILE_PATH = "M 58 446 C 61 447, 70 451, 76 454 C 82 457, 88 462, 94 466 C 100 470, 107 476, 112 481 C 117 486, 122 492, 126 498 C 130 504, 135 509, 139 515 C 143 521, 145 526, 148 532 C 151 538, 153 544, 155 550 C 157 556, 159 562, 160 568 C 161 574, 162 580, 163 586 C 164 592, 164 598, 164 604 C 164 610, 166 616, 166 622 C 166 628, 166 634, 165 640 C 164 646, 164 652, 163 658 C 162 664, 162 670, 161 676 C 160 682, 160 688, 159 694 C 158 700, 155 706, 154 712 C 153 718, 152 724, 151 730 C 150 736, 148 746, 147 749";
52
59
  export declare const VOLATILE_WIDTH = 5;
60
+ /** Every {@link LABELS} wording, for `translations.ts`'s manifest. */
61
+ export declare const ESTUARINE_CANVAS_WORDINGS: readonly ChromeWording[];
53
62
  /** Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing. */
54
63
  export declare const LABELS: {
55
64
  readonly counterfactual: {
56
- readonly text: "COUNTER FACTUAL";
65
+ readonly wording: ChromeWording;
57
66
  readonly x: 422;
58
67
  readonly y: 25;
59
68
  readonly size: 20;
60
69
  readonly color: "#1a1a1a";
61
70
  };
62
71
  readonly liminal: {
63
- readonly text: "LIMINAL";
72
+ readonly wording: ChromeWording;
64
73
  readonly x: 316;
65
74
  readonly y: 192;
66
75
  readonly size: 18;
67
76
  readonly color: "#2e7d32";
68
77
  };
69
78
  readonly volatile: {
70
- readonly text: "VOLATILE";
79
+ readonly wording: ChromeWording;
71
80
  readonly x: 219;
72
81
  readonly y: 783;
73
82
  readonly size: 20;
@@ -7,6 +7,12 @@
7
7
  * letterboxed (see `EstuarineFit` in `./element-renderer.ts`). The e axis is
8
8
  * vertical & double-headed (energy), the t axis horizontal & single-headed
9
9
  * (time only flows one way).
10
+ *
11
+ * The three curve legends below (`LABELS`) carry a `wording`, resolved by
12
+ * `element-renderer.ts` through the `CanvasRenderer` it is handed at paint
13
+ * time — see `cynefin/consts.ts` for the identical mechanism next door. The
14
+ * italic `e` / `t` axis LETTERS (`AXIS_LABELS`) are notation, not words, and
15
+ * are deliberately NOT keyed (PO decision).
10
16
  */
11
17
  export const REF_W = 690;
12
18
  export const REF_H = 801;
@@ -54,24 +60,42 @@ export const COUNTERFACTUAL_WIDTH = 5.5;
54
60
  /** Volatile: red boundary descending along the left, bulging right. */
55
61
  export const VOLATILE_PATH = 'M 58 446 C 61 447, 70 451, 76 454 C 82 457, 88 462, 94 466 C 100 470, 107 476, 112 481 C 117 486, 122 492, 126 498 C 130 504, 135 509, 139 515 C 143 521, 145 526, 148 532 C 151 538, 153 544, 155 550 C 157 556, 159 562, 160 568 C 161 574, 162 580, 163 586 C 164 592, 164 598, 164 604 C 164 610, 166 616, 166 622 C 166 628, 166 634, 165 640 C 164 646, 164 652, 163 658 C 162 664, 162 670, 161 676 C 160 682, 160 688, 159 694 C 158 700, 155 706, 154 712 C 153 718, 152 724, 151 730 C 150 736, 148 746, 147 749';
56
62
  export const VOLATILE_WIDTH = 5;
63
+ const LABEL_COUNTERFACTUAL = [
64
+ 'com.labre.cynefin-estuarine.estuarine.legend.counter-factual',
65
+ 'COUNTER FACTUAL',
66
+ ];
67
+ const LABEL_LIMINAL = [
68
+ 'com.labre.cynefin-estuarine.estuarine.legend.liminal',
69
+ 'LIMINAL',
70
+ ];
71
+ const LABEL_VOLATILE = [
72
+ 'com.labre.cynefin-estuarine.estuarine.legend.volatile',
73
+ 'VOLATILE',
74
+ ];
75
+ /** Every {@link LABELS} wording, for `translations.ts`'s manifest. */
76
+ export const ESTUARINE_CANVAS_WORDINGS = [
77
+ LABEL_COUNTERFACTUAL,
78
+ LABEL_LIMINAL,
79
+ LABEL_VOLATILE,
80
+ ];
57
81
  /** Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing. */
58
82
  export const LABELS = {
59
83
  counterfactual: {
60
- text: 'COUNTER FACTUAL',
84
+ wording: LABEL_COUNTERFACTUAL,
61
85
  x: 422,
62
86
  y: 25,
63
87
  size: 20,
64
88
  color: COLORS.label,
65
89
  },
66
90
  liminal: {
67
- text: 'LIMINAL',
91
+ wording: LABEL_LIMINAL,
68
92
  x: 316,
69
93
  y: 192,
70
94
  size: 18,
71
95
  color: COLORS.liminalLabel,
72
96
  },
73
97
  volatile: {
74
- text: 'VOLATILE',
98
+ wording: LABEL_VOLATILE,
75
99
  x: 219,
76
100
  y: 783,
77
101
  size: 20,
@@ -1,4 +1,5 @@
1
1
  import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { translateKey, } from '@formicoidea/labre-core/shared/services';
2
3
  import { FONT_FAMILY } from '../utils.js';
3
4
  import { ARROWHEADS, AXIS_LABELS, AXIS_WIDTH, COLORS, COUNTERFACTUAL_PATH, COUNTERFACTUAL_WIDTH, E_AXIS, LABEL_LETTER_SPACING, LABELS, LIMINAL_PATH, LIMINAL_WIDTH, REF_H, REF_W, T_AXIS, VOLATILE_PATH, VOLATILE_WIDTH, } from './consts.js';
4
5
  /**
@@ -99,7 +100,8 @@ export function applyEstuarineTransform(ctx, w, h) {
99
100
  * the three curves, which are boundaries across the plane and must cover
100
101
  * whatever plane the user has made.
101
102
  */
102
- export const estuarine = (model, ctx, matrix) => {
103
+ export const estuarine = (model, ctx, matrix, renderer) => {
104
+ const tr = (wording) => renderer ? translateKey(renderer.std, ...wording) : wording[1];
103
105
  const [, , w, h] = model.deserializedXYWH;
104
106
  const cx = w / 2;
105
107
  const cy = h / 2;
@@ -152,7 +154,7 @@ export const estuarine = (model, ctx, matrix) => {
152
154
  if (hasSpacing) {
153
155
  ctx.letterSpacing = `${LABEL_LETTER_SPACING * fit.strokeScale}px`;
154
156
  }
155
- ctx.fillText(l.text, ax(l.x), ay(l.y));
157
+ ctx.fillText(tr(l.wording), ax(l.x), ay(l.y));
156
158
  if (hasSpacing)
157
159
  ctx.letterSpacing = '0px';
158
160
  };
@@ -1,5 +1,7 @@
1
1
  import { type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
2
2
  import { type TemplateResult } from 'lit';
3
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
4
+ export declare const ESTUARINE_TOOLBAR_WORDINGS: readonly ChromeWording[];
3
5
  export declare const estuarineToolbarConfig: {
4
6
  readonly actions: [{
5
7
  id: string;
@@ -64,6 +64,30 @@ const AxisIcon = html `<svg
64
64
  <path d="M6 3v18M6 12h15" />
65
65
  <path d="M3 6l3-3 3 3M18 9l3 3-3 3" />
66
66
  </svg>`;
67
+ /** The four toolbar tooltips this file used to hard-code as English literals. */
68
+ const LIMINAL_TOGGLE = [
69
+ 'com.labre.cynefin-estuarine.estuarine.toolbar.liminal',
70
+ 'Show / hide the Liminal line',
71
+ ];
72
+ const VOLATILE_TOGGLE = [
73
+ 'com.labre.cynefin-estuarine.estuarine.toolbar.volatile',
74
+ 'Show / hide the Volatile line',
75
+ ];
76
+ const COUNTERFACTUAL_TOGGLE = [
77
+ 'com.labre.cynefin-estuarine.estuarine.toolbar.counterfactual',
78
+ 'Show / hide the Counter-factual line',
79
+ ];
80
+ const AXIS_LABELS_TOGGLE = [
81
+ 'com.labre.cynefin-estuarine.estuarine.toolbar.axis-labels',
82
+ 'Show / hide axis labels (e / t)',
83
+ ];
84
+ /** Every wording above, for `translations.ts`'s manifest contribution. */
85
+ export const ESTUARINE_TOOLBAR_WORDINGS = [
86
+ LIMINAL_TOGGLE,
87
+ VOLATILE_TOGGLE,
88
+ COUNTERFACTUAL_TOGGLE,
89
+ AXIS_LABELS_TOGGLE,
90
+ ];
67
91
  function booleanToggle(id, tooltip, icon, prop) {
68
92
  return {
69
93
  id,
@@ -93,10 +117,10 @@ function booleanToggle(id, tooltip, icon, prop) {
93
117
  export const estuarineToolbarConfig = {
94
118
  actions: [
95
119
  booleanToggle('a.toggle-resize', BOARD_RESIZE_TOGGLE, ResizeIcon, 'resizeEnabled'),
96
- booleanToggle('b.toggle-liminal', 'Show / hide the Liminal line', LiminalIcon, 'showLiminal'),
97
- booleanToggle('c.toggle-volatile', 'Show / hide the Volatile line', VolatileIcon, 'showVolatile'),
98
- booleanToggle('d.toggle-counterfactual', 'Show / hide the Counter-factual line', CounterfactualIcon, 'showCounterfactual'),
99
- booleanToggle('e.toggle-axis-labels', 'Show / hide axis labels (e / t)', AxisIcon, 'showAxisLabels'),
120
+ booleanToggle('b.toggle-liminal', LIMINAL_TOGGLE, LiminalIcon, 'showLiminal'),
121
+ booleanToggle('c.toggle-volatile', VOLATILE_TOGGLE, VolatileIcon, 'showVolatile'),
122
+ booleanToggle('d.toggle-counterfactual', COUNTERFACTUAL_TOGGLE, CounterfactualIcon, 'showCounterfactual'),
123
+ booleanToggle('e.toggle-axis-labels', AXIS_LABELS_TOGGLE, AxisIcon, 'showAxisLabels'),
100
124
  ],
101
125
  when: ctx => ctx.getSurfaceModelsByType(EstuarineElementModel).length > 0,
102
126
  };
@@ -1,3 +1,42 @@
1
1
  import { type TemplateCategory } from '@formicoidea/labre-core/gfx/template';
2
+ import { type ChromeWording } from '@formicoidea/labre-core/shared/services';
3
+ /**
4
+ * The seeds the two hand-composed templates below write into the document:
5
+ * the "Decision sorting" board's four domain stickies, and the "Constraint
6
+ * map" board's three hexagon captions. Exported so `translations.ts` derives
7
+ * the manifest entries from the SAME constants rather than restating them.
8
+ */
9
+ export declare const CYNEFIN_SEED_PROBE_LEARN: ChromeWording;
10
+ export declare const CYNEFIN_SEED_EXPERT_ANALYSIS: ChromeWording;
11
+ export declare const CYNEFIN_SEED_ACT_NOW: ChromeWording;
12
+ export declare const CYNEFIN_SEED_KNOWN_ISSUE: ChromeWording;
13
+ export declare const ESTUARINE_SEED_POLICY: ChromeWording;
14
+ export declare const ESTUARINE_SEED_HABIT: ChromeWording;
15
+ export declare const ESTUARINE_SEED_BUDGET: ChromeWording;
16
+ /** All seven, for `translations.ts`'s manifest contribution. */
17
+ export declare const CYNEFIN_ESTUARINE_TEMPLATE_SEEDS: readonly ChromeWording[];
18
+ /**
19
+ * The two Templates-panel tab names — new keys, since Cynefin and Estuarine
20
+ * share one senior-button key (`com.labre.framework.cynefin-estuarine`) that
21
+ * names neither individually (see `TemplateCategory.nameKey`). Both are proper
22
+ * nouns; the PO's French proposal for each is the identical word.
23
+ */
24
+ export declare const CYNEFIN_TEMPLATE_CATEGORY_NAME: ChromeWording;
25
+ export declare const ESTUARINE_TEMPLATE_CATEGORY_NAME: ChromeWording;
26
+ /**
27
+ * The two Templates-panel tab names (chrome, not seeds — a widget-resolved
28
+ * label, never written into a document), for `translations.ts`'s manifest.
29
+ */
30
+ export declare const CYNEFIN_ESTUARINE_TEMPLATE_CATEGORY_WORDINGS: readonly ChromeWording[];
31
+ /**
32
+ * The two hand-composed templates' own tile names (`Template.nameKey`):
33
+ * chrome, resolved by the panel widget every time it opens, never written
34
+ * into a document — distinct from {@link CYNEFIN_ESTUARINE_TEMPLATE_SEEDS}
35
+ * above, which the templates WRITE.
36
+ */
37
+ export declare const CYNEFIN_TEMPLATE_NAME_DECISION_SORTING: ChromeWording;
38
+ export declare const ESTUARINE_TEMPLATE_NAME_CONSTRAINT_MAP: ChromeWording;
39
+ /** Both tile names, for `translations.ts`'s manifest. */
40
+ export declare const CYNEFIN_ESTUARINE_TEMPLATE_NAME_WORDINGS: readonly ChromeWording[];
2
41
  export declare const cynefinTemplateCategory: TemplateCategory;
3
42
  export declare const estuarineTemplateCategory: TemplateCategory;
@@ -1,5 +1,7 @@
1
1
  import { makeTemplateSnapshot, surfaceText, templateFromCommand, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { FontFamily, ShapeStyle, TextAlign } from '@formicoidea/labre-core/model';
3
+ import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
4
+ import { translateKey, } from '@formicoidea/labre-core/shared/services';
3
5
  import { cynefinEstuarineCommands } from '../commands.js';
4
6
  import { HEX_SIZE } from '../estuarine/consts.js';
5
7
  import { CYNEFIN_H, CYNEFIN_W, cynefinBackgroundProps, ESTUARINE_MAP_H, ESTUARINE_MAP_W, estuarineHexagonBox, estuarineHexagonProps, estuarineMapProps, MAP_SCALE, } from '../presets.js';
@@ -37,7 +39,7 @@ function sticky(x, y, text) {
37
39
  roughness: 0,
38
40
  radius: 8,
39
41
  text: surfaceText(text),
40
- color: '#1a1a1a',
42
+ color: NOTATION_NEUTRALS.ink,
41
43
  fontFamily: FontFamily.Inter,
42
44
  fontSize: 18,
43
45
  textAlign: TextAlign.Center,
@@ -72,53 +74,159 @@ function caption(cx, cy, str) {
72
74
  return {
73
75
  type: 'text',
74
76
  text: surfaceText(str),
75
- color: '#1a1a1a',
77
+ color: NOTATION_NEUTRALS.ink,
76
78
  fontFamily: FontFamily.Inter,
77
79
  fontSize: 16,
78
80
  textAlign: TextAlign.Center,
79
81
  xywh: `[${x},${y},${CAPTION_W},${CAPTION_H}]`,
80
82
  };
81
83
  }
82
- /** A hand-composed template — what is left once the artefacts are derived. */
83
- function tpl(name, preview, elements) {
84
+ /**
85
+ * The seeds the two hand-composed templates below write into the document:
86
+ * the "Decision sorting" board's four domain stickies, and the "Constraint
87
+ * map" board's three hexagon captions. Exported so `translations.ts` derives
88
+ * the manifest entries from the SAME constants rather than restating them.
89
+ */
90
+ export const CYNEFIN_SEED_PROBE_LEARN = [
91
+ 'com.labre.cynefin-estuarine.seed.probe-learn',
92
+ 'Probe & learn',
93
+ ];
94
+ export const CYNEFIN_SEED_EXPERT_ANALYSIS = [
95
+ 'com.labre.cynefin-estuarine.seed.expert-analysis',
96
+ 'Expert analysis',
97
+ ];
98
+ export const CYNEFIN_SEED_ACT_NOW = [
99
+ 'com.labre.cynefin-estuarine.seed.act-now',
100
+ 'Act now',
101
+ ];
102
+ export const CYNEFIN_SEED_KNOWN_ISSUE = [
103
+ 'com.labre.cynefin-estuarine.seed.known-issue',
104
+ 'Known issue',
105
+ ];
106
+ export const ESTUARINE_SEED_POLICY = [
107
+ 'com.labre.cynefin-estuarine.seed.policy',
108
+ 'Policy',
109
+ ];
110
+ export const ESTUARINE_SEED_HABIT = [
111
+ 'com.labre.cynefin-estuarine.seed.habit',
112
+ 'Habit',
113
+ ];
114
+ export const ESTUARINE_SEED_BUDGET = [
115
+ 'com.labre.cynefin-estuarine.seed.budget',
116
+ 'Budget',
117
+ ];
118
+ /** All seven, for `translations.ts`'s manifest contribution. */
119
+ export const CYNEFIN_ESTUARINE_TEMPLATE_SEEDS = [
120
+ CYNEFIN_SEED_PROBE_LEARN,
121
+ CYNEFIN_SEED_EXPERT_ANALYSIS,
122
+ CYNEFIN_SEED_ACT_NOW,
123
+ CYNEFIN_SEED_KNOWN_ISSUE,
124
+ ESTUARINE_SEED_POLICY,
125
+ ESTUARINE_SEED_HABIT,
126
+ ESTUARINE_SEED_BUDGET,
127
+ ];
128
+ /**
129
+ * The two Templates-panel tab names — new keys, since Cynefin and Estuarine
130
+ * share one senior-button key (`com.labre.framework.cynefin-estuarine`) that
131
+ * names neither individually (see `TemplateCategory.nameKey`). Both are proper
132
+ * nouns; the PO's French proposal for each is the identical word.
133
+ */
134
+ export const CYNEFIN_TEMPLATE_CATEGORY_NAME = [
135
+ 'com.labre.cynefin-estuarine.template.category.cynefin',
136
+ 'Cynefin',
137
+ ];
138
+ export const ESTUARINE_TEMPLATE_CATEGORY_NAME = [
139
+ 'com.labre.cynefin-estuarine.template.category.estuarine',
140
+ 'Estuarine',
141
+ ];
142
+ /**
143
+ * The two Templates-panel tab names (chrome, not seeds — a widget-resolved
144
+ * label, never written into a document), for `translations.ts`'s manifest.
145
+ */
146
+ export const CYNEFIN_ESTUARINE_TEMPLATE_CATEGORY_WORDINGS = [CYNEFIN_TEMPLATE_CATEGORY_NAME, ESTUARINE_TEMPLATE_CATEGORY_NAME];
147
+ /**
148
+ * The two hand-composed templates' own tile names (`Template.nameKey`):
149
+ * chrome, resolved by the panel widget every time it opens, never written
150
+ * into a document — distinct from {@link CYNEFIN_ESTUARINE_TEMPLATE_SEEDS}
151
+ * above, which the templates WRITE.
152
+ */
153
+ export const CYNEFIN_TEMPLATE_NAME_DECISION_SORTING = [
154
+ 'com.labre.cynefin-estuarine.template.decision-sorting',
155
+ 'Decision sorting',
156
+ ];
157
+ export const ESTUARINE_TEMPLATE_NAME_CONSTRAINT_MAP = [
158
+ 'com.labre.cynefin-estuarine.template.constraint-map',
159
+ 'Constraint map',
160
+ ];
161
+ /** Both tile names, for `translations.ts`'s manifest. */
162
+ export const CYNEFIN_ESTUARINE_TEMPLATE_NAME_WORDINGS = [
163
+ CYNEFIN_TEMPLATE_NAME_DECISION_SORTING,
164
+ ESTUARINE_TEMPLATE_NAME_CONSTRAINT_MAP,
165
+ ];
166
+ /** Resolve a wording through the host, or its English fallback with no host. */
167
+ function tr(std, wording) {
168
+ return std ? translateKey(std, ...wording) : wording[1];
169
+ }
170
+ /**
171
+ * A hand-composed template — what is left once the artefacts are derived.
172
+ *
173
+ * The seeds a template writes into the document go through the translation
174
+ * seam at placement (ADR 0016); `content` stays the English build, and
175
+ * `localize` rebuilds the same snapshot with translated seeds.
176
+ */
177
+ function tpl(name, preview, build, nameKey) {
84
178
  return {
85
179
  name,
86
180
  type: 'template',
87
181
  preview,
88
- content: makeTemplateSnapshot(elements, name),
182
+ nameKey,
183
+ content: makeTemplateSnapshot(build(), name),
184
+ localize: std => makeTemplateSnapshot(build(std), name),
89
185
  };
90
186
  }
91
187
  const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
92
188
  export const cynefinTemplateCategory = {
93
189
  name: 'Cynefin',
190
+ nameKey: CYNEFIN_TEMPLATE_CATEGORY_NAME[0],
94
191
  templates: [
95
- tpl('Decision sorting', `<svg ${ATTRS} fill="none"><rect x="8" y="10" width="119" height="60" rx="4" stroke="#2a9d99" stroke-width="1.5"/><path d="M67 10 V70 M8 40 H127" stroke="#9aa0a6"/><rect x="18" y="18" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="83" y="18" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="18" y="48" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="83" y="48" width="34" height="14" rx="2" fill="#fff3b0"/></svg>`, {
192
+ tpl('Decision sorting', `<svg ${ATTRS} fill="none"><rect x="8" y="10" width="119" height="60" rx="4" stroke="#2a9d99" stroke-width="1.5"/><path d="M67 10 V70 M8 40 H127" stroke="${NOTATION_NEUTRALS.divider}"/><rect x="18" y="18" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="83" y="18" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="18" y="48" width="34" height="14" rx="2" fill="#fff3b0"/><rect x="83" y="48" width="34" height="14" rx="2" fill="#fff3b0"/></svg>`, std => ({
96
193
  bg: cynefinBackgroundProps({
97
194
  xywh: `[0,0,${CYNEFIN_W},${CYNEFIN_H}]`,
98
195
  }),
99
- s1: sticky(190, 175, 'Probe & learn'),
100
- s2: sticky(690, 175, 'Expert analysis'),
101
- s3: sticky(190, 505, 'Act now'),
102
- s4: sticky(690, 505, 'Known issue'),
103
- }),
104
- templateFromCommand(byId('cynefin-estuarine.addCynefin'), `<svg ${ATTRS} fill="none"><rect x="14" y="12" width="107" height="56" rx="4" stroke="#2a9d99" stroke-width="1.6"/><path d="M67 12 V68 M14 40 H121" stroke="#9aa0a6"/></svg>`, 'Cynefin framework'),
196
+ s1: sticky(190, 175, tr(std, CYNEFIN_SEED_PROBE_LEARN)),
197
+ s2: sticky(690, 175, tr(std, CYNEFIN_SEED_EXPERT_ANALYSIS)),
198
+ s3: sticky(190, 505, tr(std, CYNEFIN_SEED_ACT_NOW)),
199
+ s4: sticky(690, 505, tr(std, CYNEFIN_SEED_KNOWN_ISSUE)),
200
+ }), CYNEFIN_TEMPLATE_NAME_DECISION_SORTING[0]),
201
+ // No name override: it would only restate `addCynefin`'s own
202
+ // `labelFallback` as a second literal the panel could not translate — see
203
+ // `resolveTemplateName`, which now reads the command's `labelKey` instead.
204
+ templateFromCommand(byId('cynefin-estuarine.addCynefin'), `<svg ${ATTRS} fill="none"><rect x="14" y="12" width="107" height="56" rx="4" stroke="#2a9d99" stroke-width="1.6"/><path d="M67 12 V68 M14 40 H121" stroke="${NOTATION_NEUTRALS.divider}"/></svg>`),
105
205
  ],
106
206
  };
107
207
  export const estuarineTemplateCategory = {
108
208
  name: 'Estuarine',
209
+ nameKey: ESTUARINE_TEMPLATE_CATEGORY_NAME[0],
109
210
  templates: [
110
- tpl('Constraint map', `<svg ${ATTRS} fill="none"><path d="M20 12 V70 M20 70 H120" stroke="#941253" stroke-width="2"/><g fill="#34c724" stroke="#1f1f1f"><path d="M44 28 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/><path d="M74 40 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/><path d="M56 52 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/></g></svg>`, {
211
+ tpl('Constraint map', `<svg ${ATTRS} fill="none"><path d="M20 12 V70 M20 70 H120" stroke="#941253" stroke-width="2"/><g fill="#34c724" stroke="#1f1f1f"><path d="M44 28 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/><path d="M74 40 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/><path d="M56 52 l6 4 l0 8 l-6 4 l-6 -4 l0 -8 z"/></g></svg>`, std => ({
111
212
  bg: estuarineMapProps({
112
213
  xywh: `[0,0,${ESTUARINE_MAP_W},${ESTUARINE_MAP_H}]`,
113
214
  }),
114
215
  h1: hexAt(210, 280),
115
- c1: caption(210, 280, 'Policy'),
216
+ c1: caption(210, 280, tr(std, ESTUARINE_SEED_POLICY)),
116
217
  h2: hexAt(390, 420),
117
- c2: caption(390, 420, 'Habit'),
218
+ c2: caption(390, 420, tr(std, ESTUARINE_SEED_HABIT)),
118
219
  h3: hexAt(290, 580),
119
- c3: caption(290, 580, 'Budget'),
120
- }),
121
- templateFromCommand(byId('cynefin-estuarine.addEstuarineMap'), `<svg ${ATTRS} fill="none"><path d="M24 10 V70 M24 70 H120" stroke="#941253" stroke-width="2.4"/><path d="M30 52 q40 -30 84 -34" stroke="#5ecc44" stroke-width="2" fill="none"/></svg>`, 'Estuarine map'),
220
+ c3: caption(290, 580, tr(std, ESTUARINE_SEED_BUDGET)),
221
+ }), ESTUARINE_TEMPLATE_NAME_CONSTRAINT_MAP[0]),
222
+ // No name override: same reason as `addCynefin` above.
223
+ templateFromCommand(byId('cynefin-estuarine.addEstuarineMap'), `<svg ${ATTRS} fill="none"><path d="M24 10 V70 M24 70 H120" stroke="#941253" stroke-width="2.4"/><path d="M30 52 q40 -30 84 -34" stroke="#5ecc44" stroke-width="2" fill="none"/></svg>`),
224
+ // Kept, unlike the two overrides above: `addConstraintHexagon`'s own
225
+ // label is "Hexagon node" (the generic node-picker's wording), and this
226
+ // tile is specifically the CONSTRAINT preset of it — a name of its own,
227
+ // not a restatement. `resolveTemplateName` still resolves it through the
228
+ // command's `labelKey`, with THIS literal (not the command's) as the
229
+ // fallback, so the tile keeps its own English wording with no catalogue.
122
230
  templateFromCommand(byId('cynefin-estuarine.addConstraintHexagon'), `<svg ${ATTRS} fill="none"><path d="M67 24 l18 11 l0 22 l-18 11 l-18 -11 l0 -22 z" fill="#34c724" stroke="#1f1f1f" stroke-width="2"/></svg>`, 'Hexagon constraint'),
123
231
  ],
124
232
  };
@@ -1,8 +1,13 @@
1
1
  import { collectTranslationKeys, commandCategoryTranslationEntries, commandTranslationEntries, mergeTranslationEntries, } from '@formicoidea/labre-core/std';
2
2
  import { cynefinEstuarineCommands } from './commands.js';
3
+ import { CYNEFIN_CANVAS_WORDINGS } from './cynefin/consts.js';
4
+ import { CYNEFIN_TOOLBAR_WORDINGS } from './cynefin/toolbar/config.js';
5
+ import { ESTUARINE_CANVAS_WORDINGS } from './estuarine/consts.js';
3
6
  import { ESTUARINE_NUDGES } from './estuarine/nudges.js';
4
7
  import { ESTUARINE_READING } from './estuarine/reading.js';
5
8
  import { ESTUARINE_ROLES } from './estuarine/roles.js';
9
+ import { ESTUARINE_TOOLBAR_WORDINGS } from './estuarine/toolbar/config.js';
10
+ import { CYNEFIN_ESTUARINE_TEMPLATE_CATEGORY_WORDINGS, CYNEFIN_ESTUARINE_TEMPLATE_NAME_WORDINGS, CYNEFIN_ESTUARINE_TEMPLATE_SEEDS, } from './templates/index.js';
6
11
  /**
7
12
  * THIS framework's contribution to the translation-key manifest.
8
13
  *
@@ -34,4 +39,41 @@ commandCategoryTranslationEntries(cynefinEstuarineCommands), collectTranslationK
34
39
  // This profile declares no relation, so today it contributes no key of its
35
40
  // own — walking it costs nothing and means the day it gains one the
36
41
  // manifest already names it.
37
- collectTranslationKeys('reading', ESTUARINE_READING));
42
+ collectTranslationKeys('reading', ESTUARINE_READING),
43
+ // The seeds the two hand-composed templates write into the document
44
+ // (`templates/index.ts`), resolved through `Template.localize` rather than
45
+ // a creation action — no command draws these two scenes.
46
+ CYNEFIN_ESTUARINE_TEMPLATE_SEEDS.map(([key, fallback]) => ({
47
+ key,
48
+ fallback,
49
+ source: 'seed',
50
+ })),
51
+ // The Templates-panel tab names — chrome, resolved by the panel widget,
52
+ // never written into a document.
53
+ CYNEFIN_ESTUARINE_TEMPLATE_CATEGORY_WORDINGS.map(([key, fallback]) => ({
54
+ key,
55
+ fallback,
56
+ source: 'chrome',
57
+ })),
58
+ // The two hand-composed templates' own tile names — chrome as well, and
59
+ // distinct from the seeds they WRITE above.
60
+ CYNEFIN_ESTUARINE_TEMPLATE_NAME_WORDINGS.map(([key, fallback]) => ({
61
+ key,
62
+ fallback,
63
+ source: 'chrome',
64
+ })),
65
+ // The two frameworks' own toolbar tooltips.
66
+ [...CYNEFIN_TOOLBAR_WORDINGS, ...ESTUARINE_TOOLBAR_WORDINGS].map(([key, fallback]) => ({ key, fallback, source: 'chrome' })),
67
+ // Every word the Cynefin frame's canvas renderer paints (headings,
68
+ // decisions, annotations, markers) — fixed notation, not document seeds.
69
+ CYNEFIN_CANVAS_WORDINGS.map(([key, fallback]) => ({
70
+ key,
71
+ fallback,
72
+ source: 'chrome',
73
+ })),
74
+ // The Estuarine map's own three curve legends.
75
+ ESTUARINE_CANVAS_WORDINGS.map(([key, fallback]) => ({
76
+ key,
77
+ fallback,
78
+ source: 'chrome',
79
+ })));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-framework-cynefin",
3
3
  "description": "Labre cynefin-estuarine framework for @formicoidea/labre-core.",
4
- "version": "0.39.3",
4
+ "version": "0.41.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -31,7 +31,7 @@
31
31
  "dist"
32
32
  ],
33
33
  "dependencies": {
34
- "@formicoidea/labre-core": "0.39.3",
34
+ "@formicoidea/labre-core": "0.41.0",
35
35
  "@preact/signals-core": "^1.8.0",
36
36
  "lit": "^3.2.0"
37
37
  }