@formicoidea/labre-framework-cynefin 0.40.0 → 0.42.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.
@@ -0,0 +1,40 @@
1
+ import { backgroundTextHitBox } from '@formicoidea/labre-core/blocks/surface';
2
+ import { refScale } from '../utils.js';
3
+ import { DOMAINS, HEADING_SIZE, REF_H, REF_W } from './consts.js';
4
+ /**
5
+ * What a domain heading SAYS: the user's own word, else the vocabulary.
6
+ *
7
+ * The very precedence `backgroundLabelText` applies to a declared background —
8
+ * user text wins over the catalogue, the catalogue over the shipped English —
9
+ * spelled out here because this diagram has no declaration to apply it for it.
10
+ * An empty string is a real answer (the user erased the word), which is why the
11
+ * test is against `undefined` and not against falsiness.
12
+ */
13
+ export function cynefinHeadingText(model, block, translate) {
14
+ const own = model[block.prop];
15
+ return own === undefined || own === null
16
+ ? translate(block.heading)
17
+ : String(own);
18
+ }
19
+ /**
20
+ * Clickable boxes of the four domain headings, in ELEMENT-LOCAL coordinates.
21
+ *
22
+ * Built from the same reference positions and the same uniform letterbox fit
23
+ * the renderer draws them with, so the target tracks the word at any size, and
24
+ * empty while the headings are hidden — a label that is not painted may not be
25
+ * aimed at either.
26
+ */
27
+ export function cynefinLabelHits(model, w, h, translate) {
28
+ if (!model.showTitles)
29
+ return [];
30
+ const { s, ox, oy } = refScale(w, h, REF_W, REF_H);
31
+ return DOMAINS.map(block => {
32
+ const text = cynefinHeadingText(model, block, translate);
33
+ return {
34
+ id: block.prop,
35
+ prop: block.prop,
36
+ text,
37
+ ...backgroundTextHitBox(text, HEADING_SIZE * s, ox + block.x * s, oy + block.hy * s),
38
+ };
39
+ });
40
+ }
@@ -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,42 @@ 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;
53
- /** Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing. */
60
+ /** Every {@link LABELS} wording, for `translations.ts`'s manifest. */
61
+ export declare const ESTUARINE_CANVAS_WORDINGS: readonly ChromeWording[];
62
+ /**
63
+ * Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing.
64
+ *
65
+ * `prop` names the model key a double-click renames the legend into (issue
66
+ * #355), and `visibleProp` the toggle that shows it — the legend is painted
67
+ * with its curve and hidden with it, so it is aimable exactly when it is drawn.
68
+ *
69
+ * The three legends and nothing else: the italic `e` / `t` axis letters are
70
+ * notation, not words (PO decision, and the reason they carry no i18n key
71
+ * either), so they are not renamable.
72
+ */
54
73
  export declare const LABELS: {
55
74
  readonly counterfactual: {
56
- readonly text: "COUNTER FACTUAL";
75
+ readonly wording: ChromeWording;
76
+ readonly prop: "counterfactualLabel";
77
+ readonly visibleProp: "showCounterfactual";
57
78
  readonly x: 422;
58
79
  readonly y: 25;
59
80
  readonly size: 20;
60
81
  readonly color: "#1a1a1a";
61
82
  };
62
83
  readonly liminal: {
63
- readonly text: "LIMINAL";
84
+ readonly wording: ChromeWording;
85
+ readonly prop: "liminalLabel";
86
+ readonly visibleProp: "showLiminal";
64
87
  readonly x: 316;
65
88
  readonly y: 192;
66
89
  readonly size: 18;
67
90
  readonly color: "#2e7d32";
68
91
  };
69
92
  readonly volatile: {
70
- readonly text: "VOLATILE";
93
+ readonly wording: ChromeWording;
94
+ readonly prop: "volatileLabel";
95
+ readonly visibleProp: "showVolatile";
71
96
  readonly x: 219;
72
97
  readonly y: 783;
73
98
  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,58 @@ 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;
57
- /** Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing. */
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
+ ];
81
+ /**
82
+ * Uppercase legends: anchored centre, alphabetic baseline, with letter-spacing.
83
+ *
84
+ * `prop` names the model key a double-click renames the legend into (issue
85
+ * #355), and `visibleProp` the toggle that shows it — the legend is painted
86
+ * with its curve and hidden with it, so it is aimable exactly when it is drawn.
87
+ *
88
+ * The three legends and nothing else: the italic `e` / `t` axis letters are
89
+ * notation, not words (PO decision, and the reason they carry no i18n key
90
+ * either), so they are not renamable.
91
+ */
58
92
  export const LABELS = {
59
93
  counterfactual: {
60
- text: 'COUNTER FACTUAL',
94
+ wording: LABEL_COUNTERFACTUAL,
95
+ prop: 'counterfactualLabel',
96
+ visibleProp: 'showCounterfactual',
61
97
  x: 422,
62
98
  y: 25,
63
99
  size: 20,
64
100
  color: COLORS.label,
65
101
  },
66
102
  liminal: {
67
- text: 'LIMINAL',
103
+ wording: LABEL_LIMINAL,
104
+ prop: 'liminalLabel',
105
+ visibleProp: 'showLiminal',
68
106
  x: 316,
69
107
  y: 192,
70
108
  size: 18,
71
109
  color: COLORS.liminalLabel,
72
110
  },
73
111
  volatile: {
74
- text: 'VOLATILE',
112
+ wording: LABEL_VOLATILE,
113
+ prop: 'volatileLabel',
114
+ visibleProp: 'showVolatile',
75
115
  x: 219,
76
116
  y: 783,
77
117
  size: 20,
@@ -1,6 +1,8 @@
1
- import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
1
+ import { ElementRendererExtension, taggedPath2D, } 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';
5
+ import { estuarineLegendText } from './labels.js';
4
6
  /**
5
7
  * The three reference curves are drawn as a permanent GHOST (PO arbitration,
6
8
  * 26/08/2026): dashed, translucent, never solid again.
@@ -39,21 +41,21 @@ export function estuarineCurves() {
39
41
  return (_curves ??= [
40
42
  {
41
43
  key: 'liminal',
42
- path: new Path2D(LIMINAL_PATH),
44
+ path: taggedPath2D(LIMINAL_PATH),
43
45
  color: COLORS.liminal,
44
46
  width: LIMINAL_WIDTH,
45
47
  visibleProp: 'showLiminal',
46
48
  },
47
49
  {
48
50
  key: 'volatile',
49
- path: new Path2D(VOLATILE_PATH),
51
+ path: taggedPath2D(VOLATILE_PATH),
50
52
  color: COLORS.volatile,
51
53
  width: VOLATILE_WIDTH,
52
54
  visibleProp: 'showVolatile',
53
55
  },
54
56
  {
55
57
  key: 'counterfactual',
56
- path: new Path2D(COUNTERFACTUAL_PATH),
58
+ path: taggedPath2D(COUNTERFACTUAL_PATH),
57
59
  color: COLORS.counterfactual,
58
60
  width: COUNTERFACTUAL_WIDTH,
59
61
  visibleProp: 'showCounterfactual',
@@ -99,7 +101,8 @@ export function applyEstuarineTransform(ctx, w, h) {
99
101
  * the three curves, which are boundaries across the plane and must cover
100
102
  * whatever plane the user has made.
101
103
  */
102
- export const estuarine = (model, ctx, matrix) => {
104
+ export const estuarine = (model, ctx, matrix, renderer) => {
105
+ const tr = (wording) => renderer ? translateKey(renderer.std, ...wording) : wording[1];
103
106
  const [, , w, h] = model.deserializedXYWH;
104
107
  const cx = w / 2;
105
108
  const cy = h / 2;
@@ -144,7 +147,8 @@ export const estuarine = (model, ctx, matrix) => {
144
147
  // Uppercase legend (centre-anchored, alphabetic baseline, letter-spaced).
145
148
  // Anchored proportionally, typed isotropically — never inside the stretch.
146
149
  const hasSpacing = 'letterSpacing' in ctx;
147
- const legend = (l) => {
150
+ const legend = (key) => {
151
+ const l = LABELS[key];
148
152
  ctx.fillStyle = l.color;
149
153
  ctx.font = `600 ${l.size * fit.strokeScale}px ${FONT_FAMILY}`;
150
154
  ctx.textAlign = 'center';
@@ -152,7 +156,9 @@ export const estuarine = (model, ctx, matrix) => {
152
156
  if (hasSpacing) {
153
157
  ctx.letterSpacing = `${LABEL_LETTER_SPACING * fit.strokeScale}px`;
154
158
  }
155
- ctx.fillText(l.text, ax(l.x), ay(l.y));
159
+ // The user's own word when they have renamed this legend, the catalogue's
160
+ // otherwise — the same precedence a declared background applies.
161
+ ctx.fillText(estuarineLegendText(model, key, tr), ax(l.x), ay(l.y));
156
162
  if (hasSpacing)
157
163
  ctx.letterSpacing = '0px';
158
164
  };
@@ -171,7 +177,7 @@ export const estuarine = (model, ctx, matrix) => {
171
177
  ctx.setLineDash([...GHOST_DASH]);
172
178
  ctx.stroke(curve.path);
173
179
  ctx.restore();
174
- legend(LABELS[curve.key]);
180
+ legend(curve.key);
175
181
  }
176
182
  // ── Italic e / t axis letters ───────────────────────────────────────
177
183
  if (model.showAxisLabels) {
@@ -1,11 +1,19 @@
1
+ import type { EditableBackgroundLabel } from '@formicoidea/labre-core/blocks/surface';
2
+ import { FrameworkBackgroundView } from '@formicoidea/labre-core/blocks/surface';
1
3
  import type { EstuarineElementModel } from '@formicoidea/labre-core/model';
2
- import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
3
4
  /**
4
5
  * View for the Estuarine background. Registering it ensures `gfx.view.get(model)`
5
6
  * returns a view (required so move / select interactions work).
7
+ *
8
+ * Extends {@link FrameworkBackgroundView} rather than
9
+ * `DeclaredBackgroundView`: the map is a reproduction of the official SVG with
10
+ * a stretch of its own and no `FrameworkBackgroundDef` behind it, so it says
11
+ * where its three legends are through the very fit the renderer anchors them
12
+ * with. The gesture itself is the shared one (issue #355).
6
13
  */
7
- export declare class EstuarineView extends GfxElementModelView<EstuarineElementModel> {
14
+ export declare class EstuarineView extends FrameworkBackgroundView<EstuarineElementModel> {
8
15
  static type: string;
16
+ protected labelAt(lx: number, ly: number, w: number, h: number): EditableBackgroundLabel | null;
9
17
  }
10
18
  /**
11
19
  * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
@@ -1,10 +1,25 @@
1
- import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
1
+ import { FrameworkBackgroundView, hitTestBackgroundLabel, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { translateKey, } from '@formicoidea/labre-core/shared/services';
3
+ import { GfxViewInteractionExtension } from '@formicoidea/labre-core/std/gfx';
4
+ import { estuarineFit } from './element-renderer.js';
5
+ import { estuarineLabelHits } from './labels.js';
2
6
  /**
3
7
  * View for the Estuarine background. Registering it ensures `gfx.view.get(model)`
4
8
  * returns a view (required so move / select interactions work).
9
+ *
10
+ * Extends {@link FrameworkBackgroundView} rather than
11
+ * `DeclaredBackgroundView`: the map is a reproduction of the official SVG with
12
+ * a stretch of its own and no `FrameworkBackgroundDef` behind it, so it says
13
+ * where its three legends are through the very fit the renderer anchors them
14
+ * with. The gesture itself is the shared one (issue #355).
5
15
  */
6
- export class EstuarineView extends GfxElementModelView {
16
+ export class EstuarineView extends FrameworkBackgroundView {
7
17
  static { this.type = 'estuarine'; }
18
+ labelAt(lx, ly, w, h) {
19
+ const translate = (wording) => translateKey(this.gfx.std, ...wording);
20
+ const hit = hitTestBackgroundLabel(estuarineLabelHits(this.model, estuarineFit(w, h), translate), lx, ly);
21
+ return hit ? { prop: hit.prop, text: hit.text } : null;
22
+ }
8
23
  }
9
24
  /**
10
25
  * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
@@ -0,0 +1,39 @@
1
+ import type { BackgroundLabelHit } from '@formicoidea/labre-core/blocks/surface';
2
+ import type { EstuarineElementModel } from '@formicoidea/labre-core/model';
3
+ import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
4
+ import { LABELS } from './consts.js';
5
+ /** Resolves a wording against the host's catalogue, or falls back to English. */
6
+ export type EstuarineWordingResolver = (wording: ChromeWording) => string;
7
+ /** The three curve legends, in the order the renderer paints them. */
8
+ export type EstuarineLegendKey = keyof typeof LABELS;
9
+ /**
10
+ * Only the factors the legends need out of `EstuarineFit`.
11
+ *
12
+ * Taken as a parameter rather than imported: the fit is computed in
13
+ * `./element-renderer.ts`, which reads this module to know what a legend says,
14
+ * and a module cycle to save two fields would be a poor trade.
15
+ */
16
+ export interface EstuarineLegendFit {
17
+ sx: number;
18
+ sy: number;
19
+ /** The one undeformed factor — type is never stretched. */
20
+ strokeScale: number;
21
+ }
22
+ /**
23
+ * What a legend SAYS: the user's own word, else the vocabulary.
24
+ *
25
+ * The very precedence `backgroundLabelText` applies to a declared background —
26
+ * user text wins over the catalogue, the catalogue over the shipped English —
27
+ * spelled out here because this map has no declaration to apply it for it. An
28
+ * empty string is a real answer (the user erased the word), which is why the
29
+ * test is against `undefined` and not against falsiness.
30
+ */
31
+ export declare function estuarineLegendText(model: EstuarineElementModel, key: EstuarineLegendKey, translate: EstuarineWordingResolver): string;
32
+ /**
33
+ * Clickable boxes of the visible legends, in ELEMENT-LOCAL coordinates.
34
+ *
35
+ * Anchored through the same fit the renderer anchors them with: proportionally
36
+ * in both directions, typed isotropically. A legend whose curve is switched off
37
+ * is not painted and is therefore not aimable either.
38
+ */
39
+ export declare function estuarineLabelHits(model: EstuarineElementModel, fit: EstuarineLegendFit, translate: EstuarineWordingResolver): BackgroundLabelHit[];
@@ -0,0 +1,49 @@
1
+ import { backgroundTextHitBox } from '@formicoidea/labre-core/blocks/surface';
2
+ import { LABEL_LETTER_SPACING, LABELS } from './consts.js';
3
+ /**
4
+ * What a legend SAYS: the user's own word, else the vocabulary.
5
+ *
6
+ * The very precedence `backgroundLabelText` applies to a declared background —
7
+ * user text wins over the catalogue, the catalogue over the shipped English —
8
+ * spelled out here because this map has no declaration to apply it for it. An
9
+ * empty string is a real answer (the user erased the word), which is why the
10
+ * test is against `undefined` and not against falsiness.
11
+ */
12
+ export function estuarineLegendText(model, key, translate) {
13
+ const label = LABELS[key];
14
+ const own = model[label.prop];
15
+ return own === undefined || own === null
16
+ ? translate(label.wording)
17
+ : String(own);
18
+ }
19
+ /**
20
+ * Clickable boxes of the visible legends, in ELEMENT-LOCAL coordinates.
21
+ *
22
+ * Anchored through the same fit the renderer anchors them with: proportionally
23
+ * in both directions, typed isotropically. A legend whose curve is switched off
24
+ * is not painted and is therefore not aimable either.
25
+ */
26
+ export function estuarineLabelHits(model, fit, translate) {
27
+ const props = model;
28
+ const hits = [];
29
+ for (const key of Object.keys(LABELS)) {
30
+ const label = LABELS[key];
31
+ if (!props[label.visibleProp])
32
+ continue;
33
+ const text = estuarineLegendText(model, key, translate);
34
+ const box = backgroundTextHitBox(text, label.size * fit.strokeScale, label.x * fit.sx, label.y * fit.sy, 'center');
35
+ // A legend is letter-spaced, so it is wider than the shared estimate: half
36
+ // of the extra width on each side, since the word is centred on its anchor.
37
+ const spread = (Math.max(text.length - 1, 0) * LABEL_LETTER_SPACING * fit.strokeScale) /
38
+ 2;
39
+ hits.push({
40
+ id: key,
41
+ prop: label.prop,
42
+ text,
43
+ ...box,
44
+ minX: box.minX - spread,
45
+ maxX: box.maxX + spread,
46
+ });
47
+ }
48
+ return hits;
49
+ }
@@ -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;