@diagrammo/dgmo 0.21.1 → 0.22.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.
Files changed (73) hide show
  1. package/README.md +16 -6
  2. package/dist/advanced.cjs +2003 -466
  3. package/dist/advanced.d.cts +5714 -0
  4. package/dist/advanced.d.ts +5714 -0
  5. package/dist/advanced.js +1999 -466
  6. package/dist/auto.cjs +2048 -449
  7. package/dist/auto.d.cts +39 -0
  8. package/dist/auto.d.ts +39 -0
  9. package/dist/auto.js +121 -121
  10. package/dist/auto.mjs +2050 -450
  11. package/dist/cli.cjs +170 -170
  12. package/dist/editor.cjs +13 -16
  13. package/dist/editor.js +13 -16
  14. package/dist/highlight.cjs +15 -13
  15. package/dist/highlight.js +15 -13
  16. package/dist/index.cjs +2032 -435
  17. package/dist/index.d.cts +339 -0
  18. package/dist/index.d.ts +339 -0
  19. package/dist/index.js +2034 -436
  20. package/dist/internal.cjs +2003 -466
  21. package/dist/internal.d.cts +5714 -0
  22. package/dist/internal.d.ts +5714 -0
  23. package/dist/internal.js +1999 -466
  24. package/dist/map-data/water-bodies.json +1 -0
  25. package/docs/language-reference.md +20 -9
  26. package/gallery/fixtures/map-categorical-world.dgmo +16 -0
  27. package/gallery/fixtures/map-categorical.dgmo +0 -1
  28. package/gallery/fixtures/map-choropleth.dgmo +0 -1
  29. package/gallery/fixtures/map-coastline.dgmo +7 -0
  30. package/gallery/fixtures/map-colorize.dgmo +11 -0
  31. package/gallery/fixtures/map-direct-color.dgmo +0 -1
  32. package/gallery/fixtures/map-reference-world.dgmo +11 -0
  33. package/gallery/fixtures/map-region-scope.dgmo +0 -3
  34. package/gallery/fixtures/map-route.dgmo +0 -1
  35. package/package.json +1 -1
  36. package/src/advanced.ts +12 -1
  37. package/src/boxes-and-lines/renderer.ts +39 -12
  38. package/src/cli.ts +1 -1
  39. package/src/completion.ts +32 -25
  40. package/src/cycle/renderer.ts +14 -1
  41. package/src/d3.ts +8 -2
  42. package/src/editor/highlight-api.ts +4 -0
  43. package/src/editor/keywords.ts +13 -16
  44. package/src/infra/renderer.ts +35 -7
  45. package/src/map/colorize.ts +54 -0
  46. package/src/map/context-labels.ts +429 -0
  47. package/src/map/data/types.ts +34 -0
  48. package/src/map/data/water-bodies.json +1 -0
  49. package/src/map/dimensions.ts +117 -0
  50. package/src/map/geo-query.ts +21 -3
  51. package/src/map/geo.ts +47 -1
  52. package/src/map/layout.ts +1300 -251
  53. package/src/map/load-data.ts +10 -2
  54. package/src/map/parser.ts +42 -116
  55. package/src/map/renderer.ts +512 -13
  56. package/src/map/resolved-types.ts +16 -2
  57. package/src/map/resolver.ts +208 -59
  58. package/src/map/types.ts +30 -32
  59. package/src/mindmap/renderer.ts +10 -1
  60. package/src/palettes/atlas.ts +77 -0
  61. package/src/palettes/blueprint.ts +73 -0
  62. package/src/palettes/color-utils.ts +58 -1
  63. package/src/palettes/index.ts +12 -3
  64. package/src/palettes/slate.ts +73 -0
  65. package/src/palettes/tidewater.ts +73 -0
  66. package/src/render.ts +8 -1
  67. package/src/tech-radar/renderer.ts +3 -0
  68. package/src/tech-radar/types.ts +3 -0
  69. package/src/utils/d3-types.ts +5 -0
  70. package/src/utils/legend-layout.ts +21 -4
  71. package/src/utils/legend-types.ts +7 -0
  72. package/src/utils/reserved-key-registry.ts +3 -0
  73. package/src/palettes/bold.ts +0 -67
