@spectratools/graphic-designer-cli 0.9.0 → 0.10.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/cli.js CHANGED
@@ -820,6 +820,21 @@ var drawLineSchema = z2.object({
820
820
  opacity: z2.number().min(0).max(1).default(1),
821
821
  shadow: drawShadowSchema.optional()
822
822
  }).strict();
823
+ var drawArcSchema = z2.object({
824
+ type: z2.literal("arc"),
825
+ center: z2.object({
826
+ x: z2.number(),
827
+ y: z2.number()
828
+ }).strict(),
829
+ radius: z2.number().positive(),
830
+ startAngle: z2.number(),
831
+ endAngle: z2.number(),
832
+ color: colorHexSchema2.default("#FFFFFF"),
833
+ width: z2.number().min(0.5).max(32).default(2),
834
+ dash: z2.array(z2.number()).max(6).optional(),
835
+ opacity: z2.number().min(0).max(1).default(1),
836
+ shadow: drawShadowSchema.optional()
837
+ }).strict();
823
838
  var drawPointSchema = z2.object({
824
839
  x: z2.number(),
825
840
  y: z2.number()
@@ -904,6 +919,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
904
919
  drawCircleSchema,
905
920
  drawTextSchema,
906
921
  drawLineSchema,
922
+ drawArcSchema,
907
923
  drawBezierSchema,
908
924
  drawPathSchema,
909
925
  drawBadgeSchema,
@@ -1039,6 +1055,8 @@ var connectionElementSchema = z2.object({
1039
1055
  label: z2.string().min(1).max(200).optional(),
1040
1056
  labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
1041
1057
  color: colorHexSchema2.optional(),
1058
+ fromColor: colorHexSchema2.optional(),
1059
+ toColor: colorHexSchema2.optional(),
1042
1060
  width: z2.number().min(0.5).max(10).optional(),
1043
1061
  strokeWidth: z2.number().min(0.5).max(10).default(2),
1044
1062
  arrowSize: z2.number().min(4).max(32).optional(),
@@ -3170,16 +3188,6 @@ function drawBezier(ctx, points, style) {
3170
3188
  ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
3171
3189
  ctx.stroke();
3172
3190
  }
3173
- function drawOrthogonalPath(ctx, from, to, style) {
3174
- const midX = (from.x + to.x) / 2;
3175
- applyLineStyle(ctx, style);
3176
- ctx.beginPath();
3177
- ctx.moveTo(from.x, from.y);
3178
- ctx.lineTo(midX, from.y);
3179
- ctx.lineTo(midX, to.y);
3180
- ctx.lineTo(to.x, to.y);
3181
- ctx.stroke();
3182
- }
3183
3191
 
3184
3192
  // src/renderers/connection.ts
3185
3193
  var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
@@ -3419,11 +3427,36 @@ function pointAlongPolyline(points, t) {
3419
3427
  }
3420
3428
  return points[points.length - 1];
3421
3429
  }
3422
- function drawCubicInterpolatedPath(ctx, points, style) {
3430
+ function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
3431
+ const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
3432
+ gradient.addColorStop(0, fromColor);
3433
+ gradient.addColorStop(0.5, baseColor);
3434
+ gradient.addColorStop(1, toColor);
3435
+ return gradient;
3436
+ }
3437
+ function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
3438
+ if (!fromColor || !toColor) {
3439
+ return baseColor;
3440
+ }
3441
+ return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
3442
+ }
3443
+ function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
3444
+ const midX = (from.x + to.x) / 2;
3445
+ ctx.strokeStyle = stroke;
3446
+ ctx.lineWidth = style.width;
3447
+ ctx.setLineDash(style.dash ?? []);
3448
+ ctx.beginPath();
3449
+ ctx.moveTo(from.x, from.y);
3450
+ ctx.lineTo(midX, from.y);
3451
+ ctx.lineTo(midX, to.y);
3452
+ ctx.lineTo(to.x, to.y);
3453
+ ctx.stroke();
3454
+ }
3455
+ function drawCubicInterpolatedPath(ctx, points, style, stroke) {
3423
3456
  if (points.length < 2) {
3424
3457
  return;
3425
3458
  }
3426
- ctx.strokeStyle = style.color;
3459
+ ctx.strokeStyle = stroke;
3427
3460
  ctx.lineWidth = style.width;
3428
3461
  ctx.setLineDash(style.dash ?? []);
3429
3462
  ctx.beginPath();
@@ -3494,7 +3527,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3494
3527
  conn.fromAnchor,
3495
3528
  conn.toAnchor
3496
3529
  );
3497
- ctx.strokeStyle = style.color;
3530
+ const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
3531
+ ctx.strokeStyle = stroke;
3498
3532
  ctx.lineWidth = style.width;
3499
3533
  ctx.setLineDash(style.dash ?? []);
3500
3534
  ctx.beginPath();
@@ -3536,7 +3570,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3536
3570
  );
