@pixerate/editor 0.260.0 → 0.262.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 (35) hide show
  1. package/dist/agent/index.cjs +19 -3
  2. package/dist/agent/index.js +21 -5
  3. package/dist/bridge/cli.cjs +4 -3
  4. package/dist/bridge/cli.js +3 -2
  5. package/dist/bridge/index.cjs +4 -3
  6. package/dist/bridge/index.js +3 -2
  7. package/dist/{chunk-KBNCEZZB.cjs → chunk-5NRD76T7.cjs} +9 -7
  8. package/dist/{chunk-SPPDMY7T.js → chunk-6LWEL5DO.js} +4 -2
  9. package/dist/{chunk-RTPBM46F.cjs → chunk-7TD63DZP.cjs} +1 -294
  10. package/dist/{chunk-ABHOYBZD.js → chunk-K4BGRCSH.js} +1 -294
  11. package/dist/chunk-SYEOA5MS.js +301 -0
  12. package/dist/{chunk-5DLCOQOQ.cjs → chunk-YBC3RKT2.cjs} +177 -16
  13. package/dist/chunk-ZEKI6EPI.cjs +301 -0
  14. package/dist/{chunk-UCYW4M66.js → chunk-ZQI3WUPP.js} +163 -2
  15. package/dist/paint-editor/contract.d.cts +11 -0
  16. package/dist/paint-editor/contract.d.ts +11 -0
  17. package/dist/paint-editor/controller.d.cts +17 -0
  18. package/dist/paint-editor/controller.d.ts +17 -0
  19. package/dist/paint-editor/index.cjs +245 -84
  20. package/dist/paint-editor/index.d.cts +2 -1
  21. package/dist/paint-editor/index.d.ts +2 -1
  22. package/dist/paint-editor/index.js +163 -2
  23. package/dist/paint-editor/path-samples.d.cts +19 -0
  24. package/dist/paint-editor/path-samples.d.ts +19 -0
  25. package/dist/paint-editor/ui.d.cts +6 -0
  26. package/dist/paint-editor/ui.d.ts +6 -0
  27. package/dist/tools/conformance.d.cts +138 -0
  28. package/dist/tools/conformance.d.ts +138 -0
  29. package/dist/tools/index.cjs +19 -3
  30. package/dist/tools/index.d.cts +1 -0
  31. package/dist/tools/index.d.ts +1 -0
  32. package/dist/tools/index.js +21 -5
  33. package/package.json +1 -1
  34. package/skills/pixerate-paint-editor/SKILL.md +2 -0
  35. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +5 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSkillFrontmatter
3
- } from "./chunk-ABHOYBZD.js";
3
+ } from "./chunk-K4BGRCSH.js";
4
4
  import {
5
5
  readingTime
6
6
  } from "./chunk-EHTX34RE.js";
@@ -596,6 +596,160 @@ function createOriginMiddleware(options) {
596
596
  };
597
597
  }
598
598
 