@@ -0,0 +1,77 @@
1
+ import type { PaletteConfig } from './types';
2
+ import { registerPalette } from './registry';
3
+
4
+ // ============================================================
5
+ // Atlas Palette Definition
6
+ // ============================================================
7
+ //
8
+ // Inspired by vintage classroom pull-down maps and school atlases:
9
+ // soft, slightly-aged political fills on warm manila paper, sepia ink
10
+ // borders, and a muted steel-blue "pull-down map ocean". The accent
11
+ // rotation is the classic set of pastels cartographers use to keep
12
+ // adjacent regions distinct without shouting.
13
+ //
14
+ // Light mode is the star (paper-and-ink). Dark mode shifts the ground to
15
+ // deep-ocean navy — the "map at night / globe" register — and brightens
16
+ // the same pastels so they read on the darker field.
17
+
18
+ export const atlasPalette: PaletteConfig = {
19
+ id: 'atlas',
20
+ name: 'Atlas',
21
+ light: {
22
+ bg: '#f3ead3', // warm manila / parchment
23
+ surface: '#ece0c0', // deeper paper (cards, panels)
24
+ overlay: '#e8dab8', // popovers, dropdowns
25
+ border: '#bcaa86', // muted sepia rule line
26
+ text: '#463a26', // aged sepia-brown ink
27
+ textMuted: '#7a6a4f', // faded annotation ink
28
+ textOnFillLight: '#f7f1de', // parchment (light text on dark fills)
29
+ textOnFillDark: '#3a2e1c', // deep ink (dark text on light fills)
30
+ primary: '#5b7a99', // pull-down map ocean (steel-blue)
31
+ secondary: '#7e9a6f', // lowland sage / celadon
32
+ accent: '#b07f7c', // dusty rose
33
+ destructive: '#b25a45', // brick / terracotta
34
+ colors: {
35
+ red: '#bf6a52', // terracotta brick
36
+ orange: '#cf9a5c', // map tan / ochre
37
+ yellow: '#cdb35e', // straw / muted lemon
38
+ green: '#7e9a6f', // sage / celadon lowland
39
+ blue: '#5b7a99', // steel-blue ocean
40
+ purple: '#9a7fa6', // dusty lilac / mauve
41
+ teal: '#6fa094', // muted seafoam
42
+ cyan: '#79a7b5', // shallow-water blue
43
+ gray: '#8a7d68', // warm taupe
44
+ black: '#463a26', // ink
45
+ white: '#ece0c0', // paper
46
+ },
47
+ },
48
+ dark: {
49
+ bg: '#1e2a33', // deep map ocean (night globe)
50
+ surface: '#27353f', // raised ocean
51
+ overlay: '#2e3d48', // popovers, dropdowns
52
+ border: '#3d4f5c', // depth-contour line
53
+ text: '#e8dcc0', // parchment ink, inverted
54
+ textMuted: '#a89a7d', // faded label
55
+ textOnFillLight: '#f7f1de', // parchment
56
+ textOnFillDark: '#1a242c', // deep ocean ink
57
+ primary: '#7ba0bf', // brighter ocean
58
+ secondary: '#9bb588', // sage, lifted
59
+ accent: '#cf9a96', // dusty rose, lifted
60
+ destructive: '#c9745c', // brick, lifted
61
+ colors: {
62
+ red: '#cf7a60', // terracotta
63
+ orange: '#d9a96a', // tan / ochre
64
+ yellow: '#d8c074', // straw
65
+ green: '#9bb588', // sage lowland
66
+ blue: '#7ba0bf', // ocean
67
+ purple: '#b59ac0', // lilac / mauve
68
+ teal: '#85b3a6', // seafoam
69
+ cyan: '#92bccb', // shallow-water blue
70
+ gray: '#9a8d76', // warm taupe
71
+ black: '#27353f', // raised ocean
72
+ white: '#e8dcc0', // parchment
73
+ },
74
+ },
75
+ };
76
+
77
+ registerPalette(atlasPalette);
@@ -0,0 +1,73 @@
1
+ import type { PaletteConfig } from './types';
2
+ import { registerPalette } from './registry';
3
+
4
+ // ============================================================
5
+ // Blueprint Palette Definition
6
+ // ============================================================
7
+ //
8
+ // A cyanotype engineering-drawing palette — the technical register the set
9
+ // was missing. Dark mode is the iconic one: chalk-pastel "colored-pencil"
10
+ // accents drafted on a deep blueprint-blue ground (reads unmistakably as a
11
+ // technical drawing — ideal for infra, C4, ER, flowcharts). Light mode is
12
+ // the reversed draft: blueprint-blue ink and lines on pale drafting white.
13
+
14
+ export const blueprintPalette: PaletteConfig = {
15
+ id: 'blueprint',
16
+ name: 'Blueprint',
17
+ light: {
18
+ bg: '#f4f8fb', // pale drafting white (faint cyan)
19
+ surface: '#e6eef4', // drafting panel
20
+ overlay: '#dde9f1', // popovers, dropdowns
21
+ border: '#aac3d6', // pale blue grid line
22
+ text: '#123a5e', // blueprint navy ink
23
+ textMuted: '#4f7390', // faint draft note
24
+ textOnFillLight: '#f4f8fb', // drafting white
25
+ textOnFillDark: '#0c2f4d', // deep blueprint navy
26
+ primary: '#1f5e8c', // blueprint blue
27
+ secondary: '#5b7d96', // steel
28
+ accent: '#b08a3e', // draftsman's ochre highlight
29
+ destructive: '#c0504d', // correction red
30
+ colors: {
31
+ red: '#c25a4e', // correction red
32
+ orange: '#c2823e', // ochre
33
+ yellow: '#c2a843', // pencil gold
34
+ green: '#4f8a6b', // drafting green
35
+ blue: '#1f5e8c', // blueprint blue
36
+ purple: '#6f5e96', // indigo pencil
37
+ teal: '#3a8a8a', // teal
38
+ cyan: '#3f8fb5', // cyan
39
+ gray: '#7e8e98', // graphite
40
+ black: '#123a5e', // navy ink
41
+ white: '#e6eef4', // panel
42
+ },
43
+ },
44
+ dark: {
45
+ bg: '#103a5e', // deep blueprint blue (cyanotype ground)
46
+ surface: '#16466e', // raised sheet
47
+ overlay: '#1c5180', // popovers, dropdowns
48
+ border: '#3a6f96', // grid line
49
+ text: '#eaf2f8', // chalk white
50
+ textMuted: '#9fc0d6', // faint chalk note
51
+ textOnFillLight: '#eaf2f8', // chalk white
52
+ textOnFillDark: '#0c2f4d', // deep blueprint navy
53
+ primary: '#7fb8d8', // chalk cyan
54
+ secondary: '#9fb8c8', // pale steel
55
+ accent: '#d8c27a', // chalk amber
56
+ destructive: '#e08a7a', // chalk correction red
57
+ colors: {
58
+ red: '#e0907e', // chalk red
59
+ orange: '#e0ab78', // chalk amber
60
+ yellow: '#e3d089', // chalk gold
61
+ green: '#93c79e', // chalk green
62
+ blue: '#8ec3e0', // chalk cyan-blue
63
+ purple: '#b6a6d8', // chalk indigo
64
+ teal: '#84c7c2', // chalk teal
65
+ cyan: '#9fd6e0', // chalk cyan
66
+ gray: '#aebecb', // chalk graphite
67
+ black: '#16466e', // raised sheet
68
+ white: '#eaf2f8', // chalk white
69
+ },
70
+ },
71
+ };
72
+
73
+ registerPalette(blueprintPalette);
@@ -182,7 +182,7 @@ export function relativeLuminance(hex: string): number {
182
182
  * Range: 1.0 (identical) to 21.0 (black on white). Internal helper used by
183
183
  * `contrastText`'s pastel branch.
184
184
  */
185
- function contrastRatio(a: string, b: string): number {
185
+ export function contrastRatio(a: string, b: string): number {
186
186
  const la = relativeLuminance(a);
187
187
  const lb = relativeLuminance(b);
188
188
  const lighter = Math.max(la, lb);
@@ -308,3 +308,60 @@ export function getSegmentColors(
308
308
  hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
309
309
  );
310
310
  }
311
+
312
+ // ============================================================
313
+ // Political map fills (map colorize, §24B)
314
+ // ============================================================
315
+
316
+ /** Mix-toward-surface bands (% of the raw swatch retained) for political fills,
317
+ * mode-aware. Colorize fills must stay ON-PALETTE — they're soft tints of the
318
+ * palette's OWN named hues, not wheel-generated colours. The retained-% is kept
319
+ * clear of the water backdrop's own blue tint (WATER_TINT_*), so a country fill
320
+ * never coincides with the ocean. The first band is the normal soft tint; later
321
+ * bands only come into play if a map needs more colours than the palette has
322
+ * distinct land hues (rare — first-fit coloring needs ≤6 on the shipped graphs). */
323
+ const POLITICAL_TINT_BANDS = {
324
+ light: [32, 48, 64, 80],
325
+ dark: [44, 58, 72, 86],
326
+ } as const;
327
+
328
+ /**
329
+ * Generate `count` political-fill tints from the active palette's OWN hues
330
+ * (§24B colorize). Each is a palette swatch softened toward the surface, so the
331
+ * fills always read as that palette (Atlas tints look like Atlas, not neon wheel
332
+ * samples). Hues are ordered LAND-FIRST — blue/cyan (the ocean-adjacent hues) go
333
+ * last, so country fills never read as water; they are only reached if a map
334
+ * needs >6 colours (it never does — first-fit needs ≤6, palettes ship ≥6 land
335
+ * hues). `count` distinct colours come from walking the deduped hues; if more are
336
+ * needed, additional lightness BANDS of the same hues are appended (still
337
+ * on-palette). Deterministic; resvg-safe (hex out).
338
+ */
339
+ export function politicalTints(
340
+ palette: PaletteColors,
341
+ count: number,
342
+ isDark: boolean
343
+ ): string[] {
344
+ if (count <= 0) return [];
345
+ const base = isDark ? palette.surface : palette.bg;
346
+ const c = palette.colors;
347
+ // Land-first: greens/earth tones lead; water-like blue & cyan trail.
348
+ const swatches = [
349
+ ...new Set([
350
+ c.green,
351
+ c.yellow,
352
+ c.orange,
353
+ c.purple,
354
+ c.red,
355
+ c.teal,
356
+ c.cyan,
357
+ c.blue,
358
+ ]),
359
+ ];
360
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
361
+ const out: string[] = [];
362
+ for (const pct of bands) {
363
+ if (out.length >= count) break;
364
+ for (const s of swatches) out.push(mix(s, base, pct));
365
+ }
366
+ return out.slice(0, count);
367
+ }
@@ -23,13 +23,16 @@ export {
23
23
  } from './color-utils';
24
24
 
25
25
  // Re-export palette definitions (alphabetical)
26
- export { boldPalette } from './bold';
26
+ export { atlasPalette } from './atlas';
27
+ export { blueprintPalette } from './blueprint';
27
28
  export { catppuccinPalette } from './catppuccin';
28
29
  export { gruvboxPalette } from './gruvbox';
29
30
  export { nordPalette } from './nord';
30
31
  export { oneDarkPalette } from './one-dark';
31
32
  export { rosePinePalette } from './rose-pine';
33
+ export { slatePalette } from './slate';
32
34
  export { solarizedPalette } from './solarized';
35
+ export { tidewaterPalette } from './tidewater';
33
36
  export { tokyoNightPalette } from './tokyo-night';
34
37
 
35
38
  export { draculaPalette } from './dracula';
@@ -39,7 +42,8 @@ export { monokaiPalette } from './monokai';
39
42
  // Public namespace — `palettes` for use with render()
40
43
  // ============================================================
41
44
 
42
- import { boldPalette } from './bold';
45
+ import { atlasPalette } from './atlas';
46
+ import { blueprintPalette } from './blueprint';
43
47
  import { catppuccinPalette } from './catppuccin';
44
48
  import { draculaPalette } from './dracula';
45
49
  import { gruvboxPalette } from './gruvbox';
@@ -47,7 +51,9 @@ import { monokaiPalette } from './monokai';
47
51
  import { nordPalette } from './nord';
48
52
  import { oneDarkPalette } from './one-dark';
49
53
  import { rosePinePalette } from './rose-pine';
54
+ import { slatePalette } from './slate';
50
55
  import { solarizedPalette } from './solarized';
56
+ import { tidewaterPalette } from './tidewater';
51
57
  import { tokyoNightPalette } from './tokyo-night';
52
58
 
53
59
  import type { PaletteConfig } from './types';
@@ -62,6 +68,10 @@ import type { PaletteConfig } from './types';
62
68
  * used by share URLs and the CLI `--palette` flag.
63
69
  */
64
70
  export const palettes = {
71
+ atlas: atlasPalette,
72
+ blueprint: blueprintPalette,
73
+ slate: slatePalette,
74
+ tidewater: tidewaterPalette,
65
75
  nord: nordPalette,
66
76
  catppuccin: catppuccinPalette,
67
77
  solarized: solarizedPalette,
@@ -71,5 +81,4 @@ export const palettes = {
71
81
  rosePine: rosePinePalette,
72
82
  dracula: draculaPalette,
73
83
  monokai: monokaiPalette,
74
- bold: boldPalette,
75
84
  } as const satisfies Record<string, PaletteConfig>;
@@ -0,0 +1,73 @@
1
+ import type { PaletteConfig } from './types';
2
+ import { registerPalette } from './registry';
3
+
4
+ // ============================================================
5
+ // Slate Palette Definition
6
+ // ============================================================
7
+ //
8
+ // A restrained, presentation-grade palette for professional decks and
9
+ // reports — the "not very stylistic" option done tastefully. Clean white
10
+ // (or deep slate) ground, one confident corporate blue, neutral cool-gray
11
+ // surfaces, and a categorical rotation tuned to a single perceived weight
12
+ // so no series shouts. Replaces the harsh primary-RGB feel of "bold".
13
+
14
+ export const slatePalette: PaletteConfig = {
15
+ id: 'slate',
16
+ name: 'Slate',
17
+ light: {
18
+ bg: '#ffffff', // clean slide white
19
+ surface: '#f3f5f8', // light cool-gray panel
20
+ overlay: '#eaeef3', // popovers, dropdowns
21
+ border: '#d4dae1', // hairline rule
22
+ text: '#1f2933', // near-black slate (softer than pure black)
23
+ textMuted: '#5b6672', // secondary label
24
+ textOnFillLight: '#ffffff', // light text on dark fills
25
+ textOnFillDark: '#1f2933', // dark text on light fills
26
+ primary: '#3b6ea5', // confident corporate blue
27
+ secondary: '#5b6672', // slate gray
28
+ accent: '#3a9188', // muted teal accent
29
+ destructive: '#c0504d', // brick red
30
+ colors: {
31
+ red: '#c0504d', // brick
32
+ orange: '#cc7a33', // muted amber
33
+ yellow: '#c9a227', // gold (not neon)
34
+ green: '#5b9357', // forest / sage
35
+ blue: '#3b6ea5', // corporate blue
36
+ purple: '#7d5ba6', // muted violet
37
+ teal: '#3a9188', // teal
38
+ cyan: '#4f96c4', // steel cyan
39
+ gray: '#7e8a97', // cool gray
40
+ black: '#1f2933', // slate ink
41
+ white: '#f3f5f8', // panel
42
+ },
43
+ },
44
+ dark: {
45
+ bg: '#161b22', // deep slate (keynote dark)
46
+ surface: '#202833', // raised panel
47
+ overlay: '#29323e', // popovers, dropdowns
48
+ border: '#38424f', // divider
49
+ text: '#e6eaef', // off-white
50
+ textMuted: '#9aa5b1', // secondary label
51
+ textOnFillLight: '#ffffff', // light text on dark fills
52
+ textOnFillDark: '#161b22', // dark text on light fills
53
+ primary: '#5b9bd5', // lifted corporate blue
54
+ secondary: '#8593a3', // slate gray, lifted
55
+ accent: '#45b3a3', // teal, lifted
56
+ destructive: '#e07b6e', // brick, lifted
57
+ colors: {
58
+ red: '#e07b6e', // brick
59
+ orange: '#e0975a', // amber
60
+ yellow: '#d9bd5a', // gold
61
+ green: '#74b56e', // forest / sage
62
+ blue: '#5b9bd5', // corporate blue
63
+ purple: '#a585c9', // violet
64
+ teal: '#45b3a3', // teal
65
+ cyan: '#62b0d9', // steel cyan
66
+ gray: '#95a1ae', // cool gray
67
+ black: '#202833', // raised panel
68
+ white: '#e6eaef', // off-white
69
+ },
70
+ },
71
+ };
72
+
73
+ registerPalette(slatePalette);
@@ -0,0 +1,73 @@
1
+ import type { PaletteConfig } from './types';
2
+ import { registerPalette } from './registry';
3
+
4
+ // ============================================================
5
+ // Tidewater Palette Definition
6
+ // ============================================================
7
+ //
8
+ // A nautical, maritime-chart palette — on-brand with Diagrammo's seafaring
9
+ // voice. Weathered sea-mist paper with deep ship's-log navy ink, rope/manila
10
+ // tan, brass, signal-flag red, and sea-glass greens (light). Dark mode dives
11
+ // to a night-harbor deep-sea ground with the same rigging lifted to read on
12
+ // the dark water.
13
+
14
+ export const tidewaterPalette: PaletteConfig = {
15
+ id: 'tidewater',
16
+ name: 'Tidewater',
17
+ light: {
18
+ bg: '#eceff0', // weathered sea-mist paper
19
+ surface: '#e0e4e3', // worn deck panel
20
+ overlay: '#dadfdf', // popovers, dropdowns
21
+ border: '#a9b2b3', // muted slate rule
22
+ text: '#18313f', // ship's-log navy ink
23
+ textMuted: '#51636b', // faded log entry
24
+ textOnFillLight: '#f3f5f3', // weathered white
25
+ textOnFillDark: '#162c38', // deep navy
26
+ primary: '#1f4e6b', // deep-sea navy
27
+ secondary: '#b08a4f', // rope / manila tan
28
+ accent: '#c69a3e', // brass
29
+ destructive: '#c1433a', // signal-flag red
30
+ colors: {
31
+ red: '#c1433a', // signal-flag red
32
+ orange: '#cc7a38', // weathered amber
33
+ yellow: '#d6bf5a', // brass gold
34
+ green: '#4f8a6b', // sea-glass green
35
+ blue: '#1f4e6b', // deep-sea navy
36
+ purple: '#6a5a8c', // twilight harbor
37
+ teal: '#3d8c8c', // sea-glass teal
38
+ cyan: '#4f9bb5', // shallow water
39
+ gray: '#8a8d86', // driftwood gray
40
+ black: '#18313f', // navy ink
41
+ white: '#e0e4e3', // deck panel
42
+ },
43
+ },
44
+ dark: {
45
+ bg: '#0f2230', // night-harbor deep sea
46
+ surface: '#16303f', // raised hull
47
+ overlay: '#1d3a4a', // popovers, dropdowns
48
+ border: '#2c4856', // rigging line
49
+ text: '#e6ebe8', // weathered white
50
+ textMuted: '#9aaab0', // faded label
51
+ textOnFillLight: '#f3f5f3', // weathered white
52
+ textOnFillDark: '#0f2230', // deep sea
53
+ primary: '#4f9bc4', // lifted sea blue
54
+ secondary: '#c9a46a', // rope tan, lifted
55
+ accent: '#d9b25a', // brass, lifted
56
+ destructive: '#e06a5e', // signal red, lifted
57
+ colors: {
58
+ red: '#e06a5e', // signal-flag red
59
+ orange: '#df9a52', // amber
60
+ yellow: '#e0c662', // brass gold
61
+ green: '#6fb58c', // sea-glass green
62
+ blue: '#4f9bc4', // sea blue
63
+ purple: '#9486bf', // twilight harbor
64
+ teal: '#5cb0ac', // sea-glass teal
65
+ cyan: '#62b4cf', // shallow water
66
+ gray: '#9aa39c', // driftwood gray
67
+ black: '#16303f', // raised hull
68
+ white: '#e6ebe8', // weathered white
69
+ },
70
+ },
71
+ };
72
+
73
+ registerPalette(tidewaterPalette);
package/src/render.ts CHANGED
@@ -88,6 +88,9 @@ export async function render(
88
88
  legendState?: { activeGroup?: string; hiddenAttributes?: string[] };
89
89
  /** View state for export — controls interactive state (collapse, swimlanes, etc.) */
90
90
  viewState?: CompactViewState;
91
+ /** Bundled map data for `map` charts in the browser, where the Node fs
92
+ * `loadMapData()` seam can't run. CLI/SSR omit this and fall back to fs. */
93
+ mapData?: import('./map/resolved-types').MapData;
91
94
  }
92
95
  ): Promise<{ svg: string; diagnostics: DgmoError[] }> {
93
96
  const theme = options?.theme ?? 'light';
@@ -133,6 +136,7 @@ export async function render(
133
136
  c4Container: options.c4Container,
134
137
  }),
135
138
  ...(options?.tagGroup !== undefined && { tagGroup: options.tagGroup }),
139
+ ...(options?.mapData !== undefined && { mapData: options.mapData }),
136
140
  });