3537
3571
  const [p0, cp1, cp2, pMid] = first;
3538
3572
  const [, cp3, cp4, p3] = second;
3539
- ctx.strokeStyle = style.color;
3573
+ const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
3574
+ ctx.strokeStyle = stroke;
3540
3575
  ctx.lineWidth = style.width;
3541
3576
  ctx.setLineDash(style.dash ?? []);
3542
3577
  ctx.beginPath();
@@ -3579,10 +3614,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3579
3614
  endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
3580
3615
  startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
3581
3616
  endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
3617
+ const stroke = resolveConnectionStroke(
3618
+ ctx,
3619
+ startPoint,
3620
+ endPoint,
3621
+ conn.fromColor,
3622
+ style.color,
3623
+ conn.toColor
3624
+ );
3582
3625
  if (useElkRoute) {
3583
- drawCubicInterpolatedPath(ctx, linePoints, style);
3626
+ drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
3584
3627
  } else {
3585
- drawOrthogonalPath(ctx, startPoint, endPoint, style);
3628
+ drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
3586
3629
  }
3587
3630
  labelPoint = pointAlongPolyline(linePoints, labelT);
3588
3631
  }
@@ -3887,6 +3930,9 @@ function measureTextBounds(ctx, options) {
3887
3930
  function angleBetween(from, to) {
3888
3931
  return Math.atan2(to.y - from.y, to.x - from.x);
3889
3932
  }
3933
+ function degreesToRadians(angle) {
3934
+ return angle * Math.PI / 180;
3935
+ }
3890
3936
  function pathBounds(operations) {
3891
3937
  let minX = Number.POSITIVE_INFINITY;
3892
3938
  let minY = Number.POSITIVE_INFINITY;
@@ -4124,6 +4170,34 @@ function renderDrawCommands(ctx, commands, theme) {
4124
4170
  });
4125
4171
  break;
4126
4172
  }
4173
+ case "arc": {
4174
+ const startAngle = degreesToRadians(command.startAngle);
4175
+ const endAngle = degreesToRadians(command.endAngle);
4176
+ withOpacity(ctx, command.opacity, () => {
4177
+ applyDrawShadow(ctx, command.shadow);
4178
+ ctx.beginPath();
4179
+ ctx.setLineDash(command.dash ?? []);
4180
+ ctx.lineWidth = command.width;
4181
+ ctx.strokeStyle = command.color;
4182
+ ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
4183
+ ctx.stroke();
4184
+ });
4185
+ rendered.push({
4186
+ id,
4187
+ kind: "draw",
4188
+ bounds: expandRect(
4189
+ {
4190
+ x: command.center.x - command.radius,
4191
+ y: command.center.y - command.radius,
4192
+ width: command.radius * 2,
4193
+ height: command.radius * 2
4194
+ },
4195
+ command.width / 2
4196
+ ),
4197
+ foregroundColor: command.color
4198
+ });
4199
+ break;
4200
+ }
4127
4201
  case "bezier": {
4128
4202
  const points = command.points;
4129
4203
  withOpacity(ctx, command.opacity, () => {
@@ -4767,6 +4841,18 @@ async function renderDesign(input, options = {}) {
4767
4841
  const specHash = computeSpecHash(spec);
4768
4842
  const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
4769
4843
  const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4844
+ const iteration = options.iteration;
4845
+ if (iteration) {
4846
+ if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
4847
+ throw new Error("Iteration metadata requires iteration to be a positive integer.");
4848
+ }
4849
+ if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
4850
+ throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
4851
+ }
4852
+ if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
4853
+ throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
4854
+ }
4855
+ }
4770
4856
  const renderScale = resolveRenderScale(spec);
4771
4857
  const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
4772
4858
  const ctx = canvas.getContext("2d");
@@ -5007,7 +5093,8 @@ async function renderDesign(input, options = {}) {
5007
5093
  layout: {
5008
5094
  safeFrame,
5009
5095
  elements
5010
- }
5096
+ },
5097
+ ...iteration ? { iteration } : {}
5011
5098
  };