599
+ // src/tools/conformance.ts
600
+ var ConformanceIds = class {
601
+ toPlaceholder = /* @__PURE__ */ new Map();
602
+ fromPlaceholder = /* @__PURE__ */ new Map();
603
+ /** The placeholder for `id`, assigning the next one when it is new. */
604
+ add(id) {
605
+ let placeholder = this.toPlaceholder.get(id);
606
+ if (!placeholder) {
607
+ placeholder = `@${this.toPlaceholder.size + 1}`;
608
+ this.toPlaceholder.set(id, placeholder);
609
+ this.fromPlaceholder.set(placeholder, id);
610
+ }
611
+ return placeholder;
612
+ }
613
+ placeholder(id) {
614
+ return this.toPlaceholder.get(id);
615
+ }
616
+ /** `text` with every known id in it replaced by its placeholder (longest ids first). */
617
+ replaceWithin(text) {
618
+ const exact = this.toPlaceholder.get(text);
619
+ if (exact) return exact;
620
+ let out = text;
621
+ for (const id of [...this.toPlaceholder.keys()].sort((a, b) => b.length - a.length)) {
622
+ if (out.includes(id)) out = out.split(id).join(this.toPlaceholder.get(id));
623
+ }
624
+ return out;
625
+ }
626
+ /** The real id a placeholder stands for. */
627
+ resolve(placeholder) {
628
+ return this.fromPlaceholder.get(placeholder);
629
+ }
630
+ };
631
+ var PLACEHOLDER = /^@[1-9][0-9]*$/;
632
+ var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
633
+ var BASE64 = /^[A-Za-z0-9+/=_-]+$/;
634
+ var CONFORMANCE_BINARY_MIN_LENGTH = 200;
635
+ function isBinary(value) {
636
+ return value.startsWith("data:") || value.length >= CONFORMANCE_BINARY_MIN_LENGTH && BASE64.test(value);
637
+ }
638
+ function sortedKeys(value) {
639
+ return Object.keys(value).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
640
+ }
641
+ function collectIds(value, ids, isId) {
642
+ if (typeof value === "string") {
643
+ if (isId(value)) ids.add(value);
644
+ return;
645
+ }
646
+ if (Array.isArray(value)) {
647
+ for (const item of value) collectIds(item, ids, isId);
648
+ return;
649
+ }
650
+ if (value && typeof value === "object") {
651
+ for (const k of sortedKeys(value)) {
652
+ if (isId(k)) ids.add(k);
653
+ collectIds(value[k], ids, isId);
654
+ }
655
+ }
656
+ }
657
+ function replaceIds(value, ids) {
658
+ if (typeof value === "string") return isBinary(value) ? "<binary>" : TIMESTAMP.test(value) ? "<time>" : ids.replaceWithin(value);
659
+ if (Array.isArray(value)) return value.map((item) => replaceIds(item, ids));
660
+ if (value && typeof value === "object") {
661
+ const out = {};
662
+ for (const k of sortedKeys(value)) out[ids.replaceWithin(k)] = replaceIds(value[k], ids);
663
+ return out;
664
+ }
665
+ return value;
666
+ }
667
+ function normalizeConformanceValue(value, ids, isGeneratedId = () => false) {
668
+ const json = value === void 0 ? null : JSON.parse(JSON.stringify(value));
669
+ collectIds(json, ids, (v) => !PLACEHOLDER.test(v) && isGeneratedId(v));
670
+ return replaceIds(json, ids);
671
+ }
672
+ function omitConformancePaths(value, paths) {
673
+ let out = value === void 0 ? null : JSON.parse(JSON.stringify(value));
674
+ for (const path of paths) out = omitPath(out, path.split("/").slice(1));
675
+ return out;
676
+ }
677
+ function omitPath(value, parts) {
678
+ if (!value || typeof value !== "object" || parts.length === 0) return value;
679
+ const [head, ...rest] = parts;
680
+ if (Array.isArray(value)) {
681
+ if (head === "*") return value.map((item) => omitPath(item, rest));
682
+ const i = Number(head);
683
+ if (!Number.isInteger(i) || i < 0 || i >= value.length) return value;
684
+ if (rest.length === 0) return value.filter((_, j) => j !== i);
685
+ return value.map((item, j) => j === i ? omitPath(item, rest) : item);
686
+ }
687
+ const object = value;
688
+ const keys = head === "*" ? Object.keys(object) : head in object ? [head] : [];
689
+ for (const key of keys) {
690
+ if (rest.length === 0) delete object[key];
691
+ else object[key] = omitPath(object[key], rest);
692
+ }
693
+ return object;
694
+ }
695
+ function resolveConformanceArguments(value, ids) {
696
+ if (typeof value === "string" && PLACEHOLDER.test(value)) {
697
+ const id = ids.resolve(value);
698
+ if (id === void 0) throw new Error(`Placeholder ${value} is used before any result returned that id.`);
699
+ return id;
700
+ }
701
+ if (Array.isArray(value)) return value.map((item) => resolveConformanceArguments(item, ids));
702
+ if (value && typeof value === "object") {
703
+ const out = {};
704
+ for (const [k, v] of Object.entries(value)) out[k] = resolveConformanceArguments(v, ids);
705
+ return out;
706
+ }
707
+ return value;
708
+ }
709
+ function conformanceResult(result) {
710
+ if (result.isError) {
711
+ const error = result.structuredContent?.error ?? {};
712
+ const value = { code: error.code ?? "tool_failed", message: error.message ?? resultText(result) ?? "", ...error.details !== void 0 ? { details: error.details } : {} };
713
+ return { value: { error: value }, isError: true };
714
+ }
715
+ if (result.structuredContent !== void 0) return { value: result.structuredContent, isError: false };
716
+ const text = resultText(result);
717
+ if (text === void 0) return { value: null, isError: false };
718
+ try {
719
+ return { value: JSON.parse(text), isError: false };
720
+ } catch {
721
+ return { value: text, isError: false };
722
+ }
723
+ }
724
+ function resultText(result) {
725
+ for (const content of result.content) {
726
+ if (content.type === "text" && "text" in content && typeof content.text === "string") return content.text;
727
+ }
728
+ return void 0;
729
+ }
730
+ async function runConformanceScenario(scenario, host) {
731
+ const ids = new ConformanceIds();
732
+ const isId = host.isGeneratedId?.bind(host);
733
+ const outputs = [];
734
+ try {
735
+ for (const step of scenario.steps) {
736
+ const args = resolveConformanceArguments(step.arguments ?? {}, ids);
737
+ const { value, isError } = conformanceResult(await host.callTool(step.tool, args));
738
+ const result = normalizeConformanceValue(value, ids, isId);
739
+ const output = { result: step.ignore ? omitConformancePaths(result, step.ignore) : result, isError };
740
+ if (step.document) output.document = normalizeConformanceValue(host.document(), ids, isId);
741
+ if (step.pixels) {
742
+ const image = host.pixels({ paper: typeof step.pixels === "object" ? step.pixels.paper : void 0 });
743
+ output.pixels = { width: image.width, height: image.height, data: new Uint8Array(image.data.buffer, image.data.byteOffset, image.data.byteLength) };
744
+ }
745
+ outputs.push(output);
746
+ }
747
+ } finally {
748
+ host.dispose?.();
749
+ }
750
+ return outputs;
751
+ }
752
+
599
753
  export {
600
754
  listSkillEvals,
601
755
  imageEvalFixturePixels,
@@ -611,5 +765,12 @@ export {
611
765
  runStyleChecks,
612
766
  renderStyle,
613
767
  createStyleToolProvider,
614
- createOriginMiddleware
768
+ createOriginMiddleware,
769
+ ConformanceIds,
770
+ CONFORMANCE_BINARY_MIN_LENGTH,
771
+ normalizeConformanceValue,
772
+ omitConformancePaths,
773
+ resolveConformanceArguments,
774
+ conformanceResult,
775
+ runConformanceScenario
615
776
  };
@@ -9,7 +9,9 @@
9
9
  * package version, so a release does not change the files.
10
10
  */
11
11
  import type { EffectProvider } from '../effects/types.cjs';
12
+ import type { ConformanceHost } from '../tools/conformance.cjs';
12
13
  import type { JsonSchema } from '../tools/schema.cjs';
14
+ import { type PaintEditorOptions } from './controller.cjs';
13
15
  /** Bumped when a contract file changes shape (not when its data changes). */
14
16
  export declare const PAINT_CONTRACT_FORMAT_VERSION = 1;
15
17
  /**
@@ -27,3 +29,12 @@ export declare const PAINT_DOCUMENT_JSON_SCHEMA: JsonSchema;
27
29
  export declare function paintContracts(options: {
28
30
  effects: EffectProvider;
29
31
  }): Record<string, unknown>;
32
+ /**
33
+ * A fresh paint editor for replaying a conformance scenario (`domains/paint/fixtures`): every paint
34
+ * tool provider in one registry that validates arguments, the saved document (the
35
+ * `pixerate.editor-document` envelope) and the composited pixels. `setup` is the scenario's
36
+ * editor options, such as `{ width, height }`; `effects` is the effect provider the effect tools use.
37
+ */
38
+ export declare function createPaintConformanceHost(setup: PaintEditorOptions, options: {
39
+ effects: EffectProvider;
40
+ }): ConformanceHost;
@@ -9,7 +9,9 @@
9
9
  * package version, so a release does not change the files.
10
10
  */
11
11
  import type { EffectProvider } from '../effects/types.js';
12
+ import type { ConformanceHost } from '../tools/conformance.js';
12
13
  import type { JsonSchema } from '../tools/schema.js';
14
+ import { type PaintEditorOptions } from './controller.js';
13
15
  /** Bumped when a contract file changes shape (not when its data changes). */
14
16
  export declare const PAINT_CONTRACT_FORMAT_VERSION = 1;
15
17
  /**
@@ -27,3 +29,12 @@ export declare const PAINT_DOCUMENT_JSON_SCHEMA: JsonSchema;
27
29
  export declare function paintContracts(options: {
28
30
  effects: EffectProvider;
29
31
  }): Record<string, unknown>;
32
+ /**
33
+ * A fresh paint editor for replaying a conformance scenario (`domains/paint/fixtures`): every paint
34
+ * tool provider in one registry that validates arguments, the saved document (the
35
+ * `pixerate.editor-document` envelope) and the composited pixels. `setup` is the scenario's
36
+ * editor options, such as `{ width, height }`; `effects` is the effect provider the effect tools use.
37
+ */
38
+ export declare function createPaintConformanceHost(setup: PaintEditorOptions, options: {
39
+ effects: EffectProvider;
40
+ }): ConformanceHost;
@@ -26,6 +26,7 @@ import type { ColorScheme } from '../color/dual.cjs';
26
26
  import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.cjs';
27
27
  import { type ImageEncoder, type ImageFormat } from './encode.cjs';
28
28
  import type { PenPoint } from './engine.cjs';
29
+ import { type PathSampleOptions } from './path-samples.cjs';
29
30
  import type { Hand } from './input.cjs';
30
31
  import { type PaperSettings } from './paper.cjs';
31
32
  import { type ViewLayer } from './render.cjs';
@@ -945,6 +946,22 @@ export declare class PaintEditorController {
945
946
  paintStroke(init: PaintStrokeInit & {
946
947
  points: readonly PenPoint[];
947
948
  }): PaintStrokeResult;
949
+ /**
950
+ * Brushes vector path data (SVG `d`, document pixels) with the active brush, or `brushId` /
951
+ * `brush`, as if drawn by hand: one recorded stroke per subpath, sampled every `spacing` px
952
+ * with an optional `pressure` profile along each. All of it is one undo step. A given `seed`
953
+ * seeds the first subpath; the next ones take the following seeds.
954
+ */
955
+ strokePath(init: Omit<PaintStrokeInit, 'start' | 'copies' | 'load'> & PathSampleOptions & {
956
+ path: string;
957
+ }): {
958
+ layerId: string;
959
+ bounds: RasterRect | null;
960
+ dabs: number;
961
+ strokeIds: string[];
962
+ mixerLoad?: MixerLoad;
963
+ wet?: boolean;
964
+ };
948
965
  /** Checks stroke samples (finite x and y, pressure 0..1) and returns them; throws a sentence naming the bad point. */
949
966
  checkPoints(points: readonly PenPoint[]): PenPoint[];
950
967
  /** Every recorded stroke, oldest first. */
@@ -26,6 +26,7 @@ import type { ColorScheme } from '../color/dual.js';
26
26
  import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.js';
27
27
  import { type ImageEncoder, type ImageFormat } from './encode.js';
28
28
  import type { PenPoint } from './engine.js';
29
+ import { type PathSampleOptions } from './path-samples.js';
29
30
  import type { Hand } from './input.js';
30
31
  import { type PaperSettings } from './paper.js';
31
32
  import { type ViewLayer } from './render.js';
@@ -945,6 +946,22 @@ export declare class PaintEditorController {
945
946
  paintStroke(init: PaintStrokeInit & {
946
947
  points: readonly PenPoint[];
947
948
  }): PaintStrokeResult;
949
+ /**
950
+ * Brushes vector path data (SVG `d`, document pixels) with the active brush, or `brushId` /
951
+ * `brush`, as if drawn by hand: one recorded stroke per subpath, sampled every `spacing` px
952
+ * with an optional `pressure` profile along each. All of it is one undo step. A given `seed`
953
+ * seeds the first subpath; the next ones take the following seeds.
954
+ */
955
+ strokePath(init: Omit<PaintStrokeInit, 'start' | 'copies' | 'load'> & PathSampleOptions & {
956
+ path: string;
957
+ }): {
958
+ layerId: string;
959
+ bounds: RasterRect | null;
960
+ dabs: number;
961
+ strokeIds: string[];
962
+ mixerLoad?: MixerLoad;
963
+ wet?: boolean;
964
+ };
948
965
  /** Checks stroke samples (finite x and y, pressure 0..1) and returns them; throws a sentence naming the bad point. */
949
966
  checkPoints(points: readonly PenPoint[]): PenPoint[];
950
967
  /** Every recorded stroke, oldest first. */