137
141
 
138
142
  // The map pipeline resolves names AFTER parsing (gazetteer/ISO lookup), so its
@@ -149,7 +153,10 @@ export async function render(
149
153
  import('./map/load-data'),
150
154
  ]
151
155
  );
152
- const data = await loadMapData();
156
+ // Prefer injected data (browser); fall back to the fs loader (CLI/SSR).
157
+ const data = options?.mapData ?? (await loadMapData());
158
+ // resolveMap seeds its diagnostics with the parser's, so this is a superset.
159
+ // (The layout stage has no diagnostics producer, so there is nothing to merge.)
153
160
  diagnostics = [...resolveMap(parseMap(content), data).diagnostics];
154
161
  } catch {
155
162
  /* asset load failed — keep the parser diagnostics */
@@ -201,6 +201,9 @@ export function renderTechRadar(
201
201
  },
202
202
  ],
203
203
  },
204
+ ...(options.controlsHost !== undefined && {
205
+ controlsHost: options.controlsHost,
206
+ }),
204
207
  };
205
208
  const legendState: LegendState = {
206
209
  activeGroup: options?.activeLegendGroup ?? null,
@@ -80,4 +80,7 @@ export interface TechRadarRenderOptions {
80
80
  activeLine?: number | null;
81
81
  /** True when rendering for export (PNG/SVG/PDF) — controls whether collapsed legend pills and cog are stripped. */
82
82
  exportMode?: boolean;
83
+ /** When 'app', the Blip Legend toggle is hosted by the app overlay strip
84
+ * (inline gear suppressed, controls row + anchor reserved). */
85
+ controlsHost?: 'app' | 'inline';
83
86
  }
@@ -1,4 +1,9 @@
1
1
  export interface D3ExportDimensions {
2
2
  width?: number;
3
3
  height?: number;
4
+ /** Map-only: when true, the map renderer suppresses its global stretch-fill and
5
+ * contain-fits (letterbox) instead. Set by `mapExportDimensions` when the export
6
+ * canvas was clamped/floored away from the map's content aspect, so the
7
+ * off-aspect canvas doesn't re-distort. Ignored by all non-map renderers. */
8
+ preferContain?: boolean;
4
9
  }
@@ -248,6 +248,18 @@ export function controlsGroupCapsuleWidth(
248
248
  return w;
249
249
  }
250
250
 
251
+ /** True when the controlsGroup should be hosted by the app overlay strip
252
+ * (gear suppressed, controls row + anchor reserved) rather than drawn inline.
253
+ * Never gates on the export path. */
254
+ function isAppHostedControls(config: LegendConfig, isExport: boolean): boolean {
255
+ return (
256
+ !isExport &&
257
+ config.controlsHost === 'app' &&
258
+ !!config.controlsGroup &&
259
+ config.controlsGroup.toggles.length > 0
260
+ );
261
+ }
262
+
251
263
  function buildControlsGroupLayout(
252
264
  config: LegendConfig,
253
265
  state: LegendState
@@ -325,6 +337,9 @@ export function computeLegendLayout(
325
337
  const { groups, controls: configControls, mode } = config;
326
338
  const isExport = mode === 'export';
327
339
 
340
+ // App-hosted controls: suppress the inline gear, reserve a header row + anchor.
341
+ const gated = isAppHostedControls(config, isExport);
342
+
328
343
  // Filter groups for export: only active group shown
329
344
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
330
345
 
@@ -339,10 +354,9 @@ export function computeLegendLayout(
339
354
  };
340
355
  }
341
356
 
342
- // Controls group (strip in export mode)
343
- const controlsGroupLayout = isExport
344
- ? undefined
345
- : buildControlsGroupLayout(config, state);
357
+ // Controls group (gear): suppressed in export and when app-hosted.
358
+ const controlsGroupLayout =
359
+ isExport || gated ? undefined : buildControlsGroupLayout(config, state);
346
360
 
347
361
  const visibleGroups = config.showEmptyGroups
348
362
  ? groups
@@ -469,6 +483,9 @@ export function computeLegendLayout(
469
483
  controlsGroupLayout
470
484
  );
471
485
 
486
+ // App-hosted controls: the controlsGroup was dropped above (no gear), and the
487
+ // app overlay strip pins itself to the top edge of the preview — dgmo reserves
488
+ // no row and emits no anchor, so the chart reclaims that space.
472
489
  const height = rows.length * LEGEND_HEIGHT;
473
490
  const width = containerWidth;
474
491
 
@@ -112,6 +112,13 @@ export interface LegendConfig {
112
112
  * group). Lets the user click a sibling to switch the active group. Default
113
113
  * false (legacy: when one group is active the others are hidden). */
114
114
  showInactivePills?: boolean;
115
+ /** Where the controlsGroup is hosted. Default (undefined / 'inline') renders
116
+ * the in-SVG gear exactly as before — every non-app consumer (Obsidian,
117
+ * site, remark-family, CLI) is unaffected. When 'app', the controlsGroup is
118
+ * dropped entirely (no gear, no reserved row): the app overlay strip owns the
119
+ * controls, pinned to the top edge of the preview. App preview only; never
120
+ * set on the export path. */
121
+ controlsHost?: 'app' | 'inline';
115
122
  }
116
123
 
117
124
  export interface LegendPalette {
@@ -85,6 +85,9 @@ export const MAP_REGISTRY: ReservedKeyRegistry = staticRegistry([
85
85
  'value',
86
86
  'label',
87
87
  'style',
88
+ // `surface:` was removed in the 2026-06-02 defaults-on review — it is no longer
89
+ // a recognized metadata key (the route/edge surface feature was cut; §24B.7).
90
+ // A stray `surface: water` is no longer captured as a reserved key.
88
91
  ]);
89
92
 
90
93
  export const ORG_REGISTRY: ReservedKeyRegistry = staticRegistry([
@@ -1,67 +0,0 @@
1
- import type { PaletteConfig } from './types';
2
- import { registerPalette } from './registry';
3
-
4
- // ============================================================
5
- // Bold Palette Definition
6
- // ============================================================
7
-
8
- export const boldPalette: PaletteConfig = {
9
- id: 'bold',
10
- name: 'Bold',
11
- light: {
12
- bg: '#ffffff',
13
- surface: '#f0f0f0',
14
- overlay: '#f0f0f0',
15
- border: '#cccccc',
16
- text: '#000000',
17
- textMuted: '#666666',
18
- textOnFillLight: '#ffffff',
19
- textOnFillDark: '#000000',
20
- primary: '#0000ff',
21
- secondary: '#ff00ff',
22
- accent: '#00cccc',
23
- destructive: '#ff0000',
24
- colors: {
25
- red: '#ff0000',
26
- orange: '#ff8000',
27
- yellow: '#ffcc00',
28
- green: '#00cc00',
29
- blue: '#0000ff',
30
- purple: '#cc00cc',
31
- teal: '#008080',
32
- cyan: '#00cccc',
33
- gray: '#808080',
34
- black: '#000000',
35
- white: '#f0f0f0',
36
- },
37
- },
38
- dark: {
39
- bg: '#000000',
40
- surface: '#111111',
41
- overlay: '#1a1a1a',
42
- border: '#333333',
43
- text: '#ffffff',
44
- textMuted: '#aaaaaa',
45
- textOnFillLight: '#ffffff',
46
- textOnFillDark: '#000000',
47
- primary: '#00ccff',
48
- secondary: '#ff00ff',
49
- accent: '#ffff00',
50
- destructive: '#ff0000',
51
- colors: {
52
- red: '#ff0000',
53
- orange: '#ff8000',
54
- yellow: '#ffff00',
55
- green: '#00ff00',
56
- blue: '#0066ff',
57
- purple: '#ff00ff',
58
- teal: '#00cccc',
59
- cyan: '#00ffff',
60
- gray: '#808080',
61
- black: '#111111',
62
- white: '#ffffff',
63
- },
64
- },
65
- };
66
-
67
- registerPalette(boldPalette);