5012
5099
  return {
5013
5100
  png: pngBuffer,
@@ -5314,6 +5401,12 @@ var renderOutputSchema = z3.object({
5314
5401
  artifactHash: z3.string(),
5315
5402
  specHash: z3.string(),
5316
5403
  layoutMode: z3.string(),
5404
+ iteration: z3.object({
5405
+ current: z3.number().int().positive(),
5406
+ max: z3.number().int().positive(),
5407
+ isLast: z3.boolean(),
5408
+ notes: z3.string().optional()
5409
+ }).optional(),
5317
5410
  qa: z3.object({
5318
5411
  pass: z3.boolean(),
5319
5412
  issueCount: z3.number(),
@@ -5400,8 +5493,30 @@ function readCodeRange(code, start, end) {
5400
5493
  const lines = code.split(/\r?\n/u);
5401
5494
  return lines.slice(start - 1, end).join("\n");
5402
5495
  }
5496
+ function parseIterationMeta(options) {
5497
+ if (options.iteration == null) {
5498
+ if (options.maxIterations != null || options.iterationNotes || options.previousHash) {
5499
+ throw new Error(
5500
+ "--iteration is required when using --max-iterations, --iteration-notes, or --previous-hash."
5501
+ );
5502
+ }
5503
+ return void 0;
5504
+ }
5505
+ if (options.maxIterations != null && options.maxIterations < options.iteration) {
5506
+ throw new Error("--max-iterations must be greater than or equal to --iteration.");
5507
+ }
5508
+ return {
5509
+ iteration: options.iteration,
5510
+ ...options.maxIterations != null ? { maxIterations: options.maxIterations } : {},
5511
+ ...options.iterationNotes ? { notes: options.iterationNotes } : {},
5512
+ ...options.previousHash ? { previousHash: options.previousHash } : {}
5513
+ };
5514
+ }
5403
5515
  async function runRenderPipeline(spec, options) {
5404
- const renderResult = await renderDesign(spec, { generatorVersion: pkg.version });
5516
+ const renderResult = await renderDesign(spec, {
5517
+ generatorVersion: pkg.version,
5518
+ ...options.iteration ? { iteration: options.iteration } : {}
5519
+ });
5405
5520
  const written = await writeRenderArtifacts(renderResult, options.out);
5406
5521
  const specPath = options.specOut ? resolve4(options.specOut) : specPathFor(written.metadataPath);
5407
5522
  await mkdir2(dirname3(specPath), { recursive: true });
@@ -5418,6 +5533,14 @@ async function runRenderPipeline(spec, options) {
5418
5533
  artifactHash: written.metadata.artifactHash,
5419
5534
  specHash: written.metadata.specHash,
5420
5535
  layoutMode: spec.layout.mode,
5536
+ ...written.metadata.iteration ? {
5537
+ iteration: {
5538
+ current: written.metadata.iteration.iteration,
5539
+ max: written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration,
5540
+ isLast: (written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration) === written.metadata.iteration.iteration,
5541
+ ...written.metadata.iteration.notes ? { notes: written.metadata.iteration.notes } : {}
5542
+ }
5543
+ } : {},
5421
5544
  qa: {
5422
5545
  pass: qa.pass,
5423
5546
  issueCount: qa.issues.length,
@@ -5431,6 +5554,10 @@ cli.command("render", {
5431
5554
  spec: z3.string().describe('Path to DesignSpec JSON file (or "-" to read JSON from stdin)'),
5432
5555
  out: z3.string().describe("Output file path (.png) or output directory"),
5433
5556
  specOut: z3.string().optional().describe("Optional explicit output path for normalized spec JSON"),
5557
+ iteration: z3.number().int().positive().optional().describe("Optional iteration number for iterative workflows (1-indexed)"),
5558
+ iterationNotes: z3.string().optional().describe("Optional notes for the current iteration metadata"),
5559
+ maxIterations: z3.number().int().positive().optional().describe("Optional maximum planned iteration count"),
5560
+ previousHash: z3.string().optional().describe("Optional artifact hash from the previous iteration"),
5434
5561
  allowQaFail: z3.boolean().default(false).describe("Allow render success even if QA fails")
5435
5562
  }),
5436
5563
  output: renderOutputSchema,
@@ -5445,9 +5572,26 @@ cli.command("render", {
5445
5572
  ],
5446
5573
  async run(c) {
5447
5574
  const spec = parseDesignSpec(await readJson(c.options.spec));
5575
+ let iteration;
5576
+ try {
5577
+ iteration = parseIterationMeta({
5578
+ ...c.options.iteration != null ? { iteration: c.options.iteration } : {},
5579
+ ...c.options.maxIterations != null ? { maxIterations: c.options.maxIterations } : {},
5580
+ ...c.options.iterationNotes ? { iterationNotes: c.options.iterationNotes } : {},
5581
+ ...c.options.previousHash ? { previousHash: c.options.previousHash } : {}
5582
+ });
5583
+ } catch (error) {
5584
+ const message = error instanceof Error ? error.message : String(error);
5585
+ return c.error({
5586
+ code: "INVALID_ITERATION_OPTIONS",
5587
+ message,
5588
+ retryable: false
5589
+ });
5590
+ }
5448
5591
  const runReport = await runRenderPipeline(spec, {
5449
5592
  out: c.options.out,
5450
- ...c.options.specOut ? { specOut: c.options.specOut } : {}
5593
+ ...c.options.specOut ? { specOut: c.options.specOut } : {},
5594
+ ...iteration ? { iteration } : {}
5451
5595
  });
5452
5596
  if (!runReport.qa.pass && !c.options.allowQaFail) {
5453
5597
  return c.error({
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Cli } from 'incur';
2
- import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-B_Z-KNqt.js';
3
- export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as DrawBadge, s as DrawBezier, t as DrawCircle, u as DrawFontFamily, v as DrawGradientRect, w as DrawLine, x as DrawPath, y as DrawPoint, z as DrawRect, E as DrawShadow, F as DrawText, G as DrawTextRow, H as DrawTextRowSegment, I as Element, J as FlowNodeElement, K as Gradient, L as GradientOverlayDecorator, M as GradientSpec, N as GradientStop, O as GridLayoutConfig, P as ImageElement, Q as LayoutConfig, S as LayoutSnapshot, U as ManualLayoutConfig, V as RainbowRuleDecorator, R as RenderMetadata, W as RenderResult, X as ShapeElement, Y as StackLayoutConfig, Z as TerminalElement, _ as TextElement, $ as ThemeInput, a0 as VignetteDecorator, a1 as WrittenArtifacts, a2 as builtInThemeBackgrounds, a3 as builtInThemes, a4 as computeSpecHash, a5 as connectionElementSchema, a6 as defaultAutoLayout, a7 as defaultCanvas, a8 as defaultConstraints, a9 as defaultGridLayout, aa as defaultLayout, ab as defaultStackLayout, ac as defaultTheme, ad as deriveSafeFrame, ae as designSpecSchema, af as diagramElementSchema, ag as diagramLayoutSchema, ah as diagramSpecSchema, ai as drawGradientRect, aj as drawRainbowRule, ak as drawVignette, al as flowNodeElementSchema, am as inferLayout, an as inferSidecarPath, ao as parseDesignSpec, ap as parseDiagramSpec, aq as renderDesign, ar as resolveTheme, as as writeRenderArtifacts } from './spec.schema-B_Z-KNqt.js';
2
+ import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-B6sXTTou.js';
3
+ export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as DrawArc, s as DrawBadge, t as DrawBezier, u as DrawCircle, v as DrawFontFamily, w as DrawGradientRect, x as DrawLine, y as DrawPath, z as DrawPoint, E as DrawRect, F as DrawShadow, G as DrawText, H as DrawTextRow, I as DrawTextRowSegment, J as Element, K as FlowNodeElement, L as Gradient, M as GradientOverlayDecorator, N as GradientSpec, O as GradientStop, P as GridLayoutConfig, Q as ImageElement, S as IterationMeta, U as LayoutConfig, V as LayoutSnapshot, W as ManualLayoutConfig, X as RainbowRuleDecorator, Y as RenderDesignOptions, R as RenderMetadata, Z as RenderResult, _ as ShapeElement, $ as StackLayoutConfig, a0 as TerminalElement, a1 as TextElement, a2 as ThemeInput, a3 as VignetteDecorator, a4 as WrittenArtifacts, a5 as builtInThemeBackgrounds, a6 as builtInThemes, a7 as computeSpecHash, a8 as connectionElementSchema, a9 as defaultAutoLayout, aa as defaultCanvas, ab as defaultConstraints, ac as defaultGridLayout, ad as defaultLayout, ae as defaultStackLayout, af as defaultTheme, ag as deriveSafeFrame, ah as designSpecSchema, ai as diagramElementSchema, aj as diagramLayoutSchema, ak as diagramSpecSchema, al as drawGradientRect, am as drawRainbowRule, an as drawVignette, ao as flowNodeElementSchema, ap as inferLayout, aq as inferSidecarPath, ar as parseDesignSpec, as as parseDiagramSpec, at as renderDesign, au as resolveTheme, av as writeRenderArtifacts } from './spec.schema-B6sXTTou.js';
4
4
  import { SKRSContext2D } from '@napi-rs/canvas';
5
5
  export { QaIssue, QaReferenceResult, QaReport, QaSeverity, readMetadata, runQa } from './qa.js';
6
6
  import { Highlighter } from 'shiki';
@@ -223,10 +223,10 @@ type ElkLayoutResult = LayoutResult & {
223
223
  /**
224
224
  * Render an array of freestyle draw commands onto a canvas context.
225
225
  *
226
- * Supports eight command types: `rect`, `circle`, `text`, `line`, `bezier`,
227
- * `path`, `badge`, and `gradient-rect`. Each command is rendered in order and
228
- * produces a corresponding {@link RenderedElement} with computed bounds for
229
- * downstream QA checks.
226
+ * Supports draw command types including `rect`, `circle`, `text`, `line`,
227
+ * `arc`, `bezier`, `path`, `badge`, `gradient-rect`, `grid`, and `text-row`.
228
+ * Each command is rendered in order and produces a corresponding
229
+ * {@link RenderedElement} with computed bounds for downstream QA checks.
230
230
  *
231
231
  * @param ctx - The `@napi-rs/canvas` 2D rendering context to draw into.
232
232
  * @param commands - Array of {@link DrawCommand} objects from the design spec's
package/dist/index.js CHANGED
@@ -829,6 +829,21 @@ var drawLineSchema = z2.object({
829
829
  opacity: z2.number().min(0).max(1).default(1),
830
830
  shadow: drawShadowSchema.optional()
831
831
  }).strict();
832
+ var drawArcSchema = z2.object({
833
+ type: z2.literal("arc"),
834
+ center: z2.object({
835
+ x: z2.number(),
836
+ y: z2.number()
837
+ }).strict(),
838
+ radius: z2.number().positive(),
839
+ startAngle: z2.number(),
840
+ endAngle: z2.number(),
841
+ color: colorHexSchema2.default("#FFFFFF"),
842
+ width: z2.number().min(0.5).max(32).default(2),
843
+ dash: z2.array(z2.number()).max(6).optional(),
844
+ opacity: z2.number().min(0).max(1).default(1),
845
+ shadow: drawShadowSchema.optional()
846
+ }).strict();
832
847
  var drawPointSchema = z2.object({
833
848
  x: z2.number(),
834
849
  y: z2.number()
@@ -913,6 +928,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
913
928
  drawCircleSchema,
914
929
  drawTextSchema,
915
930
  drawLineSchema,
931
+ drawArcSchema,
916
932
  drawBezierSchema,
917
933
  drawPathSchema,
918
934
  drawBadgeSchema,
@@ -1049,6 +1065,8 @@ var connectionElementSchema = z2.object({
1049
1065
  label: z2.string().min(1).max(200).optional(),
1050
1066
  labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
1051
1067
  color: colorHexSchema2.optional(),
1068
+ fromColor: colorHexSchema2.optional(),
1069
+ toColor: colorHexSchema2.optional(),
1052
1070
  width: z2.number().min(0.5).max(10).optional(),
1053
1071
  strokeWidth: z2.number().min(0.5).max(10).default(2),
1054
1072
  arrowSize: z2.number().min(4).max(32).optional(),
@@ -3187,16 +3205,6 @@ function drawBezier(ctx, points, style) {
3187
3205
  ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
3188
3206
  ctx.stroke();
3189
3207
  }
3190
- function drawOrthogonalPath(ctx, from, to, style) {
3191
- const midX = (from.x + to.x) / 2;
3192
- applyLineStyle(ctx, style);
3193
- ctx.beginPath();
3194
- ctx.moveTo(from.x, from.y);
3195
- ctx.lineTo(midX, from.y);
3196
- ctx.lineTo(midX, to.y);
3197
- ctx.lineTo(to.x, to.y);
3198
- ctx.stroke();
3199
- }
3200
3208
 
3201
3209
  // src/renderers/connection.ts
3202
3210
  var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
@@ -3436,11 +3444,36 @@ function pointAlongPolyline(points, t) {
3436
3444
  }
3437
3445
  return points[points.length - 1];
3438
3446
  }
3439
- function drawCubicInterpolatedPath(ctx, points, style) {
3447
+ function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
3448
+ const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
3449
+ gradient.addColorStop(0, fromColor);
3450
+ gradient.addColorStop(0.5, baseColor);
3451
+ gradient.addColorStop(1, toColor);
3452
+ return gradient;
3453
+ }
3454
+ function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
3455
+ if (!fromColor || !toColor) {
3456
+ return baseColor;
3457
+ }
3458
+ return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
3459
+ }
3460
+ function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
3461
+ const midX = (from.x + to.x) / 2;
3462
+ ctx.strokeStyle = stroke;
3463
+ ctx.lineWidth = style.width;
3464
+ ctx.setLineDash(style.dash ?? []);
3465
+ ctx.beginPath();
3466
+ ctx.moveTo(from.x, from.y);
3467
+ ctx.lineTo(midX, from.y);
3468
+ ctx.lineTo(midX, to.y);
3469
+ ctx.lineTo(to.x, to.y);
3470
+ ctx.stroke();
3471
+ }
3472
+ function drawCubicInterpolatedPath(ctx, points, style, stroke) {
3440
3473
  if (points.length < 2) {
3441
3474
  return;
3442
3475
  }
3443
- ctx.strokeStyle = style.color;
3476
+ ctx.strokeStyle = stroke;
3444
3477
  ctx.lineWidth = style.width;
3445
3478
  ctx.setLineDash(style.dash ?? []);
3446
3479
  ctx.beginPath();
@@ -3511,7 +3544,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3511
3544
  conn.fromAnchor,
3512
3545
  conn.toAnchor
3513
3546
  );
3514
- ctx.strokeStyle = style.color;
3547
+ const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
3548
+ ctx.strokeStyle = stroke;
3515
3549
  ctx.lineWidth = style.width;
3516
3550
  ctx.setLineDash(style.dash ?? []);
3517
3551
  ctx.beginPath();
@@ -3553,7 +3587,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3553
3587
  );
3554
3588
  const [p0, cp1, cp2, pMid] = first;
3555
3589
  const [, cp3, cp4, p3] = second;
3556
- ctx.strokeStyle = style.color;
3590
+ const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
3591
+ ctx.strokeStyle = stroke;
3557
3592
  ctx.lineWidth = style.width;
3558
3593
  ctx.setLineDash(style.dash ?? []);
3559
3594
  ctx.beginPath();
@@ -3596,10 +3631,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3596
3631
  endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
3597
3632
  startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
3598
3633
  endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
3634
+ const stroke = resolveConnectionStroke(
3635
+ ctx,
3636
+ startPoint,
3637
+ endPoint,
3638
+ conn.fromColor,
3639
+ style.color,
3640
+ conn.toColor
3641
+ );
3599
3642
  if (useElkRoute) {
3600
- drawCubicInterpolatedPath(ctx, linePoints, style);
3643
+ drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
3601
3644
  } else {
3602
- drawOrthogonalPath(ctx, startPoint, endPoint, style);
3645
+ drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
3603
3646
  }
3604
3647
  labelPoint = pointAlongPolyline(linePoints, labelT);
3605
3648
  }
@@ -3904,6 +3947,9 @@ function measureTextBounds(ctx, options) {
3904
3947
  function angleBetween(from, to) {
3905
3948
  return Math.atan2(to.y - from.y, to.x - from.x);
3906
3949
  }
3950
+ function degreesToRadians(angle) {
3951
+ return angle * Math.PI / 180;
3952
+ }
3907
3953
  function pathBounds(operations) {
3908
3954
  let minX = Number.POSITIVE_INFINITY;
3909
3955
  let minY = Number.POSITIVE_INFINITY;
@@ -4141,6 +4187,34 @@ function renderDrawCommands(ctx, commands, theme) {
4141
4187
  });
4142
4188
  break;
4143
4189
  }
4190
+ case "arc": {
4191
+ const startAngle = degreesToRadians(command.startAngle);
4192
+ const endAngle = degreesToRadians(command.endAngle);
4193
+ withOpacity(ctx, command.opacity, () => {
4194
+ applyDrawShadow(ctx, command.shadow);
4195
+ ctx.beginPath();
4196
+ ctx.setLineDash(command.dash ?? []);
4197
+ ctx.lineWidth = command.width;
4198
+ ctx.strokeStyle = command.color;
4199
+ ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
4200
+ ctx.stroke();
4201
+ });
4202
+ rendered.push({
4203
+ id,
4204
+ kind: "draw",
4205
+ bounds: expandRect(
4206
+ {
4207
+ x: command.center.x - command.radius,
4208
+ y: command.center.y - command.radius,
4209
+ width: command.radius * 2,
4210
+ height: command.radius * 2
4211
+ },
4212
+ command.width / 2
4213
+ ),
4214
+ foregroundColor: command.color
4215
+ });
4216
+ break;
4217
+ }
4144
4218
  case "bezier": {
4145
4219
  const points = command.points;
4146
4220
  withOpacity(ctx, command.opacity, () => {
@@ -4784,6 +4858,18 @@ async function renderDesign(input, options = {}) {
4784
4858
  const specHash = computeSpecHash(spec);
4785
4859
  const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
4786
4860
  const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4861
+ const iteration = options.iteration;
4862
+ if (iteration) {
4863
+ if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
4864
+ throw new Error("Iteration metadata requires iteration to be a positive integer.");
4865
+ }
4866
+ if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
4867
+ throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
4868
+ }
4869
+ if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
4870
+ throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
4871
+ }
4872
+ }
4787
4873
  const renderScale = resolveRenderScale(spec);
4788
4874
  const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
4789
4875
  const ctx = canvas.getContext("2d");
@@ -5024,7 +5110,8 @@ async function renderDesign(input, options = {}) {
5024
5110
  layout: {
5025
5111
  safeFrame,
5026
5112
  elements
5027
- }
5113
+ },
5114
+ ...iteration ? { iteration } : {}
5028
5115
  };
5029
5116
  return {
5030
5117
  png: pngBuffer,
@@ -5331,6 +5418,12 @@ var renderOutputSchema = z3.object({
5331
5418
  artifactHash: z3.string(),
5332
5419
  specHash: z3.string(),
5333
5420
  layoutMode: z3.string(),
5421
+ iteration: z3.object({
5422
+ current: z3.number().int().positive(),
5423
+ max: z3.number().int().positive(),
5424
+ isLast: z3.boolean(),
5425
+ notes: z3.string().optional()
5426
+ }).optional(),
5334
5427
  qa: z3.object({
5335
5428
  pass: z3.boolean(),
5336
5429
  issueCount: z3.number(),
@@ -5417,8 +5510,30 @@ function readCodeRange(code, start, end) {
5417
5510
  const lines = code.split(/\r?\n/u);
5418
5511
  return lines.slice(start - 1, end).join("\n");
5419
5512
  }
5513
+ function parseIterationMeta(options) {
5514
+ if (options.iteration == null) {
5515
+ if (options.maxIterations != null || options.iterationNotes || options.previousHash) {
5516
+ throw new Error(
5517
+ "--iteration is required when using --max-iterations, --iteration-notes, or --previous-hash."
5518
+ );
5519
+ }
5520
+ return void 0;
5521
+ }
5522
+ if (options.maxIterations != null && options.maxIterations < options.iteration) {
5523
+ throw new Error("--max-iterations must be greater than or equal to --iteration.");
5524
+ }
5525
+ return {
5526
+ iteration: options.iteration,
5527
+ ...options.maxIterations != null ? { maxIterations: options.maxIterations } : {},
5528
+ ...options.iterationNotes ? { notes: options.iterationNotes } : {},
5529
+ ...options.previousHash ? { previousHash: options.previousHash } : {}
5530
+ };
5531
+ }
5420
5532
  async function runRenderPipeline(spec, options) {
5421
- const renderResult = await renderDesign(spec, { generatorVersion: pkg.version });
5533
+ const renderResult = await renderDesign(spec, {
5534
+ generatorVersion: pkg.version,
5535
+ ...options.iteration ? { iteration: options.iteration } : {}
5536
+ });
5422
5537
  const written = await writeRenderArtifacts(renderResult, options.out);
5423
5538
  const specPath = options.specOut ? resolve4(options.specOut) : specPathFor(written.metadataPath);
5424
5539
  await mkdir2(dirname3(specPath), { recursive: true });
@@ -5435,6 +5550,14 @@ async function runRenderPipeline(spec, options) {
5435
5550
  artifactHash: written.metadata.artifactHash,
5436
5551
  specHash: written.metadata.specHash,
5437
5552
  layoutMode: spec.layout.mode,
5553
+ ...written.metadata.iteration ? {
5554
+ iteration: {
5555
+ current: written.metadata.iteration.iteration,
5556
+ max: written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration,
5557
+ isLast: (written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration) === written.metadata.iteration.iteration,
5558
+ ...written.metadata.iteration.notes ? { notes: written.metadata.iteration.notes } : {}
5559
+ }
5560
+ } : {},
5438
5561
  qa: {
5439
5562
  pass: qa.pass,
5440
5563
  issueCount: qa.issues.length,
@@ -5448,6 +5571,10 @@ cli.command("render", {
5448
5571
  spec: z3.string().describe('Path to DesignSpec JSON file (or "-" to read JSON from stdin)'),
5449
5572
  out: z3.string().describe("Output file path (.png) or output directory"),
5450
5573
  specOut: z3.string().optional().describe("Optional explicit output path for normalized spec JSON"),
5574
+ iteration: z3.number().int().positive().optional().describe("Optional iteration number for iterative workflows (1-indexed)"),
5575
+ iterationNotes: z3.string().optional().describe("Optional notes for the current iteration metadata"),
5576
+ maxIterations: z3.number().int().positive().optional().describe("Optional maximum planned iteration count"),
5577
+ previousHash: z3.string().optional().describe("Optional artifact hash from the previous iteration"),
5451
5578
  allowQaFail: z3.boolean().default(false).describe("Allow render success even if QA fails")
5452
5579
  }),
5453
5580
  output: renderOutputSchema,
@@ -5462,9 +5589,26 @@ cli.command("render", {
5462
5589
  ],
5463
5590
  async run(c) {
5464
5591
  const spec = parseDesignSpec(await readJson(c.options.spec));
5592
+ let iteration;
5593
+ try {
5594
+ iteration = parseIterationMeta({
5595
+ ...c.options.iteration != null ? { iteration: c.options.iteration } : {},
5596
+ ...c.options.maxIterations != null ? { maxIterations: c.options.maxIterations } : {},
5597
+ ...c.options.iterationNotes ? { iterationNotes: c.options.iterationNotes } : {},
5598
+ ...c.options.previousHash ? { previousHash: c.options.previousHash } : {}
5599
+ });
5600
+ } catch (error) {
5601
+ const message = error instanceof Error ? error.message : String(error);
5602
+ return c.error({
5603
+ code: "INVALID_ITERATION_OPTIONS",
5604
+ message,
5605
+ retryable: false
5606
+ });
5607
+ }
5465
5608
  const runReport = await runRenderPipeline(spec, {
5466
5609
  out: c.options.out,
5467
- ...c.options.specOut ? { specOut: c.options.specOut } : {}
5610
+ ...c.options.specOut ? { specOut: c.options.specOut } : {},
5611
+ ...iteration ? { iteration } : {}
5468
5612
  });
5469
5613
  if (!runReport.qa.pass && !c.options.allowQaFail) {
5470
5614
  return c.error({
package/dist/qa.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { R as RenderMetadata, D as DesignSpec } from './spec.schema-B_Z-KNqt.js';
1
+ import { R as RenderMetadata, D as DesignSpec } from './spec.schema-B6sXTTou.js';
2
2
  import 'zod';
3
3
  import '@napi-rs/canvas';
4
4
 
package/dist/qa.js CHANGED
@@ -563,6 +563,21 @@ var drawLineSchema = z2.object({
563
563
  opacity: z2.number().min(0).max(1).default(1),
564
564
  shadow: drawShadowSchema.optional()
565
565
  }).strict();
566
+ var drawArcSchema = z2.object({
567
+ type: z2.literal("arc"),
568
+ center: z2.object({
569
+ x: z2.number(),
570
+ y: z2.number()
571
+ }).strict(),
572
+ radius: z2.number().positive(),
573
+ startAngle: z2.number(),
574
+ endAngle: z2.number(),
575
+ color: colorHexSchema2.default("#FFFFFF"),
576
+ width: z2.number().min(0.5).max(32).default(2),
577
+ dash: z2.array(z2.number()).max(6).optional(),
578
+ opacity: z2.number().min(0).max(1).default(1),
579
+ shadow: drawShadowSchema.optional()
580
+ }).strict();
566
581
  var drawPointSchema = z2.object({
567
582
  x: z2.number(),
568
583
  y: z2.number()
@@ -647,6 +662,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
647
662
  drawCircleSchema,
648
663
  drawTextSchema,
649
664
  drawLineSchema,
665
+ drawArcSchema,
650
666
  drawBezierSchema,
651
667
  drawPathSchema,
652
668
  drawBadgeSchema,
@@ -782,6 +798,8 @@ var connectionElementSchema = z2.object({
782
798
  label: z2.string().min(1).max(200).optional(),
783
799
  labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
784
800
  color: colorHexSchema2.optional(),
801
+ fromColor: colorHexSchema2.optional(),
802
+ toColor: colorHexSchema2.optional(),
785
803
  width: z2.number().min(0.5).max(10).optional(),
786
804
  strokeWidth: z2.number().min(0.5).max(10).default(2),
787
805
  arrowSize: z2.number().min(4).max(32).optional(),