@formicoidea/labre-framework-wardley 0.60.0 → 1.0.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/commands.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
2
2
  import { activateWardleyAreaPolygon, activateWardleyConnector, createWardleyAccelerator, createWardleyArea, createWardleyBackground, createWardleyInertia, createWardleyMarket, createWardleyNode, createWardleyPipeline, createWardleyPorter, exportOwmFile, importOwmFile, importWardleySvgFile, wardleyMapsSelected, } from './actions.js';
3
- import { WARDLEY_LEGEND_BOX, wardleyLegendEntry, } from './legend.js';
3
+ import { WARDLEY_LEGEND_BOX, wardleyLegendEntries, } from './legend.js';
4
4
  import { WARDLEY_ROLE, WARDLEY_ROLES } from './roles.js';
5
5
  import { wardleyAcceleratorIcon, wardleyAnchorIcon, wardleyAreaPolygonIcon, wardleyAreaRectIcon, wardleyArrowIcon, wardleyBackgroundIcon, wardleyDeceleratorIcon, wardleyBenefitIcon, wardleyComponentIcon, wardleyEcosystemIcon, wardleyEvolutionGradientIcon, wardleyExportOwmIcon, wardleyImportOwmIcon, wardleyImportSvgIcon, wardleyInertiaIcon, wardleyLinkIcon, wardleyMarketIcon, wardleyMethodIcon, wardleyOpportunityIcon, wardleyPipelineIcon, wardleyPorterIcon, wardleyToolbarIcon, } from './toolbar/icons.js';
