ggaction 0.0.9 → 0.0.11

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 (222) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +5 -4
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. package/src/actions/errorBars/resolve.js +0 -25
@@ -1,7 +1,12 @@
1
1
  import { deriveArcSectors } from "../../../grammar/arcs.js";
2
+ import { resolvePolarPoint } from "../../../grammar/polarPaths.js";
2
3
  import { resolvePolarFrame } from "../../../grammar/polar.js";
3
4
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
4
- import { finalizeItems, uniqueFields } from "./common.js";
5
+ import {
6
+ concreteProperties,
7
+ finalizeItems,
8
+ uniqueFields
9
+ } from "./common.js";
5
10
 
6
11
  export function resolveArcItems(program, layer, dataset) {
7
12
  const thetaScale = program.resolvedScales[layer.encoding?.theta?.scale];
@@ -13,7 +18,8 @@ export function resolveArcItems(program, layer, dataset) {
13
18
  frame,
14
19
  innerRadiusRatio: program.markConfigs[layer.id]?.innerRadius ?? 0
15
20
  });
16
- const definitions = derived.sectors.map(sector => {
21
+ const graphic = program.graphicSpec.objects[layer.id];
22
+ const definitions = derived.sectors.map((sector, index) => {
17
23
  const members = sector.sourceIndices.map(index => dataset.values[index]);
18
24
  return {
19
25
  fields: uniqueFields(members),
@@ -22,6 +28,14 @@ export function resolveArcItems(program, layer, dataset) {
22
28
  ...(sector.radius === undefined ? {} : { radius: sector.radius }),
23
29
  ...(sector.color === undefined ? {} : { color: sector.color })
24
30
  },
31
+ properties: {
32
+ ...concreteProperties(graphic.items[index]?.properties),
33
+ ...resolvePolarPoint(
34
+ frame,
35
+ (sector.startTheta + sector.endTheta) / 2,
36
+ (sector.innerRadius + sector.outerRadius) / 2
37
+ )
38
+ },
25
39
  members
26
40
  };
27
41
  });
@@ -6,6 +6,7 @@ import {
6
6
  resolveBarOffsetChannel,
7
7
  resolveBarGrain
8
8
  } from "../../../grammar/bars/policy.js";
9
+ import { numericExtent } from "../../../grammar/numeric.js";
9
10
  import { layoutSeriesPartition } from "../../../grammar/seriesLayout.js";
10
11
  import {
11
12
  readNominalField,
@@ -215,14 +216,15 @@ function barStackDefinitions(layer, barGrain, definitions) {
215
216
  `Bar mark "${layer.id}" stack ${index} has no resolved semantic endpoints.`
216
217
  );
217
218
  }
219
+ const [minimum, maximum] = numericExtent(endpoints);
218
220
  const members = grouped.flatMap(definition => definition.members);
219
221
  return {
220
222
  key: itemKey(layer, "stack", index),
221
223
  fields: uniqueFields(members),
222
224
  channels: {
223
225
  ...sharedChannels(grouped),
224
- [measure]: Math.min(...endpoints),
225
- [secondary]: Math.max(...endpoints)
226
+ [measure]: minimum,
227
+ [secondary]: maximum
226
228
  },
227
229
  members,
228
230
  graphicIndices: entries.map(entry => entry.index)
@@ -1,5 +1,8 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../../../core/immutable.js";
2
- import { MARK_GRAPHIC_PROPERTIES } from "../../../grammar/markSelection.js";
2
+ import {
3
+ MARK_GRAPHIC_PROPERTIES,
4
+ selectMarkItemKeys
5
+ } from "../../../grammar/markSelection.js";
3
6
  import { unionConcreteGraphicBounds } from
4
7
  "../../../grammar/schemas/graphicBounds.js";
5
8
 
@@ -129,3 +132,20 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
129
132
  });
130
133
  }));
131
134
  }
135
+
136
+ export function reorderHighlightedItems(program, layer, definitions) {
137
+ let ordered = definitions;
138
+ for (const config of Object.values(
139
+ program.materializationConfigs.highlights ?? {}
140
+ )) {
141
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
142
+ const selection = program.materializationConfigs.selections?.[config.selection];
143
+ if (selection?.target !== layer.id) continue;
144
+ const selected = new Set(selectMarkItemKeys(ordered, selection.selector));
145
+ ordered = [
146
+ ...ordered.filter(definition => !selected.has(definition.key)),
147
+ ...ordered.filter(definition => selected.has(definition.key))
148
+ ];
149
+ }
150
+ return ordered;
151
+ }
@@ -3,8 +3,12 @@ import {
3
3
  deriveAreaSeries,
4
4
  deriveDensityAreaSeries
5
5
  } from "../../../grammar/areaSeries.js";
