@dxos/plugin-illustrator 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.
Files changed (172) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +55 -0
  3. package/dist/lib/IllustratorPlugin.mjs +18 -0
  4. package/dist/lib/IllustratorPlugin.mjs.map +1 -0
  5. package/dist/lib/IllustratorPlugin.node.mjs +18 -0
  6. package/dist/lib/IllustratorPlugin.node.mjs.map +1 -0
  7. package/dist/lib/IllustratorPlugin.workerd.mjs +10 -0
  8. package/dist/lib/IllustratorPlugin.workerd.mjs.map +1 -0
  9. package/dist/lib/capabilities.mjs +11 -0
  10. package/dist/lib/capabilities.mjs.map +1 -0
  11. package/dist/lib/chunk-IllustratorCapabilities.mjs +16 -0
  12. package/dist/lib/chunk-IllustratorCapabilities.mjs.map +1 -0
  13. package/dist/lib/chunk-comment-config.mjs +16 -0
  14. package/dist/lib/chunk-comment-config.mjs.map +1 -0
  15. package/dist/lib/chunk-create-object.mjs +46 -0
  16. package/dist/lib/chunk-create-object.mjs.map +1 -0
  17. package/dist/lib/chunk-create.mjs +20 -0
  18. package/dist/lib/chunk-create.mjs.map +1 -0
  19. package/dist/lib/chunk-edit.mjs +20 -0
  20. package/dist/lib/chunk-edit.mjs.map +1 -0
  21. package/dist/lib/chunk-load-sketch.mjs +64 -0
  22. package/dist/lib/chunk-load-sketch.mjs.map +1 -0
  23. package/dist/lib/chunk-meta.mjs +40 -0
  24. package/dist/lib/chunk-meta.mjs.map +1 -0
  25. package/dist/lib/chunk-model.mjs +202 -0
  26. package/dist/lib/chunk-model.mjs.map +1 -0
  27. package/dist/lib/chunk-operation-handler.mjs +11 -0
  28. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  29. package/dist/lib/chunk-react-surface.mjs +31 -0
  30. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  31. package/dist/lib/chunk-read.mjs +13 -0
  32. package/dist/lib/chunk-read.mjs.map +1 -0
  33. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  34. package/dist/lib/chunk-skill-definition.mjs +10 -0
  35. package/dist/lib/chunk-skill-definition.mjs.map +1 -0
  36. package/dist/lib/chunk-skills.mjs +65 -0
  37. package/dist/lib/chunk-skills.mjs.map +1 -0
  38. package/dist/lib/chunk-types.mjs +108 -0
  39. package/dist/lib/chunk-types.mjs.map +1 -0
  40. package/dist/lib/components.mjs +45 -0
  41. package/dist/lib/components.mjs.map +1 -0
  42. package/dist/lib/containers.mjs +58 -0
  43. package/dist/lib/containers.mjs.map +1 -0
  44. package/dist/lib/index.mjs +9 -0
  45. package/dist/lib/meta.mjs +2 -0
  46. package/dist/lib/model.mjs +2 -0
  47. package/dist/lib/operations.mjs +7 -0
  48. package/dist/lib/operations.mjs.map +1 -0
  49. package/dist/lib/plugin.mjs +9 -0
  50. package/dist/lib/plugin.mjs.map +1 -0
  51. package/dist/lib/skills.mjs +2 -0
  52. package/dist/lib/translations.mjs +27 -0
  53. package/dist/lib/translations.mjs.map +1 -0
  54. package/dist/lib/types.mjs +3 -0
  55. package/dist/lib/util.mjs +2 -0
  56. package/dist/types/dx.config.d.ts +28 -0
  57. package/dist/types/dx.config.d.ts.map +1 -0
  58. package/dist/types/src/IllustratorPlugin.d.ts +4 -0
  59. package/dist/types/src/IllustratorPlugin.d.ts.map +1 -0
  60. package/dist/types/src/IllustratorPlugin.node.d.ts +10 -0
  61. package/dist/types/src/IllustratorPlugin.node.d.ts.map +1 -0
  62. package/dist/types/src/IllustratorPlugin.workerd.d.ts +4 -0
  63. package/dist/types/src/IllustratorPlugin.workerd.d.ts.map +1 -0
  64. package/dist/types/src/capabilities/comment-config.d.ts +5 -0
  65. package/dist/types/src/capabilities/comment-config.d.ts.map +1 -0
  66. package/dist/types/src/capabilities/create-object.d.ts +5 -0
  67. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  68. package/dist/types/src/capabilities/index.d.ts +8 -0
  69. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  71. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  72. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  73. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  74. package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
  75. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
  76. package/dist/types/src/components/CreateSketchPanel.d.ts +15 -0
  77. package/dist/types/src/components/CreateSketchPanel.d.ts.map +1 -0
  78. package/dist/types/src/components/index.d.ts +2 -0
  79. package/dist/types/src/components/index.d.ts.map +1 -0
  80. package/dist/types/src/containers/SketchArticle/SketchArticle.d.ts +11 -0
  81. package/dist/types/src/containers/SketchArticle/SketchArticle.d.ts.map +1 -0
  82. package/dist/types/src/containers/SketchArticle/index.d.ts +2 -0
  83. package/dist/types/src/containers/SketchArticle/index.d.ts.map +1 -0
  84. package/dist/types/src/containers/SketchCard/SketchCard.d.ts +10 -0
  85. package/dist/types/src/containers/SketchCard/SketchCard.d.ts.map +1 -0
  86. package/dist/types/src/containers/SketchCard/index.d.ts +2 -0
  87. package/dist/types/src/containers/SketchCard/index.d.ts.map +1 -0
  88. package/dist/types/src/containers/index.d.ts +3 -0
  89. package/dist/types/src/containers/index.d.ts.map +1 -0
  90. package/dist/types/src/index.d.ts +7 -0
  91. package/dist/types/src/index.d.ts.map +1 -0
  92. package/dist/types/src/meta.d.ts +33 -0
  93. package/dist/types/src/meta.d.ts.map +1 -0
  94. package/dist/types/src/model/builder.d.ts +32 -0
  95. package/dist/types/src/model/builder.d.ts.map +1 -0
  96. package/dist/types/src/model/dialect.d.ts +19 -0
  97. package/dist/types/src/model/dialect.d.ts.map +1 -0
  98. package/dist/types/src/model/index.d.ts +4 -0
  99. package/dist/types/src/model/index.d.ts.map +1 -0
  100. package/dist/types/src/model/scene.d.ts +843 -0
  101. package/dist/types/src/model/scene.d.ts.map +1 -0
  102. package/dist/types/src/operations/create.d.ts +5 -0
  103. package/dist/types/src/operations/create.d.ts.map +1 -0
  104. package/dist/types/src/operations/edit.d.ts +5 -0
  105. package/dist/types/src/operations/edit.d.ts.map +1 -0
  106. package/dist/types/src/operations/index.d.ts +3 -0
  107. package/dist/types/src/operations/index.d.ts.map +1 -0
  108. package/dist/types/src/operations/read.d.ts +5 -0
  109. package/dist/types/src/operations/read.d.ts.map +1 -0
  110. package/dist/types/src/plugin.d.ts +4 -0
  111. package/dist/types/src/plugin.d.ts.map +1 -0
  112. package/dist/types/src/skills/index.d.ts +2 -0
  113. package/dist/types/src/skills/index.d.ts.map +1 -0
  114. package/dist/types/src/skills/sketch-skill.d.ts +4 -0
  115. package/dist/types/src/skills/sketch-skill.d.ts.map +1 -0
  116. package/dist/types/src/translations.d.ts +34 -0
  117. package/dist/types/src/translations.d.ts.map +1 -0
  118. package/dist/types/src/types/IllustratorCapabilities.d.ts +10 -0
  119. package/dist/types/src/types/IllustratorCapabilities.d.ts.map +1 -0
  120. package/dist/types/src/types/Sketch.d.ts +50 -0
  121. package/dist/types/src/types/Sketch.d.ts.map +1 -0
  122. package/dist/types/src/types/SketchOperation.d.ts +427 -0
  123. package/dist/types/src/types/SketchOperation.d.ts.map +1 -0
  124. package/dist/types/src/types/index.d.ts +5 -0
  125. package/dist/types/src/types/index.d.ts.map +1 -0
  126. package/dist/types/src/types/types.d.ts +45 -0
  127. package/dist/types/src/types/types.d.ts.map +1 -0
  128. package/dist/types/src/util/index.d.ts +2 -0
  129. package/dist/types/src/util/index.d.ts.map +1 -0
  130. package/dist/types/src/util/load-sketch.d.ts +37 -0
  131. package/dist/types/src/util/load-sketch.d.ts.map +1 -0
  132. package/dist/types/tsconfig.tsbuildinfo +1 -0
  133. package/dx.config.ts +35 -0
  134. package/package.json +144 -0
  135. package/src/IllustratorPlugin.node.tsx +27 -0
  136. package/src/IllustratorPlugin.tsx +30 -0
  137. package/src/IllustratorPlugin.workerd.tsx +16 -0
  138. package/src/capabilities/comment-config.ts +20 -0
  139. package/src/capabilities/create-object.ts +66 -0
  140. package/src/capabilities/index.ts +15 -0
  141. package/src/capabilities/operation-handler.ts +16 -0
  142. package/src/capabilities/react-surface.tsx +37 -0
  143. package/src/capabilities/skill-definition.ts +14 -0
  144. package/src/components/CreateSketchPanel.tsx +68 -0
  145. package/src/components/index.ts +5 -0
  146. package/src/containers/SketchArticle/SketchArticle.tsx +48 -0
  147. package/src/containers/SketchArticle/index.ts +5 -0
  148. package/src/containers/SketchCard/SketchCard.tsx +41 -0
  149. package/src/containers/SketchCard/index.ts +5 -0
  150. package/src/containers/index.ts +6 -0
  151. package/src/index.ts +10 -0
  152. package/src/meta.ts +9 -0
  153. package/src/model/builder.ts +41 -0
  154. package/src/model/dialect.ts +53 -0
  155. package/src/model/index.ts +7 -0
  156. package/src/model/scene.ts +208 -0
  157. package/src/operations/create.ts +27 -0
  158. package/src/operations/edit.ts +23 -0
  159. package/src/operations/index.ts +11 -0
  160. package/src/operations/read.ts +21 -0
  161. package/src/plugin.ts +11 -0
  162. package/src/skills/index.ts +5 -0
  163. package/src/skills/sketch-skill.ts +70 -0
  164. package/src/translations.ts +33 -0
  165. package/src/types/IllustratorCapabilities.ts +19 -0
  166. package/src/types/Sketch.ts +69 -0
  167. package/src/types/SketchOperation.ts +76 -0
  168. package/src/types/index.ts +9 -0
  169. package/src/types/types.ts +53 -0
  170. package/src/util/index.ts +5 -0
  171. package/src/util/load-sketch.ts +74 -0
  172. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // Renderer-neutral builder contract: each canvas variant (tldraw, excalidraw, …) implements