6
6
  const SPECS = [
@@ -267,7 +267,7 @@ const toolboxCommands = SPECS.map((spec, order) => ({
267
267
  // the sheet's own command carries the box the rows are drawn in. Which
268
268
  // sub-title a row files under is `category`, so the sections are the very
269
269
  // groups the catalogue already shows.
270
- ...(spec.legendRole ? { legend: wardleyLegendEntry(spec.legendRole) } : {}),
270
+ ...(spec.legendRole ? { legend: wardleyLegendEntries(spec.legendRole) } : {}),
271
271
  ...(spec.id === 'addBackground' ? { legendBox: WARDLEY_LEGEND_BOX } : {}),
272
272
  }));
273
273
  /**
@@ -7,6 +7,9 @@ import type { NotationDarkPalette } from '@formicoidea/labre-core/blocks/surface
7
7
  * than white in light: the gradient keeps its direction, and the dark
8
8
  * selection accent keeps 3:1 on every band (`notation-dark-palettes`).
9
9
  * - The greys a node or a link is painted in keep the contrast they have on
10
- * white, on the other side of the card.
10
+ * white, on the other side of the card. The buying-policy ramp (make, buy,
11
+ * outsource: `METHOD_STYLE`, ADR 0046) keeps its ORDER that way too: its
12
+ * darkest grey on white becomes its lightest on the dark card, so outsource
13
+ * is still the step furthest from the paper.
11
14
  */
12
15
  export declare const WARDLEY_DARK_PALETTE: NotationDarkPalette;
@@ -1,6 +1,6 @@
1
1
  import { COLORS } from './consts.js';
2
2
  import { PORTER_PANEL } from './legend.js';
3
- import { ACCELERATOR_FILL, LINK_GREY, METHOD_FILL } from './node/consts.js';
3
+ import { ACCELERATOR_FILL, LINK_GREY, METHOD_BUY_FILL, METHOD_FILL, METHOD_OUTSOURCE_FILL, } from './node/consts.js';
4
4
  /**
5
5
  * Wardley on a dark canvas (ADR 0039). The neutrals come from the shared
6
6
  * scale; these are the map's own values that would vanish on the dark card.
@@ -9,7 +9,10 @@ import { ACCELERATOR_FILL, LINK_GREY, METHOD_FILL } from './node/consts.js';
9
9
  * than white in light: the gradient keeps its direction, and the dark
10
10
  * selection accent keeps 3:1 on every band (`notation-dark-palettes`).
11
11
  * - The greys a node or a link is painted in keep the contrast they have on
12
- * white, on the other side of the card.
12
+ * white, on the other side of the card. The buying-policy ramp (make, buy,
13
+ * outsource: `METHOD_STYLE`, ADR 0046) keeps its ORDER that way too: its
14
+ * darkest grey on white becomes its lightest on the dark card, so outsource
15
+ * is still the step furthest from the paper.
13
16
  */
14
17
  export const WARDLEY_DARK_PALETTE = {
15
18
  framework: 'wardley',
@@ -20,6 +23,8 @@ export const WARDLEY_DARK_PALETTE = {
20
23
  [COLORS.band[3]]: '#020a16',
21
24
  [LINK_GREY]: '#909090',
22
25
  [METHOD_FILL]: '#313131',
26
+ [METHOD_BUY_FILL]: '#6b6b6b',
27
+ [METHOD_OUTSOURCE_FILL]: '#b5b5b5',
23
28
  [ACCELERATOR_FILL]: '#434343',
24
29
  [PORTER_PANEL.backing]: '#26282b',
25
30
  },
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ export { WARDLEY_ROLE, WARDLEY_ROLES, type WardleyRole, type WardleyRoleId, } fr
2
2
  export { WARDLEY_AUDIT_CRITERIA } from './audit-criteria.js';
3
3
  export { wardleyCommandIcons, wardleyCommands } from './commands.js';
4
4
  export { wardleyTranslationEntries } from './translations.js';
5
- export { WARDLEY_COMPETITION, WARDLEY_COMPETITION_TAG_ID, WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
5
+ export { WARDLEY_COMPETITION, WARDLEY_COMPETITION_TAG_ID, WARDLEY_METHOD, WARDLEY_METHOD_TAG_ID, WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
6
+ export { type WardleyMethodFamily, wardleyMethodOf, wardleyMethodParts, type WardleyMethodParts, type WardleyMethodPhase, } from './method.js';
6
7
  export { competitionOfPorterLetter, porterLetterOfCompetition, type WardleyPorterLetter, } from './porter-competition.js';
7
8
  export { WARDLEY_PROFILES } from './profiles.js';
8
9
  export { WARDLEY_NUDGES } from './nudges.js';
@@ -29,6 +30,8 @@ export { exportWardleyOwm, exportWardleyOwmWithWarnings, OWM_SCOPE, type OwmPlot
29
30
  export { wardleyBoardFrom } from './export-board.js';
30
31
  export { WARDLEY_AXIS_SEED, WARDLEY_TOOLBOX, wardleyAreaElement, wardleyChangeArrowElement, wardleyDependencyElement, wardleyInertiaElement, wardleyMapElement, type WardleyMapTexts, wardleyMarketElements, type WardleyNodeItemKind, wardleyNodeElements, wardleyPipelineElements, wardleyPorterElements, } from './toolbox.js';
31
32
  export { importWardleyOwm } from './import.js';
33
+ export { exportWardleyRendererJson } from './renderer-json-export.js';
34
+ export { importWardleyRendererJson } from './renderer-json-import.js';
32
35
  export { importWardleySvg } from './svg-import.js';
33
36
  export { wardleyBoardOf, wardleyExportElementsOf, wardleyMapsSelected, } from './actions.js';
34
- export { WARDLEY_INTERCHANGE, WARDLEY_OWM_EXPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange.js';
37
+ export { WARDLEY_INTERCHANGE, WARDLEY_JSON_EXPORT, WARDLEY_JSON_EXTENSION, WARDLEY_JSON_FORMAT, WARDLEY_JSON_IMPORT, WARDLEY_JSON_MIME, WARDLEY_OWM_EXPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange.js';
package/dist/index.js CHANGED
@@ -2,7 +2,11 @@ export { WARDLEY_ROLE, WARDLEY_ROLES, } from './roles.js';
2
2
  export { WARDLEY_AUDIT_CRITERIA } from './audit-criteria.js';
3
3
  export { wardleyCommandIcons, wardleyCommands } from './commands.js';
4
4
  export { wardleyTranslationEntries } from './translations.js';
5
- export { WARDLEY_COMPETITION, WARDLEY_COMPETITION_TAG_ID, WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
5
+ export { WARDLEY_COMPETITION, WARDLEY_COMPETITION_TAG_ID, WARDLEY_METHOD, WARDLEY_METHOD_TAG_ID, WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
6
+ // The method value and the renderer JSON's `{ category, recommendation }`
7
+ // pair, both directions, for an importer — and a host reading a map without
8
+ // an editor — to write and read the tag without re-deriving the table.
9
+ export { wardleyMethodOf, wardleyMethodParts, } from './method.js';
6
10
  // The letter and the tag, related in both directions. Exported for the reason
7
11
  // the presets are: a host laying out or reading a map without an editor —
8
12
  // labre-mcp does — must be able to say which force a glyph marks without
@@ -42,6 +46,9 @@ export { wardleyBoardFrom } from './export-board.js';
42
46
  // The toolbar as pure builders (`docs/adr/0045`), also on `./interchange`.
43
47
  export { WARDLEY_AXIS_SEED, WARDLEY_TOOLBOX, wardleyAreaElement, wardleyChangeArrowElement, wardleyDependencyElement, wardleyInertiaElement, wardleyMapElement, wardleyMarketElements, wardleyNodeElements, wardleyPipelineElements, wardleyPorterElements, } from './toolbox.js';
44
48
  export { importWardleyOwm } from './import.js';
49
+ // The renderer JSON, both ways, pure for the same reason (ADR 0012).
50
+ export { exportWardleyRendererJson } from './renderer-json-export.js';
51
+ export { importWardleyRendererJson } from './renderer-json-import.js';
45
52
  // The SVG reader that recognises a map (ADR 0032), pure for the same reason:
46
53
  // labre-mcp calls the very function the command does.
47
54
  export { importWardleySvg } from './svg-import.js';
@@ -53,4 +60,4 @@ export { wardleyBoardOf, wardleyExportElementsOf, wardleyMapsSelected, } from '.
53
60
  // DSL both ways, and the visual-tier SVG fallback. Exported whole so a host can
54
61
  // ask what Wardley can read and write without mounting an editor, and call it
55
62
  // without one either (P3).
56
- export { WARDLEY_INTERCHANGE, WARDLEY_OWM_EXPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange.js';
63
+ export { WARDLEY_INTERCHANGE, WARDLEY_JSON_EXPORT, WARDLEY_JSON_EXTENSION, WARDLEY_JSON_FORMAT, WARDLEY_JSON_IMPORT, WARDLEY_JSON_MIME, WARDLEY_OWM_EXPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange.js';
@@ -1,11 +1,13 @@
1
- import type { InterchangeFormat, InterchangeImportCapability } from '@formicoidea/labre-core/interchange';
1
+ import type { InterchangeExportCapability, InterchangeFormat, InterchangeImportCapability } from '@formicoidea/labre-core/interchange';
2
2
  /**
3
- * `@labre/affine-gfx-wardley/interchange` — Wardley's formats and its two
4
- * READERS, with no editor behind them (ADR 0012, P3 as amended 2026-10-14).
3
+ * `@labre/affine-gfx-wardley/interchange` — Wardley's formats, its READERS
4
+ * and the one writer that needs no editor, with no editor behind them
5
+ * (ADR 0012, P3 as amended 2026-10-14).
5
6
  *
6
- * What a server needs to read an `.owm` or an `.svg` as a map: the format
7
- * declarations and the import capabilities, whose `run` is a pure function of
8
- * text. The OWM EXPORT capability is not here: it picks the artefacts out of
7
+ * What a server needs to read an `.owm`, an `.svg` or a renderer `.json` as a
8
+ * map: the format declarations and the import capabilities, whose `run` is a
9
+ * pure function of text, plus the renderer JSON's export, which reads stored
10
+ * fields only (see its declaration). The OWM EXPORT capability is not here: it picks the artefacts out of
9
11
  * live element models (`wardleyBoardFrom`, by `instanceof`), which is the
10
12
  * editor graph this entry exists to leave out. It stays in `interchange.ts`,
11
13
  * which re-exports everything below. The serializer itself
@@ -62,4 +64,28 @@ export declare const WARDLEY_SVG_FORMAT: InterchangeFormat;
62
64
  * {@link WARDLEY_OWM_IMPORT} beside it stays the reference Wardley import.
63
65
  */
64
66
  export declare const WARDLEY_SVG_IMPORT: InterchangeImportCapability;
67
+ /**
68
+ * The JSON language of `@formicoidea/wardley-map-renderer`. **Semantic**: an
69
+ * `(evolution, visibility)` pair IS a position. Unlike OWM it keeps nothing
70
+ * it cannot show: no foreign id and no foreign field rides on an element, and
71
+ * what the canvas cannot display is dropped and reported (ADR 0012,
72
+ * amendment of 2026-10-28 under P1).
73
+ *
74
+ * Both directions are here, unlike OWM's: the writer reads elements by their
75
+ * stored fields rather than by `instanceof`, so it loads no element class and
76
+ * a host with no editor exports with it too.
77
+ */
78
+ export declare const WARDLEY_JSON_EXTENSION = ".json";
79
+ export declare const WARDLEY_JSON_MIME = "application/json";
80
+ export declare const WARDLEY_JSON_FORMAT: InterchangeFormat;
81
+ /**
82
+ * `wardley:json:import` — a renderer JSON map, drawn with the toolbar's own
83
+ * builders: an evolution is the change arrow dropped on its target point,
84
+ * never a second component (`renderer-json-import.ts`).
85
+ */
86
+ export declare const WARDLEY_JSON_IMPORT: InterchangeImportCapability;
87
+ /** `wardley:json:export` — the map back in the renderer's language. */
88
+ export declare const WARDLEY_JSON_EXPORT: InterchangeExportCapability;
89
+ export { exportWardleyRendererJson } from './renderer-json-export.js';
90
+ export { importWardleyRendererJson } from './renderer-json-import.js';
65
91
  export { WARDLEY_AXIS_SEED, WARDLEY_TOOLBOX, wardleyAreaElement, wardleyChangeArrowElement, wardleyDependencyElement, wardleyInertiaElement, wardleyMapElement, type WardleyMapTexts, wardleyMarketElements, type WardleyNodeItemKind, wardleyNodeElements, wardleyPipelineElements, wardleyPorterElements, } from './toolbox.js';
@@ -1,14 +1,18 @@
1
1
  import { interchangeCapabilityId, SVG_SKETCH_EXTENSION, SVG_SKETCH_FORMAT_ID, SVG_SKETCH_MIME, } from '@formicoidea/labre-core/interchange';
2
- import { WARDLEY_OWM_FORMAT_ID } from './export.js';
2
+ import { WARDLEY_OWM_FORMAT_ID, wardleySafeFilename } from './export.js';
3
3
  import { importWardleyOwm } from './import.js';
4
+ import { exportWardleyRendererJson } from './renderer-json-export.js';
5
+ import { importWardleyRendererJson } from './renderer-json-import.js';
4
6
  import { importWardleySvg } from './svg-import.js';
5
7
  /**
6
- * `@labre/affine-gfx-wardley/interchange` — Wardley's formats and its two
7
- * READERS, with no editor behind them (ADR 0012, P3 as amended 2026-10-14).
8
+ * `@labre/affine-gfx-wardley/interchange` — Wardley's formats, its READERS
9
+ * and the one writer that needs no editor, with no editor behind them
10
+ * (ADR 0012, P3 as amended 2026-10-14).
8
11
  *
9
- * What a server needs to read an `.owm` or an `.svg` as a map: the format
10
- * declarations and the import capabilities, whose `run` is a pure function of
11
- * text. The OWM EXPORT capability is not here: it picks the artefacts out of
12
+ * What a server needs to read an `.owm`, an `.svg` or a renderer `.json` as a
13
+ * map: the format declarations and the import capabilities, whose `run` is a
14
+ * pure function of text, plus the renderer JSON's export, which reads stored
15
+ * fields only (see its declaration). The OWM EXPORT capability is not here: it picks the artefacts out of
12
16
  * live element models (`wardleyBoardFrom`, by `instanceof`), which is the
13
17
  * editor graph this entry exists to leave out. It stays in `interchange.ts`,
14
18
  * which re-exports everything below. The serializer itself
@@ -89,6 +93,61 @@ export const WARDLEY_SVG_IMPORT = {
89
93
  direction: 'import',
90
94
  run: importWardleySvg,
91
95
  };
96
+ /* ── The wardley-map-renderer JSON (semantic) ─────────────────────────── */
97
+ /**
98
+ * The JSON language of `@formicoidea/wardley-map-renderer`. **Semantic**: an
99
+ * `(evolution, visibility)` pair IS a position. Unlike OWM it keeps nothing
100
+ * it cannot show: no foreign id and no foreign field rides on an element, and
101
+ * what the canvas cannot display is dropped and reported (ADR 0012,
102
+ * amendment of 2026-10-28 under P1).
103
+ *
104
+ * Both directions are here, unlike OWM's: the writer reads elements by their
105
+ * stored fields rather than by `instanceof`, so it loads no element class and
106
+ * a host with no editor exports with it too.
107
+ */
108
+ export const WARDLEY_JSON_EXTENSION = '.json';
109
+ export const WARDLEY_JSON_MIME = 'application/json';
110
+ export const WARDLEY_JSON_FORMAT = {
111
+ id: 'json',
112
+ tier: 'semantic',
113
+ extensions: [WARDLEY_JSON_EXTENSION],
114
+ mime: WARDLEY_JSON_MIME,
115
+ };
116
+ /**
117
+ * `wardley:json:import` — a renderer JSON map, drawn with the toolbar's own
118
+ * builders: an evolution is the change arrow dropped on its target point,
119
+ * never a second component (`renderer-json-import.ts`).
120
+ */
121
+ export const WARDLEY_JSON_IMPORT = {
122
+ id: interchangeCapabilityId('wardley', WARDLEY_JSON_FORMAT.id, 'import'),
123
+ framework: 'wardley',
124
+ format: WARDLEY_JSON_FORMAT,
125
+ direction: 'import',
126
+ run: importWardleyRendererJson,
127
+ };
128
+ /** The board as a renderer JSON map; `warnings` only when something was lost. */
129
+ const runWardleyJsonExport = (elements, context) => {
130
+ const name = wardleySafeFilename(context.name);
131
+ const { text, warnings } = exportWardleyRendererJson(elements, {
132
+ name: context.name,
133
+ });
134
+ return {
135
+ text,
136
+ filename: `${name}${WARDLEY_JSON_EXTENSION}`,
137
+ mime: WARDLEY_JSON_MIME,
138
+ ...(warnings.length > 0 ? { warnings } : {}),
139
+ };
140
+ };
141
+ /** `wardley:json:export` — the map back in the renderer's language. */
142
+ export const WARDLEY_JSON_EXPORT = {
143
+ id: interchangeCapabilityId('wardley', WARDLEY_JSON_FORMAT.id, 'export'),
144
+ framework: 'wardley',
145
+ format: WARDLEY_JSON_FORMAT,
146
+ direction: 'export',
147
+ run: runWardleyJsonExport,
148
+ };
149
+ export { exportWardleyRendererJson } from './renderer-json-export.js';
150
+ export { importWardleyRendererJson } from './renderer-json-import.js';
92
151
  // The toolbar as pure builders (`docs/adr/0045`): what a host with no editor
93
152
  // places to draw the very elements a user places from the toolbar.
94
153
  export { WARDLEY_AXIS_SEED, WARDLEY_TOOLBOX, wardleyAreaElement, wardleyChangeArrowElement, wardleyDependencyElement, wardleyInertiaElement, wardleyMapElement, wardleyMarketElements, wardleyNodeElements, wardleyPipelineElements, wardleyPorterElements, } from './toolbox.js';
@@ -1,5 +1,5 @@
1
1
  import type { InterchangeCapability, InterchangeExportCapability } from '@formicoidea/labre-core/blocks/surface';
2
- export { WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
2
+ export { WARDLEY_JSON_EXPORT, WARDLEY_JSON_EXTENSION, WARDLEY_JSON_FORMAT, WARDLEY_JSON_IMPORT, WARDLEY_JSON_MIME, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
3
3
  /** `wardley:owm:export` — the row that replaces labre-mcp's own serializer. */
4
4
  export declare const WARDLEY_OWM_EXPORT: InterchangeExportCapability;
5
5
  /** Everything Wardley registers, in one list the view extension hands over. */
@@ -1,12 +1,15 @@
1
1
  import { interchangeCapabilityId } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { exportWardleyOwmWithWarnings, wardleySafeFilename } from './export.js';
3
3
  import { wardleyBoardFrom } from './export-board.js';
4
- import { WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
5
- export { WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
4
+ import { WARDLEY_JSON_EXPORT, WARDLEY_JSON_IMPORT, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
5
+ export { WARDLEY_JSON_EXPORT, WARDLEY_JSON_EXTENSION, WARDLEY_JSON_FORMAT, WARDLEY_JSON_IMPORT, WARDLEY_JSON_MIME, WARDLEY_OWM_EXTENSION, WARDLEY_OWM_FORMAT, WARDLEY_OWM_IMPORT, WARDLEY_OWM_MIME, WARDLEY_SVG_FORMAT, WARDLEY_SVG_IMPORT, } from './interchange-headless.js';
6
6
  /**
7
7
  * Wardley's entries in the interchange registry (`docs/adr/0012`, P1).
8
8
  *
9
- * Two formats and three rows. Both directions of the OWM DSL — the export is
9
+ * Three formats and five rows. Both directions of the renderer JSON
10
+ * (`renderer-json-import.ts`, `renderer-json-export.ts`), the language the
11
+ * workspace connector trades maps in, built with the toolbar's builders. Both
12
+ * directions of the OWM DSL — the export is
10
13
  * the row the ADR records as **owed** (a Wardley serializer exists today in
11
14
  * labre-mcp, outside this repo, and is the one violation of P3 the ADR names;
12
15
  * it exists here now, so that repo becomes a caller and its copy is deleted),
@@ -58,6 +61,8 @@ export const WARDLEY_OWM_EXPORT = {
58
61
  /* ── The list the view extension registers ────────────────────────────── */
59
62
  /** Everything Wardley registers, in one list the view extension hands over. */
60
63
  export const WARDLEY_INTERCHANGE = [
64
+ WARDLEY_JSON_EXPORT,
65
+ WARDLEY_JSON_IMPORT,
61
66
  WARDLEY_OWM_EXPORT,
62
67
  WARDLEY_OWM_IMPORT,
63
68
  WARDLEY_SVG_IMPORT,
package/dist/legend.d.ts CHANGED
@@ -85,6 +85,7 @@ export declare const WARDLEY_LEGEND_DESC_WORDINGS: readonly ChromeWording[];
85
85
  * catalogue category (Nodes, Connectors, Areas), so a section is declared by
86
86
  * filing a command rather than by a second list.
87
87
  */
88
+ export declare function wardleyLegendEntries(role: WardleyLegendRole): CommandLegendEntry | CommandLegendEntry[];
88
89
  export declare function wardleyLegendEntry(role: WardleyLegendRole): CommandLegendEntry;
89
90
  /** Every {@link LEGEND_GRADIENT} caption, for `translations.ts`'s manifest. */
90
91
  export declare const WARDLEY_LEGEND_GRADIENT_WORDINGS: readonly ChromeWording[];
package/dist/legend.js CHANGED
@@ -2,9 +2,11 @@ import { ConnectorMode, FontFamily, ShapeStyle, } from '@formicoidea/labre-core/
2
2
  import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
3
3
  import { translateKey, } from '@formicoidea/labre-core/shared/services';
4
4
  import { Bound } from '@formicoidea/labre-core/global/gfx';
5
+ import { GfxControllerIdentifier, GfxPrimitiveElementModel, setElementTag, } from '@formicoidea/labre-core/std/gfx';
5
6
  import { GRADIENT_GREEN, GRADIENT_RED } from './gradient.js';
6
7
  import { NODE_FILL, NODE_STROKE, PIPELINE_FILL, PORTER_DEFAULT_LETTER, } from './node/consts.js';
7
8
  import { WARDLEY_EDGE_STYLE, wardleyHandleProps, wardleyInertiaProps, wardleyMarketDotProps, wardleyMarketLinkPairs, wardleyMarketLinkProps, wardleyNodeProps, wardleyPorterArrowProps, wardleyPorterArrows, } from './presets.js';
9
+ import { WARDLEY_METHOD_TAG_ID, WARDLEY_TAG_DEFS } from './natures.js';
8
10
  import { WARDLEY_ROLE } from './roles.js';
9
11
  /**
10
12
  * What a Wardley legend ROW is, and the two blocks that are not rows.
@@ -279,7 +281,7 @@ const LEGEND_DESC = {
279
281
  ],
280
282
  [WARDLEY_ROLE.method]: [
281
283
  'com.labre.wardley.legend.desc.method',
282
- 'Component + method (color = phase)',
284
+ 'Component + method (the ring names the method)',
283
285
  ],
284
286
  [WARDLEY_ROLE.pipeline]: [
285
287
  'com.labre.wardley.legend.desc.pipeline',
@@ -322,6 +324,69 @@ export const WARDLEY_LEGEND_DESC_WORDINGS = Object.values(LEGEND_DESC);
322
324
  * catalogue category (Nodes, Connectors, Areas), so a section is declared by
323
325
  * filing a command rather than by a second list.
324
326
  */
327
+ export function wardleyLegendEntries(role) {
328
+ const entry = wardleyLegendEntry(role);
329
+ return role === WARDLEY_ROLE.method
330
+ ? [entry, ...WARDLEY_METHOD_LEGEND_ENTRIES]
331
+ : entry;
332
+ }
333
+ /* ── One row per method used (ADR 0046) ──────────────────────────────── */
334
+ /**
335
+ * A method's swatch: a method node at the method row's size, carrying the
336
+ * method tag, so it is painted by the very ring painter the map's nodes go
337
+ * through — the swatch cannot drift from the node it explains.
338
+ *
339
+ * The swatch carries no role (the platform strips it, and `glyphProps` never
340
+ * sets one), so its tag is nobody's: no rule reads it, and the next legend's
341
+ * scan, which reads tags off role-bearing elements only, does not list it.
342
+ */
343
+ function drawMethodSwatch(valueId) {
344
+ return (surface, box, std) => {
345
+ const [w, h] = METHOD_SWATCH_SIZE;
346
+ const id = surface.addElement({
347
+ ...glyphProps('method'),
348
+ xywh: centred(box.x + box.w / 2, box.y + box.h / 2, w, h),
349
+ });
350
+ const swatch = std.get(GfxControllerIdentifier).surface?.getElementById(id);
351
+ if (swatch instanceof GfxPrimitiveElementModel) {
352
+ setElementTag(swatch, WARDLEY_METHOD_TAG_ID, [valueId]);
353
+ }
354
+ return [id];
355
+ };
356
+ }
357
+ /** The method row's own swatch size (`WARDLEY_LEGEND_ROWS`). */
358
+ const METHOD_SWATCH_SIZE = [18, 18];
359
+ /**
360
+ * The nine method rows, lit one by one by the values the board USES
361
+ * (`CommandLegendEntry.tag`), under the family's title as the sub-title and
362
+ * with the value's own label — the very wordings of the picker, so the legend
363
+ * and the dropdown say the same word. Each row is a text element of the box
364
+ * like every other, which the author rewrites in place to tell the story of
365
+ * the picture.
366
+ */
367
+ const WARDLEY_METHOD_LEGEND_ENTRIES = (() => {
368
+ const tag = WARDLEY_TAG_DEFS.tags.find(def => def.id === WARDLEY_METHOD_TAG_ID);
369
+ if (!tag || tag.values === 'open')
370
+ return [];
371
+ return tag.values.map(value => ({
372
+ role: WARDLEY_ROLE.method,
373
+ tag: [WARDLEY_METHOD_TAG_ID, value.id],
374
+ row: {
375
+ swatch: 'custom',
376
+ color: NODE_FILL,
377
+ draw: drawMethodSwatch(value.id),
378
+ },
379
+ labelWording: [value.labelKey ?? value.id, value.label],
380
+ ...(value.group
381
+ ? {
382
+ section: [
383
+ value.group.labelKey ?? value.group.label,
384
+ value.group.label,
385
+ ],
386
+ }
387
+ : {}),
388
+ }));
389
+ })();
325
390
  export function wardleyLegendEntry(role) {
326
391
  return {
327
392
  role,
@@ -0,0 +1,53 @@
1
+ import { type WardleyMethodStyle } from './node/consts.js';
2
+ /**
3
+ * The method tag and the pair the wardley-map-renderer JSON spells it as, in
4
+ * both directions, and the prefabricated ring each value paints (ADR 0046).
5
+ *
6
+ * A pure module on purpose, like `porter-competition.ts`: this is the only
7
+ * place a `wardley:method/...` value id is related to a family, a phase and a
8
+ * style. The renderer reads it to paint, an import reads it to write the tag,
9
+ * and nothing here touches an element, a surface or a registry — the painting
10
+ * of a stored document never depends on which tag pack the host seeded.
11
+ */
12
+ /**
13
+ * The three method families, spelled as the renderer's `method.category`
14
+ * spells its three defaults.
15
+ */
16
+ export type WardleyMethodFamily = 'buying-policy' | 'project-management' | 'attitudes';
17
+ /**
18
+ * The evolution phase a method suits, which is how much of its ring is filled.
19
+ * The renderer's `method.recommendation`, lower-cased.
20
+ */
21
+ export type WardleyMethodPhase = 'uncharted' | 'transitional' | 'industrialized';
22
+ export interface WardleyMethodParts {
23
+ family: WardleyMethodFamily;
24
+ phase: WardleyMethodPhase;
25
+ }
26
+ /**
27
+ * The family and phase a method value states, `null` when it is not one of the
28
+ * nine.
29
+ *
30
+ * Total over anything a DOCUMENT can carry: defs are runtime configuration and
31
+ * are never persisted, so a node may hold a value whose pack was removed or a
32
+ * newer client added. Such a value names no family, and the node paints as an
33
+ * untagged one (`make`, the default style) rather than guessing another.
34
+ */
35
+ export declare function wardleyMethodParts(valueId: string | null | undefined): WardleyMethodParts | null;
36
+ /**
37
+ * The method value a renderer `method: { category, recommendation }` pair
38
+ * states, `null` when it states none of the nine.
39
+ *
40
+ * Forgiving in case and surrounding blanks only — the renderer's own keys are
41
+ * capitalised (`Uncharted`) and its categories kebab-case — and strict
42
+ * everywhere else: a category the renderer config made up (its `methods` list
43
+ * is open) is not a method here — Labre has no custom category (PO decision,
44
+ * 2026-10-28) — and an importer keeps it as foreign data (ADR 0012) rather
45
+ * than filing it under the nearest family.
46
+ */
47
+ export declare function wardleyMethodOf(category: string | null | undefined, recommendation: string | null | undefined): string | null;
48
+ /**
49
+ * The prefabricated ring a method value paints (`METHOD_STYLE`), `null`
50
+ * when the value names no method — the painter then falls back on `make`,
51
+ * the method node's default style.
52
+ */
53
+ export declare function wardleyMethodStyle(valueId: string | null | undefined): WardleyMethodStyle | null;
package/dist/method.js ADDED
@@ -0,0 +1,88 @@
1
+ import { WARDLEY_METHOD } from './natures.js';
2
+ import { METHOD_STYLE } from './node/consts.js';
3
+ const PARTS_OF_VALUE = {
4
+ [WARDLEY_METHOD.make]: { family: 'buying-policy', phase: 'uncharted' },
5
+ [WARDLEY_METHOD.buy]: { family: 'buying-policy', phase: 'transitional' },
6
+ [WARDLEY_METHOD.outsource]: {
7
+ family: 'buying-policy',
8
+ phase: 'industrialized',
9
+ },
10
+ [WARDLEY_METHOD.agile]: { family: 'project-management', phase: 'uncharted' },
11
+ [WARDLEY_METHOD.lean]: {
12
+ family: 'project-management',
13
+ phase: 'transitional',
14
+ },
15
+ [WARDLEY_METHOD.sixSigma]: {
16
+ family: 'project-management',
17
+ phase: 'industrialized',
18
+ },
19
+ [WARDLEY_METHOD.pioneers]: { family: 'attitudes', phase: 'uncharted' },
20
+ [WARDLEY_METHOD.settlers]: { family: 'attitudes', phase: 'transitional' },
21
+ [WARDLEY_METHOD.townPlanners]: {
22
+ family: 'attitudes',
23
+ phase: 'industrialized',
24
+ },
25
+ };
26
+ const STYLE_OF_VALUE = {
27
+ [WARDLEY_METHOD.make]: METHOD_STYLE.make,
28
+ [WARDLEY_METHOD.buy]: METHOD_STYLE.buy,
29
+ [WARDLEY_METHOD.outsource]: METHOD_STYLE.outsource,
30
+ [WARDLEY_METHOD.agile]: METHOD_STYLE.agile,
31
+ [WARDLEY_METHOD.lean]: METHOD_STYLE.lean,
32
+ [WARDLEY_METHOD.sixSigma]: METHOD_STYLE.sixSigma,
33
+ [WARDLEY_METHOD.pioneers]: METHOD_STYLE.pioneers,
34
+ [WARDLEY_METHOD.settlers]: METHOD_STYLE.settlers,
35
+ [WARDLEY_METHOD.townPlanners]: METHOD_STYLE.townPlanners,
36
+ };
37
+ /**
38
+ * The family and phase a method value states, `null` when it is not one of the
39
+ * nine.
40
+ *
41
+ * Total over anything a DOCUMENT can carry: defs are runtime configuration and
42
+ * are never persisted, so a node may hold a value whose pack was removed or a
43
+ * newer client added. Such a value names no family, and the node paints as an
44
+ * untagged one (`make`, the default style) rather than guessing another.
45
+ */
46
+ export function wardleyMethodParts(valueId) {
47
+ if (typeof valueId !== 'string')
48
+ return null;
49
+ // `Object.hasOwn`, so `'toString'` does not resolve to a method.
50
+ return Object.hasOwn(PARTS_OF_VALUE, valueId)
51
+ ? PARTS_OF_VALUE[valueId]
52
+ : null;
53
+ }
54
+ /**
55
+ * The method value a renderer `method: { category, recommendation }` pair
56
+ * states, `null` when it states none of the nine.
57
+ *
58
+ * Forgiving in case and surrounding blanks only — the renderer's own keys are
59
+ * capitalised (`Uncharted`) and its categories kebab-case — and strict
60
+ * everywhere else: a category the renderer config made up (its `methods` list
61
+ * is open) is not a method here — Labre has no custom category (PO decision,
62
+ * 2026-10-28) — and an importer keeps it as foreign data (ADR 0012) rather
63
+ * than filing it under the nearest family.
64
+ */
65
+ export function wardleyMethodOf(category, recommendation) {
66
+ if (typeof category !== 'string' || typeof recommendation !== 'string') {
67
+ return null;
68
+ }
69
+ const family = category.trim().toLowerCase();
70
+ const phase = recommendation.trim().toLowerCase();
71
+ for (const [valueId, parts] of Object.entries(PARTS_OF_VALUE)) {
72
+ if (parts.family === family && parts.phase === phase)
73
+ return valueId;
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * The prefabricated ring a method value paints (`METHOD_STYLE`), `null`
79
+ * when the value names no method — the painter then falls back on `make`,
80
+ * the method node's default style.
81
+ */
82
+ export function wardleyMethodStyle(valueId) {
83
+ if (typeof valueId !== 'string')
84
+ return null;
85
+ return Object.hasOwn(STYLE_OF_VALUE, valueId)
86
+ ? STYLE_OF_VALUE[valueId]
87
+ : null;
88
+ }
package/dist/natures.d.ts CHANGED
@@ -20,8 +20,8 @@ import type { UniverseTagDefs } from '@formicoidea/labre-core/shared/services';
20
20
  *
21
21
  * ## Why it applies to `wardley:component` and not to `'*'`
22
22
  *
23
- * A nature qualifies a component. `market` and `ecosystem` specialise
24
- * `wardley:component`, so they get it for free through `roleIsA` — that is the
23
+ * A nature qualifies a component. `market`, `ecosystem` and `method`
24
+ * specialise `wardley:component`, so they get it for free through `roleIsA` — that is the
25
25
  * entire reason role hierarchy is data. The `anchor` (a user / need) is
26
26
  * deliberately NOT a child of `component` and is deliberately NOT qualified
27
27
  * here: a need has no nature, it has a demand. The map itself, the change
@@ -78,4 +78,66 @@ export declare const WARDLEY_COMPETITION: {
78
78
  readonly struggle: "wardley:competition/struggle";
79
79
  readonly establish: "wardley:competition/establish";
80
80
  };
81
+ /**
82
+ * The **method** tag — how a component + method is sourced, run or staffed.
83
+ *
84
+ * The third tag of this pack, written on `wardley:method` alone. Since that
85
+ * role specialises `wardley:component` (ADR 0046), a method node is offered
86
+ * the nature AND this tag in the same dropdown, while a plain component — the
87
+ * parent, which a child's tag never reaches — is offered the nature only.
88
+ *
89
+ * ## Nine values in one tag, not a category tag and a recommendation tag
90
+ *
91
+ * Simon Wardley's three method families each say one thing per evolution
92
+ * phase: a buying policy makes, buys or outsources; project management runs
93
+ * agile, lean or six sigma; attitudes are pioneers, settlers or town planners.
94
+ * The wardley-map-renderer JSON spells the same thing as a pair —
95
+ * `method: { category, recommendation }`, recommendation one of Uncharted /
96
+ * Transitional / Industrialized — and the two encodings are the same nine
97
+ * facts. One value per fact is the shape that admits nothing else: two tags
98
+ * would let a board say "Transitional" with no family, which nothing can paint
99
+ * and no reader can name. The pair stays one pure lookup away in `method.ts`,
100
+ * both directions.
101
+ *
102
+ * The family is still shown twice (PO decision, 2026-10-28): each value has its
103
+ * `group` — the family's title, which the picker prints as a sub-title above
104
+ * its three values — and each of the nine has its own prefabricated ring
105
+ * (`METHOD_STYLE`), which is how the map tells them apart. No family beyond
106
+ * these three: a category the renderer config invented is not a method here.
107
+ *
108
+ * `cardinality: 'single'` because the renderer gives a component ONE method,
109
+ * and because the drawing has one ring to show it with.
110
+ *
111
+ * Labels are the practitioners' own words — Make, Buy, Outsource in the PO's
112
+ * wording — short on purpose: the trigger of the dropdown prints the values
113
+ * in force ("Activity, Buy").
114
+ */
115
+ export declare const WARDLEY_METHOD_TAG_ID = "wardley:method";
116
+ /** The nine methods, as value ids. Ids are forever; a def is only deprecated. */
117
+ export declare const WARDLEY_METHOD: {
118
+ readonly make: "wardley:method/make";
119
+ readonly buy: "wardley:method/buy";
120
+ readonly outsource: "wardley:method/outsource";
121
+ readonly agile: "wardley:method/agile";
122
+ readonly lean: "wardley:method/lean";
123
+ readonly sixSigma: "wardley:method/six-sigma";
124
+ readonly pioneers: "wardley:method/pioneers";
125
+ readonly settlers: "wardley:method/settlers";
126
+ readonly townPlanners: "wardley:method/town-planners";
127
+ };
128
+ /** The three families' titles: the picker's sub-titles and the legend's. */
129
+ export declare const WARDLEY_METHOD_GROUP: {
130
+ readonly buyingPolicy: {
131
+ readonly label: "Buying policy";
132
+ readonly labelKey: "com.labre.wardley.tag.method.group.buying-policy";
133
+ };
134
+ readonly projectManagement: {
135
+ readonly label: "Project management";
136
+ readonly labelKey: "com.labre.wardley.tag.method.group.project-management";
137
+ };
138
+ readonly attitudes: {
139
+ readonly label: "Attitudes";
140
+ readonly labelKey: "com.labre.wardley.tag.method.group.attitudes";
141
+ };
142
+ };
81
143
  export declare const WARDLEY_TAG_DEFS: UniverseTagDefs;