6
- import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
+ import {
7
+ deriveLineSeries,
8
+ resolveLineBins
9
+ } from "../../../grammar/lineSeries.js";
7
10
  import { findUpstreamTransform } from "../../dataProvenance.js";
11
+ import { requireSemanticScale } from "../../../selectors/scales.js";
8
12
  import {
9
13
  channelMapFromRow,
10
14
  finalizeItems,
@@ -61,7 +65,20 @@ export function resolveLineItems(program, layer, dataset) {
61
65
  });
62
66
  return finalizeItems(program, layer, "row", definitions, "path");
63
67
  }
64
- const derived = deriveLineSeries(dataset.values, layer);
68
+ const x = layer.encoding?.x;
69
+ const derived = deriveLineSeries(
70
+ dataset.values,
71
+ layer,
72
+ x?.bin === undefined
73
+ ? undefined
74
+ : {
75
+ xBinBoundaries: resolveLineBins(
76
+ dataset.values,
77
+ layer,
78
+ requireSemanticScale(program, x.scale)
79
+ ).boundaries
80
+ }
81
+ );
65
82
  return finalizeItems(
66
83
  program,
67
84
  layer,
@@ -1,10 +1,10 @@
1
- import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
1
  import {
3
2
  channelMapFromRow,
4
3
  concreteProperties,
5
4
  finalizeItems,
6
5
  itemKey,
7
- ownFields
6
+ ownFields,
7
+ reorderHighlightedItems
8
8
  } from "./common.js";
9
9
 
10
10
  export function resolvePointItems(program, layer, dataset) {
@@ -22,30 +22,18 @@ export function resolvePointItems(program, layer, dataset) {
22
22
  ) {
23
23
  throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
24
24
  }
25
- let definitions = dataset.values.map((row, index) => ({
25
+ const definitions = dataset.values.map((row, index) => ({
26
26
  key: itemKey(layer, "point", index),
27
27
  fields: ownFields(row),
28
28
  channels: channelMapFromRow(row, layer),
29
29
  properties: concreteProperties(graphic.items[index]?.properties),
30
30
  members: [row]
31
31
  }));
32
- for (const config of Object.values(
33
- program.materializationConfigs.highlights ?? {}
34
- )) {
35
- if (config.target !== layer.id || config.bringToFront !== true) continue;
36
- const selection = program.materializationConfigs.selections?.[config.selection];
37
- if (selection?.target !== layer.id) continue;
38
- const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
39
- definitions = [
40
- ...definitions.filter(definition => !selected.has(definition.key)),
41
- ...definitions.filter(definition => selected.has(definition.key))
42
- ];
43
- }
44
32
  return finalizeItems(
45
33
  program,
46
34
  layer,
47
35
  "point",
48
- definitions,
36
+ reorderHighlightedItems(program, layer, definitions),
49
37
  program.graphicSpec.objects[layer.id]?.type
50
38
  );
51
39
  }
@@ -1,10 +1,10 @@
1
- import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
1
  import {
3
2
  channelMapFromRow,
4
3
  concreteProperties,
5
4
  finalizeItems,
6
5
  itemKey,
7
- ownFields
6
+ ownFields,
7
+ reorderHighlightedItems
8
8
  } from "./common.js";
9
9
 
10
10
  export function resolveTickItems(program, layer, dataset) {
@@ -15,25 +15,17 @@ export function resolveTickItems(program, layer, dataset) {
15
15
  if (!Array.isArray(graphic?.items) || !completePosition) {
16
16
  throw new Error(`Tick mark "${layer.id}" is incomplete for selection.`);
17
17
  }
18
- let definitions = dataset.values.map((row, index) => ({
19
- key: itemKey(layer, "tick", index),
20
- fields: ownFields(row),
21
- channels: channelMapFromRow(row, layer),
22
- properties: concreteProperties(graphic.items[index]?.properties),
23
- members: [row]
24
- }));
25
- for (const config of Object.values(
26
- program.materializationConfigs.highlights ?? {}
27
- )) {
28
- if (config.target !== layer.id || config.bringToFront !== true) continue;
29
- const selection = program.materializationConfigs.selections?.[config.selection];
30
- if (selection?.target !== layer.id) continue;
31
- const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
32
- definitions = [
33
- ...definitions.filter(definition => !selected.has(definition.key)),
34
- ...definitions.filter(definition => selected.has(definition.key))
35
- ];
36
- }
18
+ let definitions = reorderHighlightedItems(
19
+ program,
20
+ layer,
21
+ dataset.values.map((row, index) => ({
22
+ key: itemKey(layer, "tick", index),
23
+ fields: ownFields(row),
24
+ channels: channelMapFromRow(row, layer),
25
+ properties: concreteProperties(graphic.items[index]?.properties),
26
+ members: [row]
27
+ }))
28
+ );
37
29
  definitions = definitions.map((definition, index) => ({
38
30
  ...definition,
39
31
  properties: concreteProperties(graphic.items[index]?.properties)
@@ -1,4 +1,5 @@
1
1
  import { createPointShapeGraphic } from "../../grammar/pointShapes.js";
2
+ import { finiteMidpoint } from "../../grammar/numeric.js";
2
3
 
3
4
  function pointCenterAndArea(child) {
4
5
  const properties = child.properties;
@@ -19,17 +20,23 @@ function pointCenterAndArea(child) {
19
20
  const points = properties.commands.filter(command =>
20
21
  command.op === "M" || command.op === "L"
21
22
  );
22
- const xs = points.map(point => point.x);
23
- const ys = points.map(point => point.y);
23
+ let left = Infinity;
24
+ let right = -Infinity;
25
+ let top = Infinity;
26
+ let bottom = -Infinity;
24
27
  let twiceArea = 0;
25
28
  for (let index = 0; index < points.length; index += 1) {
26
29
  const current = points[index];
27
30
  const next = points[(index + 1) % points.length];
31
+ left = Math.min(left, current.x);
32
+ right = Math.max(right, current.x);
33
+ top = Math.min(top, current.y);
34
+ bottom = Math.max(bottom, current.y);
28
35
  twiceArea += current.x * next.y - next.x * current.y;
29
36
  }
30
37
  return {
31
- x: (Math.min(...xs) + Math.max(...xs)) / 2,
32
- y: (Math.min(...ys) + Math.max(...ys)) / 2,
38
+ x: finiteMidpoint(left, right),
39
+ y: finiteMidpoint(top, bottom),
33
40
  area: Math.abs(twiceArea) / 2
34
41
  };
35
42
  }
@@ -29,6 +29,22 @@ function normalizeOffset(offset) {
29
29
  return { x, y };
30
30
  }
31
31
 
32
+ function normalizeOptionalStroke(args, mark) {
33
+ const stroke = args.stroke === undefined
34
+ ? undefined
35
+ : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
36
+ const strokeWidth = args.strokeWidth === undefined
37
+ ? undefined
38
+ : validateNonNegativeFinite(
39
+ args.strokeWidth,
40
+ `${mark} highlight strokeWidth`
41
+ );
42
+ if (strokeWidth !== undefined && stroke === undefined) {
43
+ throw new Error(`${mark} highlight strokeWidth requires stroke.`);
44
+ }
45
+ return { stroke, strokeWidth };
46
+ }
47
+
32
48
  export function normalizeDimOthers(dimOthers) {
33
49
  if (dimOthers === undefined || dimOthers === false) return false;
34
50
  if (dimOthers === true) return { opacity: DEFAULT_DIM_OPACITY };
@@ -58,15 +74,7 @@ export function normalizePointHighlightStyle(args) {
58
74
  const opacity = args.opacity === undefined
59
75
  ? undefined
60
76
  : validateUnitInterval(args.opacity, "Point highlight opacity");
61
- const stroke = args.stroke === undefined
62
- ? undefined
63
- : validateNonEmptyString(args.stroke, "Point highlight stroke");
64
- const strokeWidth = args.strokeWidth === undefined
65
- ? undefined
66
- : validateNonNegativeFinite(args.strokeWidth, "Point highlight strokeWidth");
67
- if (strokeWidth !== undefined && stroke === undefined) {
68
- throw new Error("Point highlight strokeWidth requires stroke.");
69
- }
77
+ const { stroke, strokeWidth } = normalizeOptionalStroke(args, "Point");
70
78
  return {
71
79
  fill,
72
80
  ...(opacity === undefined ? {} : { opacity }),
@@ -96,15 +104,7 @@ function normalizeRectangularHighlightStyle(args, mark, { offset = false } = {})
96
104
  const opacity = args.opacity === undefined
97
105
  ? undefined
98
106
  : validateUnitInterval(args.opacity, `${mark} highlight opacity`);
99
- const stroke = args.stroke === undefined
100
- ? undefined
101
- : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
102
- const strokeWidth = args.strokeWidth === undefined
103
- ? undefined
104
- : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
105
- if (strokeWidth !== undefined && stroke === undefined) {
106
- throw new Error(`${mark} highlight strokeWidth requires stroke.`);
107
- }
107
+ const { stroke, strokeWidth } = normalizeOptionalStroke(args, mark);
108
108
  const hasAppearanceOverride = opacity !== undefined || stroke !== undefined ||
109
109
  strokeWidth !== undefined || args.offset !== undefined;
110
110
  const fill = explicitFill === undefined && hasAppearanceOverride
@@ -178,15 +178,7 @@ export function normalizeAreaHighlightStyle(args, mark = "Area") {
178
178
  if (args.color !== undefined && args.fill !== undefined) {
179
179
  throw new Error(`${mark} highlight accepts color or fill, not both.`);
180
180
  }
181
- const stroke = args.stroke === undefined
182
- ? undefined
183
- : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
184
- const strokeWidth = args.strokeWidth === undefined
185
- ? undefined
186
- : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
187
- if (strokeWidth !== undefined && stroke === undefined) {
188
- throw new Error(`${mark} highlight strokeWidth requires stroke.`);
189
- }
181
+ const { stroke, strokeWidth } = normalizeOptionalStroke(args, mark);
190
182
  return {
191
183
  fill: validateNonEmptyString(
192
184
  args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
@@ -104,7 +104,10 @@ function relativeLuminance(color) {
104
104
  }
105
105
 
106
106
  function sourceTextConfig(config, source, item) {
107
- if (source.mark.type !== "rect" || config.fillExplicit === true) return config;
107
+ if (
108
+ !["arc", "rect"].includes(source.mark.type) ||
109
+ config.fillExplicit === true
110
+ ) return config;
108
111
  const luminance = relativeLuminance(item.properties.fill);
109
112
  if (luminance === undefined) return config;
110
113
  return {
@@ -28,7 +28,7 @@ export const OVERVIEW_URI = "ggaction://overview";
28
28
  export const SEARCH_TOOL = Object.freeze({
29
29
  name: SEARCH_TOOL_NAME,
30
30
  title: "Search ggaction authoring knowledge",
31
- description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites, executable immutable steps, terminal unsupported constraints, and explicit unresolved decisions.",
31
+ description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites, syntax-valid immutable steps, applied options, explicit placeholders and unmatched requirements, terminal unsupported constraints, and unresolved decisions.",
32
32
  inputSchema: Object.freeze({
33
33
  type: "object",
34
34
  additionalProperties: false,
@@ -164,6 +164,7 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
164
164
  if (uri === OVERVIEW_URI) {
165
165
  return jsonResource(uri, {
166
166
  schemaVersion: resourcesArtifact.schemaVersion,
167
+ packageVersion: resourcesArtifact.packageVersion,
167
168
  title: resourcesArtifact.overview.title,
168
169
  text: resourcesArtifact.overview.text
169
170
  });
@@ -184,6 +185,7 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
184
185
  if (!recipe) throw new KnowledgeResourceError(`Unknown ggaction recipe: ${id}`);
185
186
  return jsonResource(uri, {
186
187
  schemaVersion: resourcesArtifact.schemaVersion,
188
+ packageVersion: resourcesArtifact.packageVersion,
187
189
  ...recipe,
188
190
  packet: searchGgaction(recipe.query)
189
191
  });
@@ -201,6 +203,9 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
201
203
  throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
202
204
  }
203
205
 
204
- if (cardsArtifact.schemaVersion !== 1 || resourcesArtifact.schemaVersion !== 2) {
205
- throw new Error("MCP action cards must use schemaVersion 1 and resources schemaVersion 2.");
206
+ if (cardsArtifact.schemaVersion !== 2 || resourcesArtifact.schemaVersion !== 2) {
207
+ throw new Error("MCP action cards and resources must use schemaVersion 2.");
208
+ }
209
+ if (resourcesArtifact.packageVersion !== cardsArtifact.packageVersion) {
210
+ throw new Error("MCP action cards and resources must use one packageVersion.");
206
211
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  isLinearGradientPaint,
3
- resolveLinearGradientCoordinates,
4
3
  validateFillPaint
5
4
  } from "../../grammar/paint.js";
5
+ import { resolveCanvasGradientCoordinates } from "./native.js";
6
6
 
7
7
  export function applyCanvasFill(context, fill, bounds, graphicId) {
8
8
  validateFillPaint(fill, `${graphicId}.fill`);
@@ -15,7 +15,7 @@ export function applyCanvasFill(context, fill, bounds, graphicId) {
15
15
  `Graphic "${graphicId}" requires Canvas context createLinearGradient().`
16
16
  );
17
17
  }
18
- const coordinates = resolveLinearGradientCoordinates(fill, bounds);
18
+ const coordinates = resolveCanvasGradientCoordinates(fill, bounds, graphicId);
19
19
  const gradient = context.createLinearGradient(
20
20
  coordinates.from.x,
21
21
  coordinates.from.y,
@@ -4,6 +4,10 @@ import { drawPathGraphic } from "./path.js";
4
4
  import { drawRectGraphic } from "./rect.js";
5
5
  import { drawTextGraphic } from "./text.js";
6
6
  import { requireFiniteProperty } from "./validation.js";
7
+ import {
8
+ preflightCanvasGraphicSpec,
9
+ requireCanvasNativeValue
10
+ } from "./native.js";
7
11
  import {
8
12
  requireSingleOrderedGraphicByType,
9
13
  walkGraphicTreeEvents
@@ -34,6 +38,8 @@ const DRAWING_CONTEXT_METHODS = Object.freeze([
34
38
  "rotate",
35
39
  "fillText"
36
40
  ]);
41
+ const MAX_RASTER_DIMENSION = 32_767;
42
+ const MAX_RASTER_PIXELS = 16_777_216;
37
43
 
38
44
  function requireDrawingContext(context) {
39
45
  if (context === null || typeof context !== "object") {
@@ -182,12 +188,39 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
182
188
  if (!Number.isFinite(pixelRatio) || pixelRatio <= 0) {
183
189
  throw new RangeError("render pixelRatio must be a positive finite number.");
184
190
  }
191
+ requireCanvasNativeValue(pixelRatio, "render Canvas pixelRatio");
192
+ if (Math.fround(pixelRatio) === 0) {
193
+ throw new RangeError(
194
+ "render Canvas pixelRatio must remain positive at native precision."
195
+ );
196
+ }
185
197
 
186
198
  const target = resolveGraphicRenderTarget(graphicSpec);
187
199
  const { width, height } = target;
200
+ const physicalWidth = Math.max(1, Math.round(width * pixelRatio));
201
+ const physicalHeight = Math.max(1, Math.round(height * pixelRatio));
202
+ if (![physicalWidth, physicalHeight].every(Number.isSafeInteger)) {
203
+ throw new RangeError(
204
+ "render physical Canvas dimensions must be finite safe integers."
205
+ );
206
+ }
207
+ if (
208
+ physicalWidth > MAX_RASTER_DIMENSION ||
209
+ physicalHeight > MAX_RASTER_DIMENSION
210
+ ) {
211
+ throw new RangeError(
212
+ `render physical Canvas dimensions must not exceed ${MAX_RASTER_DIMENSION}.`
213
+ );
214
+ }
215
+ if (physicalWidth * physicalHeight > MAX_RASTER_PIXELS) {
216
+ throw new RangeError(
217
+ `render physical Canvas pixel count must not exceed ${MAX_RASTER_PIXELS}.`
218
+ );
219
+ }
220
+ preflightCanvasGraphicSpec(target, pixelRatio);
188
221
 
189
- context.canvas.width = Math.round(width * pixelRatio);
190
- context.canvas.height = Math.round(height * pixelRatio);
222
+ context.canvas.width = physicalWidth;
223
+ context.canvas.height = physicalHeight;
191
224
  if (
192
225
  context.canvas.style !== null &&
193
226
  typeof context.canvas.style === "object"
@@ -0,0 +1,160 @@
1
+ import { resolveLinearGradientCoordinates } from "../../grammar/paint.js";
2
+ import { resolvePathCommandBounds } from
3
+ "../../grammar/schemas/graphicBounds.js";
4
+ import { walkGraphicTreeEvents } from
5
+ "../../grammar/schemas/graphicTree.js";
6
+ import { validateConcreteGraphicProperties } from
7
+ "../../grammar/schemas/concreteGraphic.js";
8
+
9
+ const MAX_NATIVE_VALUE = 16_777_216;
10
+ const NATIVE_PROPERTIES = {
11
+ canvas: ["x", "y", "width", "height"],
12
+ circle: ["x", "y", "radius", "strokeWidth"],
13
+ rect: ["x", "y", "width", "height", "strokeWidth"],
14
+ line: ["x1", "y1", "x2", "y2", "strokeWidth"],
15
+ path: ["strokeWidth"],
16
+ text: ["x", "y", "fontSize"]
17
+ };
18
+ const COMMAND_PROPERTIES = ["x", "y", "x1", "y1", "x2", "y2"];
19
+
20
+ export function requireCanvasNativeValue(value, label, scale = 1, offset = 0) {
21
+ const magnitude = Math.abs(value) + Math.abs(offset);
22
+ const scaled = magnitude * scale;
23
+ if (
24
+ !Number.isFinite(value) || magnitude > MAX_NATIVE_VALUE ||
25
+ !Number.isFinite(scaled) || scaled > MAX_NATIVE_VALUE
26
+ ) {
27
+ throw new RangeError(
28
+ `${label} must be finite with magnitude <= ${MAX_NATIVE_VALUE}.`
29
+ );
30
+ }
31
+ }
32
+
33
+ function requireValues(values, label, scale) {
34
+ for (const value of values) {
35
+ requireCanvasNativeValue(value, label, scale);
36
+ }
37
+ }
38
+
39
+ function requireProperties(properties, keys, label, scale, offset) {
40
+ for (const key of keys) {
41
+ const value = properties?.[key];
42
+ if (value !== undefined) {
43
+ requireCanvasNativeValue(
44
+ value,
45
+ label,
46
+ scale,
47
+ offset?.["xy".indexOf(key[0])]
48
+ );
49
+ }
50
+ }
51
+ }
52
+
53
+ export function resolveCanvasGradientCoordinates(fill, bounds, graphicId, scale = 1) {
54
+ const coordinates = resolveLinearGradientCoordinates(fill, bounds);
55
+ requireValues([
56
+ coordinates.from.x,
57
+ coordinates.from.y,
58
+ coordinates.to.x,
59
+ coordinates.to.y,
60
+ Math.hypot(
61
+ coordinates.to.x - coordinates.from.x,
62
+ coordinates.to.y - coordinates.from.y
63
+ )
64
+ ],
65
+ `Graphic "${graphicId}" Canvas linear gradient geometry`,
66
+ scale
67
+ );
68
+ return coordinates;
69
+ }
70
+
71
+ function preflightGraphic(id, graphic, scale, type, offset) {
72
+ const properties = graphic.properties ?? {};
73
+ const label = `Graphic "${id}" Canvas native geometry`;
74
+ requireProperties(
75
+ properties,
76
+ NATIVE_PROPERTIES[type] ?? [],
77
+ label,
78
+ scale,
79
+ offset
80
+ );
81
+ if (properties.rotation !== undefined) {
82
+ requireCanvasNativeValue(properties.rotation, label);
83
+ }
84
+ if (Array.isArray(properties.strokeDash)) {
85
+ requireValues(properties.strokeDash, label, scale);
86
+ }
87
+ const gradient = properties.fill?.type === "linear-gradient";
88
+
89
+ let bounds;
90
+ if (type === "canvas" || type === "rect" || type === "circle") {
91
+ const circle = type === "circle";
92
+ const width = circle ? properties.radius : properties.width;
93
+ const height = circle ? properties.radius : properties.height;
94
+ if ([properties.x, properties.y, width, height].every(Number.isFinite) &&
95
+ width >= 0 && height >= 0) {
96
+ bounds = {
97
+ left: properties.x - (circle ? width : 0),
98
+ right: properties.x + width,
99
+ top: properties.y - (circle ? height : 0),
100
+ bottom: properties.y + height
101
+ };
102
+ requireCanvasNativeValue(bounds.left, label, scale, offset?.[0]);
103
+ requireCanvasNativeValue(bounds.right, label, scale, offset?.[0]);
104
+ requireCanvasNativeValue(bounds.top, label, scale, offset?.[1]);
105
+ requireCanvasNativeValue(bounds.bottom, label, scale, offset?.[1]);
106
+ }
107
+ }
108
+ if (type === "path" && Array.isArray(properties.commands)) {
109
+ for (const command of properties.commands) {
110
+ requireProperties(command, COMMAND_PROPERTIES, label, scale, offset);
111
+ }
112
+ if (gradient) {
113
+ bounds = resolvePathCommandBounds(properties.commands);
114
+ }
115
+ }
116
+ if (gradient && bounds) {
117
+ resolveCanvasGradientCoordinates(properties.fill, bounds, id, scale);
118
+ }
119
+ if (NATIVE_PROPERTIES[type] !== undefined) {
120
+ validateConcreteGraphicProperties(type, properties);
121
+ }
122
+ }
123
+
124
+ export function preflightCanvasGraphicSpec(target, scale = 1) {
125
+ const offsets = [];
126
+ walkGraphicTreeEvents(target.graphicSpec, {
127
+ enter({ id, object }) {
128
+ let offset = offsets.at(-1);
129
+ if (object.type === "canvas") {
130
+ const parent = offset ?? [0, 0];
131
+ const properties = object.properties ?? {};
132
+ const x = id === target.canvasId ? 0 : properties.x;
133
+ const y = id === target.canvasId ? 0 : properties.y;
134
+ const effective = [parent[0] + x, parent[1] + y];
135
+ if (Number.isFinite(x) && Number.isFinite(y)) {
136
+ requireValues(
137
+ effective,
138
+ `Graphic "${id}" Canvas nested translation`,
139
+ scale
140
+ );
141
+ }
142
+ offsets.push(effective);
143
+ offset = id === target.canvasId ? undefined : parent;
144
+ }
145
+ preflightGraphic(id, object, scale, object.type, offset);
146
+ },
147
+ item({ id, object, owner }) {
148
+ preflightGraphic(
149
+ id,
150
+ object,
151
+ scale,
152
+ object.type ?? owner.type,
153
+ offsets.at(-1)
154
+ );
155
+ },
156
+ exit({ object }) {
157
+ if (object.type === "canvas") offsets.pop();
158
+ }
159
+ });
160
+ }
@@ -8,6 +8,7 @@ import {
8
8
  requireProgramGraphicSpec,
9
9
  resolveGraphicRenderTarget
10
10
  } from "./canvas/index.js";
11
+ import { preflightCanvasGraphicSpec } from "./canvas/native.js";
11
12
 
12
13
  const PDF_OPTIONS = new Set(["output", "metadata"]);
13
14
  const PDF_METADATA = new Set([
@@ -16,6 +17,7 @@ const PDF_METADATA = new Set([
16
17
  "subject",
17
18
  "keywords"
18
19
  ]);
20
+ const MAX_PDF_DIMENSION = 16_777_216;
19
21
 
20
22
  function requirePlainObject(value, label) {
21
23
  if (
@@ -93,10 +95,22 @@ function renderPDFBuffer(target, metadata) {
93
95
  return document.close();
94
96
  }
95
97
 
98
+ function requirePDFPageDimensions(target) {
99
+ if (![target.width, target.height].every(value =>
100
+ Number.isSafeInteger(value) && value > 0 && value <= MAX_PDF_DIMENSION
101
+ )) {
102
+ throw new RangeError(
103
+ `PDF page dimensions must be positive integers no larger than ${MAX_PDF_DIMENSION}.`
104
+ );
105
+ }
106
+ }
107
+
96
108
  export async function renderToPDF(program, options = {}) {
97
109
  const { output, metadata } = requirePDFOptions(options);
98
110
  const graphicSpec = requireProgramGraphicSpec(program);
99
111
  const target = resolveGraphicRenderTarget(graphicSpec);
112
+ requirePDFPageDimensions(target);
113
+ preflightCanvasGraphicSpec(target);
100
114
  const buffer = renderPDFBuffer(target, metadata);
101
115
 
102
116
  await mkdir(dirname(output), { recursive: true });