7
+ // `SketchBuilder` to compile the scene DSL onto its concrete records and derive the scene back
8
+ // from them. Implementations are contributed via `IllustratorCapabilities.VariantProvider`.
9
+ //
10
+
11
+ import { type Obj } from '@dxos/echo';
12
+
13
+ import type * as Scene from './scene';
14
+
15
+ /** A world object as derived from the canvas: placement is always resolved. */
16
+ export type ReadWorldObject = Scene.WorldObject & { origin: Scene.Point; scale: number };
17
+
18
+ export type ReadResult = {
19
+ scene: { objects: ReadWorldObject[] };
20
+ /** Shapes on the canvas not managed by the DSL (hand-drawn by users). */
21
+ unmanaged: number;
22
+ };
23
+
24
+ export type ApplyResult = {
25
+ /** Object ids created or modified. */
26
+ upserted: string[];
27
+ /** Shape records removed. */
28
+ removed: number;
29
+ };
30
+
31
+ /**
32
+ * Maps the scene DSL onto a renderer-specific canvas object.
33
+ * Geometry is derived from the live canvas records — not a stored copy — so the model stays
34
+ * coherent after users drag or resize shapes in the renderer's UI.
35
+ */
36
+ export type SketchBuilder = {
37
+ /** Derive the neutral scene from the canvas. */
38
+ read: (canvas: Obj.Any) => ReadResult;
39
+ /** Apply a batch of edit commands atomically (implementations wrap their own `Obj.update`). */
40
+ apply: (canvas: Obj.Any, commands: readonly Scene.Command[]) => ApplyResult;
41
+ };
@@ -0,0 +1,53 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // Extension point for higher-level diagram languages (mermaid, d2, sequence, uml-class, …).
7
+ // A dialect compiles its input into scene edit commands — owning any layout it needs — so its
8
+ // output lands as ordinary world objects and stays editable by id. Mirrors the architecture
9
+ // shared by Mermaid/D2/Kroki: dialect → intermediate positioned model → renderer.
10
+ //
11
+
12
+ import * as Effect from 'effect/Effect';
13
+ import * as Schema from 'effect/Schema';
14
+
15
+ import { invariant } from '@dxos/invariant';
16
+
17
+ import * as Scene from './scene';
18
+
19
+ export type Dialect<Input = any> = {
20
+ id: string;
21
+ description: string;
22
+ input: Schema.Schema<Input, any>;
23
+ compile: (input: Input) => Effect.Effect<readonly Scene.Command[]>;
24
+ };
25
+
26
+ /** The core dialect: input is already scene edit commands (identity compile). */
27
+ export const sceneDialect: Dialect<readonly Scene.Command[]> = {
28
+ id: 'scene',
29
+ description: 'Positioned world objects and elements; the agent does the layout.',
30
+ input: Schema.Array(Scene.Command),
31
+ compile: Effect.succeed,
32
+ };
33
+
34
+ export class DialectRegistry {
35
+ readonly #dialects = new Map<string, Dialect>();
36
+
37
+ constructor(dialects: Dialect[] = [sceneDialect]) {
38
+ dialects.forEach((dialect) => this.register(dialect));
39
+ }
40
+
41
+ register(dialect: Dialect): void {
42
+ invariant(!this.#dialects.has(dialect.id), `duplicate dialect: ${dialect.id}`);
43
+ this.#dialects.set(dialect.id, dialect);
44
+ }
45
+
46
+ get(id: string): Dialect | undefined {
47
+ return this.#dialects.get(id);
48
+ }
49
+
50
+ list(): Dialect[] {
51
+ return [...this.#dialects.values()];
52
+ }
53
+ }
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './builder';
6
+ export * from './dialect';
7
+ export * as Scene from './scene';
@@ -0,0 +1,208 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // Backend-neutral scene DSL: a diagram is a set of named world objects ("face", "hat"),
7
+ // each composed of graphical elements (rect, ellipse, line, curve, text, arrow) authored
8
+ // in object-local units. A backend compiler (tldraw today) maps the scene onto the
9
+ // concrete canvas; see `render.ts` / `read.ts`.
10
+ //
11
+
12
+ import * as Schema from 'effect/Schema';
13
+
14
+ export const Color = Schema.Literal(
15
+ 'black',
16
+ 'grey',
17
+ 'light-violet',
18
+ 'violet',
19
+ 'blue',
20
+ 'light-blue',
21
+ 'yellow',
22
+ 'orange',
23
+ 'green',
24
+ 'light-green',
25
+ 'light-red',
26
+ 'red',
27
+ 'white',
28
+ );
29
+ export type Color = Schema.Schema.Type<typeof Color>;
30
+
31
+ export const Fill = Schema.Literal('none', 'solid', 'pattern');
32
+ export type Fill = Schema.Schema.Type<typeof Fill>;
33
+
34
+ export const Stroke = Schema.Literal('sketchy', 'solid', 'dashed', 'dotted');
35
+ export type Stroke = Schema.Schema.Type<typeof Stroke>;
36
+
37
+ export const Weight = Schema.Literal('s', 'm', 'l', 'xl');
38
+ export type Weight = Schema.Schema.Type<typeof Weight>;
39
+
40
+ export const Point = Schema.Struct({
41
+ x: Schema.Number,
42
+ y: Schema.Number,
43
+ });
44
+ export type Point = Schema.Schema.Type<typeof Point>;
45
+
46
+ /** Style props shared by all elements; every field is optional with sensible defaults. */
47
+ const styleFields = {
48
+ color: Schema.optional(Color).annotations({ description: 'Stroke/text color.' }),
49
+ fill: Schema.optional(Fill).annotations({ description: 'Interior fill of closed shapes.' }),
50
+ stroke: Schema.optional(Stroke).annotations({ description: 'Stroke rendering; sketchy is hand-drawn.' }),
51
+ weight: Schema.optional(Weight).annotations({ description: 'Stroke weight / text size.' }),
52
+ };
53
+
54
+ const id = Schema.String.annotations({
55
+ description: 'Stable element id, unique within the object (e.g. "left-eye"). Used to edit or delete the element.',
56
+ });
57
+
58
+ /** Closed shape drawn inside a local bounding box. */
59
+ export const Box = Schema.Struct({
60
+ kind: Schema.Literal('rect', 'ellipse', 'diamond', 'triangle'),
61
+ id,
62
+ x: Schema.Number.annotations({ description: 'Left edge (object-local units).' }),
63
+ y: Schema.Number.annotations({ description: 'Top edge (object-local units).' }),
64
+ w: Schema.Number,
65
+ h: Schema.Number,
66
+ rotation: Schema.optional(Schema.Number).annotations({ description: 'Clockwise rotation in degrees.' }),
67
+ text: Schema.optional(Schema.String).annotations({ description: 'Centered label.' }),
68
+ ...styleFields,
69
+ });
70
+ export type Box = Schema.Schema.Type<typeof Box>;
71
+
72
+ /** Circle sugar; reads back as an ellipse. */
73
+ export const Circle = Schema.Struct({
74
+ kind: Schema.Literal('circle'),
75
+ id,
76
+ cx: Schema.Number.annotations({ description: 'Center x (object-local units).' }),
77
+ cy: Schema.Number.annotations({ description: 'Center y (object-local units).' }),
78
+ r: Schema.Number,
79
+ text: Schema.optional(Schema.String),
80
+ ...styleFields,
81
+ });
82
+ export type Circle = Schema.Schema.Type<typeof Circle>;
83
+
84
+ /** Straight segment or open polyline through the given points. */
85
+ export const Polyline = Schema.Struct({
86
+ kind: Schema.Literal('line'),
87
+ id,
88
+ points: Schema.Array(Point).annotations({ description: 'Two or more points (object-local units).' }),
89
+ closed: Schema.optional(Schema.Boolean).annotations({ description: 'Close the path back to the first point.' }),
90
+ ...styleFields,
91
+ });
92
+ export type Polyline = Schema.Schema.Type<typeof Polyline>;
93
+
94
+ /** Smooth curve through the given points (spline). */
95
+ export const Curve = Schema.Struct({
96
+ kind: Schema.Literal('curve'),
97
+ id,
98
+ points: Schema.Array(Point).annotations({ description: 'Control points the curve passes through.' }),
99
+ ...styleFields,
100
+ });
101
+ export type Curve = Schema.Schema.Type<typeof Curve>;
102
+
103
+ /**
104
+ * Circular arc sugar; reads back as a curve. Angles are degrees, measured clockwise from
105
+ * the positive x-axis in screen coordinates (y grows downward): 0–180 is the lower half
106
+ * (a smile), 180–360 the upper half (a frown).
107
+ */
108
+ export const Arc = Schema.Struct({
109
+ kind: Schema.Literal('arc'),
110
+ id,
111
+ cx: Schema.Number,
112
+ cy: Schema.Number,
113
+ r: Schema.Number,
114
+ startAngle: Schema.Number,
115
+ endAngle: Schema.Number,
116
+ ...styleFields,
117
+ });
118
+ export type Arc = Schema.Schema.Type<typeof Arc>;
119
+
120
+ /** Free-standing text. */
121
+ export const Text = Schema.Struct({
122
+ kind: Schema.Literal('text'),
123
+ id,
124
+ x: Schema.Number,
125
+ y: Schema.Number,
126
+ w: Schema.optional(Schema.Number).annotations({ description: 'Wrap width; omit for auto-size.' }),
127
+ text: Schema.String,
128
+ ...styleFields,
129
+ });
130
+ export type Text = Schema.Schema.Type<typeof Text>;
131
+
132
+ /**
133
+ * Connector. Endpoints are element refs — `"<elementId>"` within the same object or
134
+ * `"<objectId>/<elementId>"` across objects — or explicit local points. Bound endpoints
135
+ * track their target when it moves.
136
+ */
137
+ export const Arrow = Schema.Struct({
138
+ kind: Schema.Literal('arrow'),
139
+ id,
140
+ from: Schema.optional(Schema.String).annotations({ description: 'Source element ref.' }),
141
+ to: Schema.optional(Schema.String).annotations({ description: 'Target element ref.' }),
142
+ start: Schema.optional(Point).annotations({ description: 'Explicit start (used when `from` is omitted).' }),
143
+ end: Schema.optional(Point).annotations({ description: 'Explicit end (used when `to` is omitted).' }),
144
+ text: Schema.optional(Schema.String).annotations({ description: 'Label at the arrow midpoint.' }),
145
+ ...styleFields,
146
+ });
147
+ export type Arrow = Schema.Schema.Type<typeof Arrow>;
148
+
149
+ export const Element = Schema.Union(Box, Circle, Polyline, Curve, Arc, Text, Arrow);
150
+ export type Element = Schema.Schema.Type<typeof Element>;
151
+
152
+ /**
153
+ * A semantic group of elements ("face", "hat") authored in its own local unit space.
154
+ * `origin` (canvas px) and `scale` (px per unit) place it on the canvas:
155
+ * `canvas = origin + local * scale`. On read, `origin` is derived as the top-left of the
156
+ * object's bounding box, so the model survives users dragging shapes around.
157
+ */
158
+ export const WorldObject = Schema.Struct({
159
+ id: Schema.String.annotations({
160
+ description: 'Stable object id naming the thing depicted (e.g. "face", "hat").',
161
+ }),
162
+ origin: Schema.optional(Point).annotations({
163
+ description: 'Canvas position (px). Omit on upsert to keep the current position.',
164
+ }),
165
+ scale: Schema.optional(Schema.Number).annotations({ description: 'Canvas px per local unit (default 1).' }),
166
+ elements: Schema.Array(Element),
167
+ });
168
+ export type WorldObject = Schema.Schema.Type<typeof WorldObject>;
169
+
170
+ export const Scene = Schema.Struct({
171
+ objects: Schema.Array(WorldObject),
172
+ });
173
+ export type Scene = Schema.Schema.Type<typeof Scene>;
174
+
175
+ //
176
+ // Edit commands.
177
+ //
178
+
179
+ export const UpsertObject = Schema.Struct({
180
+ op: Schema.Literal('upsert-object'),
181
+ object: WorldObject,
182
+ }).annotations({ description: 'Create an object, or replace an existing one wholesale (same id).' });
183
+
184
+ export const UpsertElements = Schema.Struct({
185
+ op: Schema.Literal('upsert-elements'),
186
+ objectId: Schema.String,
187
+ elements: Schema.Array(Element),
188
+ }).annotations({ description: 'Add elements to an existing object, replacing any with matching ids.' });
189
+
190
+ export const RemoveElements = Schema.Struct({
191
+ op: Schema.Literal('remove-elements'),
192
+ objectId: Schema.String,
193
+ elementIds: Schema.Array(Schema.String),
194
+ });
195
+
196
+ export const RemoveObject = Schema.Struct({
197
+ op: Schema.Literal('remove-object'),
198
+ objectId: Schema.String,
199
+ });
200
+
201
+ export const MoveObject = Schema.Struct({
202
+ op: Schema.Literal('move-object'),
203
+ objectId: Schema.String,
204
+ origin: Point.annotations({ description: 'New canvas position (px) for the object top-left.' }),
205
+ });
206
+
207
+ export const Command = Schema.Union(UpsertObject, UpsertElements, RemoveElements, RemoveObject, MoveObject);
208
+ export type Command = Schema.Schema.Type<typeof Command>;
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { Operation } from '@dxos/compute';
9
+
10
+ import { IllustratorCapabilities, Sketch, SketchOperation } from '../types';
11
+ import { UnknownSketchVariantError } from '../util/load-sketch';
12
+
13
+ const handler: Operation.WithHandler<typeof SketchOperation.Create> = SketchOperation.Create.pipe(
14
+ Operation.withHandler(
15
+ Effect.fn(function* ({ name, variant: variantId }) {
16
+ const variants = yield* Capability.getAll(IllustratorCapabilities.VariantProvider);
17
+ const variant = variantId ? variants.find((entry) => entry.id === variantId) : variants[0];
18
+ if (!variant) {
19
+ return yield* Effect.fail(new UnknownSketchVariantError(variantId ?? '(none registered)'));
20
+ }
21
+ const canvas = yield* variant.createCanvas();
22
+ return { object: Sketch.make({ name, canvas }) };
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Operation } from '@dxos/compute';
8
+
9
+ import { SketchOperation } from '../types';
10
+ import { resolveVariant } from '../util/load-sketch';
11
+
12
+ const handler: Operation.WithHandler<typeof SketchOperation.Edit> = SketchOperation.Edit.pipe(
13
+ Operation.withHandler(
14
+ Effect.fn(function* ({ sketch, commands }) {
15
+ const { canvas, variant } = yield* resolveVariant(sketch);
16
+ const { upserted, removed } = variant.builder.apply(canvas, commands);
17
+ const { scene, unmanaged } = variant.builder.read(canvas);
18
+ return { scene, unmanaged, upserted, removed };
19
+ }),
20
+ ),
21
+ );
22
+
23
+ export default handler;
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/compute';
6
+
7
+ export const IllustratorOperationHandlerSet = OperationHandlerSet.lazy(
8
+ () => import('./create'),
9
+ () => import('./edit'),
10
+ () => import('./read'),
11
+ );
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Operation } from '@dxos/compute';
8
+
9
+ import { SketchOperation } from '../types';
10
+ import { resolveVariant } from '../util/load-sketch';
11
+
12
+ const handler: Operation.WithHandler<typeof SketchOperation.Read> = SketchOperation.Read.pipe(
13
+ Operation.withHandler(
14
+ Effect.fn(function* ({ sketch }) {
15
+ const { canvas, variant } = yield* resolveVariant(sketch);
16
+ return variant.builder.read(canvas);
17
+ }),
18
+ ),
19
+ );
20
+
21
+ export default handler;
package/src/plugin.ts ADDED
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+
7
+ import { meta } from './meta';
8
+
9
+ export const IllustratorPlugin = Plugin.lazy(meta, () => import('#plugin'));
10
+
11
+ export { IllustratorOperationHandlerSet } from './operations';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export { default as SketchSkill } from './sketch-skill';
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Skill, Template } from '@dxos/compute';
6
+ import { trim } from '@dxos/util';
7
+
8
+ import { SketchOperation } from '#types';
9
+
10
+ const SKILL_KEY = 'org.dxos.skill.sketch';
11
+
12
+ const operations = [SketchOperation.Create, SketchOperation.Read, SketchOperation.Edit];
13
+
14
+ const make = () =>
15
+ Skill.make({
16
+ key: SKILL_KEY,
17
+ name: 'Sketch',
18
+ tools: Skill.toolDefinitions({ operations }),
19
+ instructions: Template.make({
20
+ source: trim`
21
+ {{! Sketch }}
22
+
23
+ You can draw and edit diagrams on a shared canvas using a scene DSL.
24
+
25
+ ## Model
26
+
27
+ A drawing is a set of WORLD OBJECTS — semantic things like "face", "hat", "server" —
28
+ each composed of graphical ELEMENTS (rect, ellipse, circle, diamond, triangle, line,
29
+ curve, arc, text, arrow). You do the layout; there is no auto-layout.
30
+
31
+ - Give objects and elements short, semantic ids ("face", "left-eye"). Ids are how you
32
+ edit or delete things later — choose them as carefully as variable names.
33
+ - Element coordinates are OBJECT-LOCAL units. Design each object in roughly a 100-unit
34
+ box. The object's "origin" (canvas px) and "scale" (px per unit) place it on the
35
+ canvas: canvas = origin + local * scale. Prefer scale 2-3 for a primary subject.
36
+ - Screen coordinates: x grows right, y grows DOWN. Arc angles are degrees clockwise
37
+ from the +x axis: startAngle 30, endAngle 150 bulges downward (a smile);
38
+ 210 to 330 bulges upward (a frown).
39
+ - Circle is sugar for ellipse (reads back as ellipse); arc is sugar for curve.
40
+ - Arrows can bind to elements: from/to is an element id in the same object, or
41
+ "objectId/elementId" across objects. Bound arrows track their targets.
42
+ - Styles: color (named palette), fill (none | solid | pattern),
43
+ stroke (sketchy | solid | dashed | dotted), weight (s | m | l | xl).
44
+
45
+ ## Workflow
46
+
47
+ 1. If no sketch exists in context, create one first.
48
+ 2. ALWAYS call read before editing an existing sketch — it returns the scene as you
49
+ (or the user) last left it, with origins derived from the live canvas, so it stays
50
+ correct even after the user drags shapes around. Note "unmanaged" counts shapes the
51
+ user drew by hand that are not part of the scene.
52
+ 3. Edit with commands, in one batch where possible:
53
+ - upsert-object: create a new object, or redraw one wholesale.
54
+ - upsert-elements: add or replace specific elements of an object — prefer this for
55
+ incremental changes ("make the smile bigger" replaces only "smile").
56
+ - remove-elements / remove-object: delete by id.
57
+ - move-object: reposition an object (canvas px) without resending its elements.
58
+ 4. Compose scenes from multiple objects: "add a hat" means a NEW "hat" object placed
59
+ relative to the existing object's origin and size (from read) — do not redraw the
60
+ existing object.
61
+ `,
62
+ }),
63
+ });
64
+
65
+ const skill: Skill.Definition = {
66
+ key: SKILL_KEY,
67
+ make,
68
+ };
69
+
70
+ export default skill;
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Type } from '@dxos/echo';
6
+ import { type Resource } from '@dxos/react-ui';
7
+
8
+ import { meta } from '#meta';
9
+ import { Sketch } from '#types';
10
+
11
+ export const translations = [
12
+ {
13
+ 'en-US': {
14
+ [Type.getTypename(Sketch.Sketch)]: {
15
+ 'typename.label': 'Sketch',
16
+ 'typename.label_zero': 'Sketches',
17
+ 'typename.label_one': 'Sketch',
18
+ 'typename.label_other': 'Sketches',
19
+ 'object-name.placeholder': 'New sketch',
20
+ 'add-object.label': 'Add sketch',
21
+ 'rename-object.label': 'Rename sketch',
22
+ 'delete-object.label': 'Delete sketch',
23
+ 'object-deleted.label': 'Sketch deleted',
24
+ },
25
+ [meta.profile.key]: {
26
+ 'plugin.name': 'Illustrator',
27
+ 'create-panel.variant.label': 'Select renderer',
28
+ 'create-panel.variant.placeholder': 'Search renderers...',
29
+ 'unsupported-variant.label': 'Unsupported sketch variant',
30
+ },
31
+ },
32
+ },
33
+ ] as const satisfies Resource[];
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+
9
+ import { meta } from '#meta';
10
+
11
+ import type { SketchVariant } from './types';
12
+
13
+ /**
14
+ * A sketch variant contribution. Each renderer plugin (tldraw, excalidraw, ...)
15
+ * contributes one of these via `Capability.contributes(IllustratorCapabilities.VariantProvider, variant)`.
16
+ * Consumers iterate via `Capability.getAll(IllustratorCapabilities.VariantProvider)` (Effect) or
17
+ * `useCapabilities(IllustratorCapabilities.VariantProvider)` (React).
18
+ */
19
+ export const VariantProvider = Capability.make<SketchVariant>(`${meta.profile.key}.capability.variant`);
@@ -0,0 +1,69 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';
10
+ import { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
11
+ import { CardAnnotation, CollectionItemAnnotation } from '@dxos/schema';
12
+
13
+ /**
14
+ * Base Sketch object. Renderer-specific canvas data lives in a separate referenced object.
15
+ * Unifies all canvas renderers (tldraw, excalidraw, ...) under one typename so they share
16
+ * graph node, create flow, and shared surface scaffolding.
17
+ */
18
+ export class Sketch extends Type.makeObject<Sketch>(DXN.make('org.dxos.type.sketch', '0.1.0'))(
19
+ Schema.Struct({
20
+ name: Schema.optional(Schema.String),
21
+ canvas: Ref.Ref(Obj.Unknown)
22
+ .annotations({ description: 'Reference to the renderer-specific canvas object.' })
23
+ .pipe(FormInputAnnotation.set(false)),
24
+ }).pipe(
25
+ LabelAnnotation.set(['name']),
26
+ Annotation.IconAnnotation.set({ icon: 'ph--compass-tool--regular', hue: 'indigo' }),
27
+ CardAnnotation.set(true),
28
+ CollectionItemAnnotation.set(true),
29
+ ),
30
+ ) {}
31
+
32
+ /**
33
+ * Variant-narrowed reference to a Sketch.
34
+ *
35
+ * Encodes — at the type level — that the canvas ref points to a specific canvas type.
36
+ * Runtime narrowing is performed by `loadSketch(ref, canvasType)` which validates and
37
+ * returns the resolved Sketch together with its typed canvas.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * input: Schema.Struct({
42
+ * sketch: SketchRef(Tldraw.Canvas),
43
+ * }),
44
+ * // handler:
45
+ * const { sketch, canvas } = yield* loadSketch(input.sketch, Tldraw.Canvas);
46
+ * // `canvas` is typed as Tldraw.Canvas.
47
+ * ```
48
+ */
49
+ export type SketchRef<_V> = Ref.Ref<Sketch>;
50
+
51
+ export const SketchRef = <S extends Type.AnyObj>(_canvasType: S) =>
52
+ Ref.Ref(Sketch) as Schema.Schema<SketchRef<Type.InstanceType<S>>, any, never>;
53
+
54
+ /**
55
+ * Build a base `Sketch` object referencing the given canvas ECHO object.
56
+ *
57
+ * The canvas is stored as a `Ref` so the renderer-specific data (e.g. `Tldraw.Canvas`,
58
+ * `Excalidraw.Canvas`) lives as its own ECHO object alongside the Sketch.
59
+ *
60
+ * @param name Optional display name shown in the graph node and Properties form.
61
+ * @param canvas Renderer-specific canvas object.
62
+ * @returns A new `Sketch` object with `canvas` stored as a Ref.
63
+ */
64
+ export const make = ({ name, canvas }: { name?: string; canvas: Obj.Unknown }): Sketch => {
65
+ return Obj.make(Sketch, { name, canvas: Ref.make(canvas) });
66
+ };
67
+
68
+ /** Type guard for {@link Sketch} objects. */
69
+ export const isSketch = (object: unknown): object is Sketch => Obj.instanceOf(Sketch, object);