@things-factory/figure-ui 10.1.74 → 10.1.76

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 (244) hide show
  1. package/client/figure-assistant.ts +3 -3
  2. package/client/graphql/{v3-store.ts → store.ts} +6 -6
  3. package/client/modeller/{v3-ai-proposal.ts → ai-proposal.ts} +8 -8
  4. package/client/modeller/{v3-align.ts → align.ts} +14 -14
  5. package/client/modeller/{v3-canvas.ts → canvas.ts} +13 -13
  6. package/client/modeller/{v3-clips.ts → clips.ts} +5 -5
  7. package/client/modeller/{v3-controls.ts → controls.ts} +2 -2
  8. package/client/modeller/{v3-editor.ts → editor.ts} +137 -126
  9. package/client/modeller/{figure-v3-capability.ts → figure-capability-panel.ts} +7 -7
  10. package/client/modeller/{figure-v3-clips.ts → figure-clips.ts} +10 -10
  11. package/client/modeller/{figure-v3-controls.ts → figure-controls.ts} +9 -9
  12. package/client/modeller/{v3-figure-facts.ts → figure-facts.ts} +11 -11
  13. package/client/modeller/{figure-v3-first-save.ts → figure-first-save.ts} +3 -3
  14. package/client/modeller/figure-history-panel.ts +1 -1
  15. package/client/modeller/{figure-v3-links.ts → figure-links.ts} +35 -35
  16. package/client/modeller/{figure-v3-material.ts → figure-material-panel.ts} +20 -20
  17. package/client/modeller/{figure-v3-parts.ts → figure-parts.ts} +15 -15
  18. package/client/modeller/{figure-v3-preview.ts → figure-preview.ts} +14 -14
  19. package/client/modeller/{figure-v3-properties.ts → figure-properties.ts} +25 -25
  20. package/client/modeller/{figure-v3-recipe-catalog.ts → figure-recipe-catalog.ts} +10 -10
  21. package/client/modeller/{figure-v3-verdict.ts → figure-verdict.ts} +24 -24
  22. package/client/modeller/gate-findings.ts +2 -2
  23. package/client/modeller/{v3-gizmo.ts → gizmo.ts} +11 -11
  24. package/client/modeller/{v3-joint-drag.ts → joint-drag.ts} +11 -11
  25. package/client/modeller/{v3-joint-fan.ts → joint-fan.ts} +13 -13
  26. package/client/modeller/{v3-links.ts → links.ts} +59 -59
  27. package/client/modeller/{v3-live.ts → live.ts} +9 -9
  28. package/client/modeller/{v3-maturity.ts → maturity.ts} +65 -65
  29. package/client/modeller/modeller-route.ts +1 -1
  30. package/client/modeller/{v3-occupancy.ts → occupancy.ts} +6 -6
  31. package/client/modeller/{v3-palette.ts → palette.ts} +3 -3
  32. package/client/modeller/{v3-preview-model.ts → preview-model.ts} +8 -8
  33. package/client/modeller/{v3-product-session.ts → product-session.ts} +25 -25
  34. package/client/modeller/{v3-recipe-source.ts → recipe-source.ts} +2 -2
  35. package/client/modeller/{v3-recipes.ts → recipes.ts} +9 -9
  36. package/client/modeller/{v3-refusals.ts → refusals.ts} +62 -13
  37. package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
  38. package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
  39. package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
  40. package/client/pages/figure-list-page.ts +4 -4
  41. package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
  42. package/client/route.ts +1 -5
  43. package/dist-client/figure-assistant.js +3 -3
  44. package/dist-client/figure-assistant.js.map +1 -1
  45. package/dist-client/graphql/store.d.ts +3 -0
  46. package/dist-client/graphql/store.js +39 -0
  47. package/dist-client/graphql/store.js.map +1 -0
  48. package/dist-client/modeller/ai-proposal.d.ts +51 -0
  49. package/dist-client/modeller/ai-proposal.js +64 -0
  50. package/dist-client/modeller/ai-proposal.js.map +1 -0
  51. package/dist-client/modeller/align.d.ts +30 -0
  52. package/dist-client/modeller/align.js +75 -0
  53. package/dist-client/modeller/align.js.map +1 -0
  54. package/dist-client/modeller/canvas.d.ts +72 -0
  55. package/dist-client/modeller/canvas.js +379 -0
  56. package/dist-client/modeller/canvas.js.map +1 -0
  57. package/dist-client/modeller/clips.d.ts +39 -0
  58. package/dist-client/modeller/clips.js +58 -0
  59. package/dist-client/modeller/clips.js.map +1 -0
  60. package/dist-client/modeller/controls.d.ts +28 -0
  61. package/dist-client/modeller/controls.js +28 -0
  62. package/dist-client/modeller/controls.js.map +1 -0
  63. package/dist-client/modeller/editor.d.ts +203 -0
  64. package/dist-client/modeller/editor.js +1526 -0
  65. package/dist-client/modeller/editor.js.map +1 -0
  66. package/dist-client/modeller/fields.d.ts +45 -0
  67. package/dist-client/modeller/fields.js +83 -0
  68. package/dist-client/modeller/fields.js.map +1 -0
  69. package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
  70. package/dist-client/modeller/figure-capability-panel.js +141 -0
  71. package/dist-client/modeller/figure-capability-panel.js.map +1 -0
  72. package/dist-client/modeller/figure-clips.d.ts +31 -0
  73. package/dist-client/modeller/figure-clips.js +268 -0
  74. package/dist-client/modeller/figure-clips.js.map +1 -0
  75. package/dist-client/modeller/figure-controls.d.ts +36 -0
  76. package/dist-client/modeller/figure-controls.js +226 -0
  77. package/dist-client/modeller/figure-controls.js.map +1 -0
  78. package/dist-client/modeller/figure-facts.d.ts +35 -0
  79. package/dist-client/modeller/figure-facts.js +55 -0
  80. package/dist-client/modeller/figure-facts.js.map +1 -0
  81. package/dist-client/modeller/figure-first-save.d.ts +20 -0
  82. package/dist-client/modeller/figure-first-save.js +118 -0
  83. package/dist-client/modeller/figure-first-save.js.map +1 -0
  84. package/dist-client/modeller/figure-history-panel.d.ts +1 -1
  85. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  86. package/dist-client/modeller/figure-links.d.ts +77 -0
  87. package/dist-client/modeller/figure-links.js +649 -0
  88. package/dist-client/modeller/figure-links.js.map +1 -0
  89. package/dist-client/modeller/figure-material-panel.d.ts +39 -0
  90. package/dist-client/modeller/figure-material-panel.js +323 -0
  91. package/dist-client/modeller/figure-material-panel.js.map +1 -0
  92. package/dist-client/modeller/figure-parts.d.ts +69 -0
  93. package/dist-client/modeller/figure-parts.js +559 -0
  94. package/dist-client/modeller/figure-parts.js.map +1 -0
  95. package/dist-client/modeller/figure-preview.d.ts +34 -0
  96. package/dist-client/modeller/figure-preview.js +346 -0
  97. package/dist-client/modeller/figure-preview.js.map +1 -0
  98. package/dist-client/modeller/figure-properties.d.ts +45 -0
  99. package/dist-client/modeller/figure-properties.js +239 -0
  100. package/dist-client/modeller/figure-properties.js.map +1 -0
  101. package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
  102. package/dist-client/modeller/figure-recipe-catalog.js +399 -0
  103. package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
  104. package/dist-client/modeller/figure-verdict.d.ts +64 -0
  105. package/dist-client/modeller/figure-verdict.js +787 -0
  106. package/dist-client/modeller/figure-verdict.js.map +1 -0
  107. package/dist-client/modeller/first-save.d.ts +17 -0
  108. package/dist-client/modeller/first-save.js +26 -0
  109. package/dist-client/modeller/first-save.js.map +1 -0
  110. package/dist-client/modeller/gate-findings.d.ts +2 -2
  111. package/dist-client/modeller/gate-findings.js +2 -2
  112. package/dist-client/modeller/gate-findings.js.map +1 -1
  113. package/dist-client/modeller/gizmo.d.ts +72 -0
  114. package/dist-client/modeller/gizmo.js +111 -0
  115. package/dist-client/modeller/gizmo.js.map +1 -0
  116. package/dist-client/modeller/help.d.ts +18 -0
  117. package/dist-client/modeller/help.js +37 -0
  118. package/dist-client/modeller/help.js.map +1 -0
  119. package/dist-client/modeller/joint-drag.d.ts +19 -0
  120. package/dist-client/modeller/joint-drag.js +97 -0
  121. package/dist-client/modeller/joint-drag.js.map +1 -0
  122. package/dist-client/modeller/joint-fan.d.ts +9 -0
  123. package/dist-client/modeller/joint-fan.js +276 -0
  124. package/dist-client/modeller/joint-fan.js.map +1 -0
  125. package/dist-client/modeller/keys.d.ts +22 -0
  126. package/dist-client/modeller/keys.js +26 -0
  127. package/dist-client/modeller/keys.js.map +1 -0
  128. package/dist-client/modeller/links.d.ts +267 -0
  129. package/dist-client/modeller/links.js +480 -0
  130. package/dist-client/modeller/links.js.map +1 -0
  131. package/dist-client/modeller/live.d.ts +37 -0
  132. package/dist-client/modeller/live.js +68 -0
  133. package/dist-client/modeller/live.js.map +1 -0
  134. package/dist-client/modeller/maturity.d.ts +120 -0
  135. package/dist-client/modeller/maturity.js +313 -0
  136. package/dist-client/modeller/maturity.js.map +1 -0
  137. package/dist-client/modeller/modeller-route.d.ts +1 -1
  138. package/dist-client/modeller/modeller-route.js +1 -1
  139. package/dist-client/modeller/modeller-route.js.map +1 -1
  140. package/dist-client/modeller/occupancy.d.ts +18 -0
  141. package/dist-client/modeller/occupancy.js +24 -0
  142. package/dist-client/modeller/occupancy.js.map +1 -0
  143. package/dist-client/modeller/palette.d.ts +7 -0
  144. package/dist-client/modeller/palette.js +46 -0
  145. package/dist-client/modeller/palette.js.map +1 -0
  146. package/dist-client/modeller/preview-model.d.ts +47 -0
  147. package/dist-client/modeller/preview-model.js +68 -0
  148. package/dist-client/modeller/preview-model.js.map +1 -0
  149. package/dist-client/modeller/product-session.d.ts +510 -0
  150. package/dist-client/modeller/product-session.js +157 -0
  151. package/dist-client/modeller/product-session.js.map +1 -0
  152. package/dist-client/modeller/recipe-source.d.ts +8 -0
  153. package/dist-client/modeller/recipe-source.js +14 -0
  154. package/dist-client/modeller/recipe-source.js.map +1 -0
  155. package/dist-client/modeller/recipes.d.ts +37 -0
  156. package/dist-client/modeller/recipes.js +103 -0
  157. package/dist-client/modeller/recipes.js.map +1 -0
  158. package/dist-client/modeller/refusals.d.ts +68 -0
  159. package/dist-client/modeller/refusals.js +142 -0
  160. package/dist-client/modeller/refusals.js.map +1 -0
  161. package/dist-client/modeller/renderer.d.ts +40 -0
  162. package/dist-client/modeller/renderer.js +73 -0
  163. package/dist-client/modeller/renderer.js.map +1 -0
  164. package/dist-client/modeller/stage-model.d.ts +39 -0
  165. package/dist-client/modeller/stage-model.js +147 -0
  166. package/dist-client/modeller/stage-model.js.map +1 -0
  167. package/dist-client/modeller/thumbnail.d.ts +3 -0
  168. package/dist-client/modeller/thumbnail.js +75 -0
  169. package/dist-client/modeller/thumbnail.js.map +1 -0
  170. package/dist-client/modeller/v3-clips.d.ts +1 -1
  171. package/dist-client/modeller/v3-editor.d.ts +5 -0
  172. package/dist-client/modeller/v3-editor.js +21 -10
  173. package/dist-client/modeller/v3-editor.js.map +1 -1
  174. package/dist-client/modeller/v3-product-session.d.ts +31 -0
  175. package/dist-client/modeller/v3-refusals.d.ts +37 -5
  176. package/dist-client/modeller/v3-refusals.js +59 -16
  177. package/dist-client/modeller/v3-refusals.js.map +1 -1
  178. package/dist-client/pages/figure-list-page.d.ts +1 -1
  179. package/dist-client/pages/figure-list-page.js +4 -4
  180. package/dist-client/pages/figure-list-page.js.map +1 -1
  181. package/dist-client/pages/figure-modeller-page.d.ts +102 -0
  182. package/dist-client/pages/figure-modeller-page.js +548 -0
  183. package/dist-client/pages/figure-modeller-page.js.map +1 -0
  184. package/dist-client/route.d.ts +1 -1
  185. package/dist-client/route.js +1 -5
  186. package/dist-client/route.js.map +1 -1
  187. package/dist-client/tsconfig.tsbuildinfo +1 -1
  188. package/docs/product-cutover.md +25 -0
  189. package/package.json +5 -5
  190. package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
  191. package/test/{v3-align.test.ts → align.test.ts} +8 -8
  192. package/test/browser/modeller-page.js +30 -30
  193. package/test/browser/modeller.check.mjs +17 -16
  194. package/test/browser/serve.mjs +1 -1
  195. package/test/browser/thumbnail-page.js +6 -6
  196. package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
  197. package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
  198. package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
  199. package/test/figure-assistant-surface.test.ts +3 -3
  200. package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
  201. package/test/fixtures/joint-column.json +2 -2
  202. package/test/fixtures/joint-roller.json +2 -2
  203. package/test/fixtures/robot-arm-6dof.json +2 -2
  204. package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
  205. package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
  206. package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
  207. package/test/joints.ts +6 -6
  208. package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
  209. package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
  210. package/test/{v3-links.test.ts → links.test.ts} +201 -201
  211. package/test/{v3-live.test.ts → live.test.ts} +18 -18
  212. package/test/{v3-material.test.ts → material.test.ts} +12 -12
  213. package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
  214. package/test/modeller-route.test.ts +5 -6
  215. package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
  216. package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +11 -7
  217. package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
  218. package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
  219. package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
  220. package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
  221. package/test/{v3-recipes.test.ts → recipes.test.ts} +10 -10
  222. package/test/refusals.test.ts +166 -0
  223. package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
  224. package/test/renderer.test.ts +24 -0
  225. package/test/resolve-ts-siblings.mjs +1 -1
  226. package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
  227. package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
  228. package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
  229. package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
  230. package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
  231. package/test/{v3-view.test.ts → view.test.ts} +2 -2
  232. package/things-factory.config.js +2 -3
  233. package/translations/en.json +15 -7
  234. package/translations/ja.json +15 -7
  235. package/translations/ko.json +15 -7
  236. package/translations/ms.json +15 -7
  237. package/translations/zh.json +15 -7
  238. package/docs/v3-product-cutover.md +0 -25
  239. package/test/v3-refusals.test.ts +0 -136
  240. package/test/v3-renderer.test.ts +0 -24
  241. /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
  242. /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
  243. /package/client/modeller/{v3-help.ts → help.ts} +0 -0
  244. /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
@@ -0,0 +1,510 @@
1
+ import { type FigureInputChange, type FigurePrimitiveCreation, type FigurePartEdit, type FigureAuthoringAction, type FigureProposalStep } from '@hatiolab/figure-model';
2
+ export interface FigureProductRecord {
3
+ id: string;
4
+ source: string;
5
+ }
6
+ /** Persistence is injected; neither the model engine nor the renderer owns the product ID. */
7
+ export interface FigureProductStore {
8
+ load(id: string): Promise<FigureProductRecord>;
9
+ save(request: {
10
+ id?: string;
11
+ source: string;
12
+ expectedSource?: string;
13
+ creationId?: string;
14
+ /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */
15
+ thumbnail?: string;
16
+ /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */
17
+ type?: string;
18
+ }): Promise<FigureProductRecord>;
19
+ }
20
+ /** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
21
+ export declare function createFigureProductSession(store: FigureProductStore): {
22
+ read: () => {
23
+ id: string | undefined;
24
+ loading: boolean;
25
+ saving: boolean;
26
+ error: string | undefined;
27
+ context: null;
28
+ name: null;
29
+ fields: never[];
30
+ geometry: never[];
31
+ bindings: never[];
32
+ dirty: boolean;
33
+ revision: null;
34
+ canUndo?: undefined;
35
+ canRedo?: undefined;
36
+ palette?: undefined;
37
+ } | {
38
+ id: string | undefined;
39
+ loading: boolean;
40
+ saving: boolean;
41
+ error: string | undefined;
42
+ context: string;
43
+ name: string;
44
+ revision: number;
45
+ dirty: boolean;
46
+ canUndo: boolean;
47
+ canRedo: boolean;
48
+ fields: {
49
+ value: number;
50
+ id: string;
51
+ unit: import("@hatiolab/figure-model").FigureUnit;
52
+ min: number;
53
+ max: number;
54
+ role?: "design" | "state";
55
+ }[];
56
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
57
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
58
+ palette: Record<string, string>;
59
+ };
60
+ startNew({ discard }?: {
61
+ discard?: boolean | undefined;
62
+ }): {
63
+ id: string | undefined;
64
+ loading: boolean;
65
+ saving: boolean;
66
+ error: string | undefined;
67
+ context: null;
68
+ name: null;
69
+ fields: never[];
70
+ geometry: never[];
71
+ bindings: never[];
72
+ dirty: boolean;
73
+ revision: null;
74
+ canUndo?: undefined;
75
+ canRedo?: undefined;
76
+ palette?: undefined;
77
+ } | {
78
+ id: string | undefined;
79
+ loading: boolean;
80
+ saving: boolean;
81
+ error: string | undefined;
82
+ context: string;
83
+ name: string;
84
+ revision: number;
85
+ dirty: boolean;
86
+ canUndo: boolean;
87
+ canRedo: boolean;
88
+ fields: {
89
+ value: number;
90
+ id: string;
91
+ unit: import("@hatiolab/figure-model").FigureUnit;
92
+ min: number;
93
+ max: number;
94
+ role?: "design" | "state";
95
+ }[];
96
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
97
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
98
+ palette: Record<string, string>;
99
+ };
100
+ open(nextId: string, { discard }?: {
101
+ discard?: boolean | undefined;
102
+ }): Promise<{
103
+ id: string | undefined;
104
+ loading: boolean;
105
+ saving: boolean;
106
+ error: string | undefined;
107
+ context: null;
108
+ name: null;
109
+ fields: never[];
110
+ geometry: never[];
111
+ bindings: never[];
112
+ dirty: boolean;
113
+ revision: null;
114
+ canUndo?: undefined;
115
+ canRedo?: undefined;
116
+ palette?: undefined;
117
+ } | {
118
+ id: string | undefined;
119
+ loading: boolean;
120
+ saving: boolean;
121
+ error: string | undefined;
122
+ context: string;
123
+ name: string;
124
+ revision: number;
125
+ dirty: boolean;
126
+ canUndo: boolean;
127
+ canRedo: boolean;
128
+ fields: {
129
+ value: number;
130
+ id: string;
131
+ unit: import("@hatiolab/figure-model").FigureUnit;
132
+ min: number;
133
+ max: number;
134
+ role?: "design" | "state";
135
+ }[];
136
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
137
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
138
+ palette: Record<string, string>;
139
+ }>;
140
+ close({ discard }?: {
141
+ discard?: boolean | undefined;
142
+ }): {
143
+ id: string | undefined;
144
+ loading: boolean;
145
+ saving: boolean;
146
+ error: string | undefined;
147
+ context: null;
148
+ name: null;
149
+ fields: never[];
150
+ geometry: never[];
151
+ bindings: never[];
152
+ dirty: boolean;
153
+ revision: null;
154
+ canUndo?: undefined;
155
+ canRedo?: undefined;
156
+ palette?: undefined;
157
+ } | {
158
+ id: string | undefined;
159
+ loading: boolean;
160
+ saving: boolean;
161
+ error: string | undefined;
162
+ context: string;
163
+ name: string;
164
+ revision: number;
165
+ dirty: boolean;
166
+ canUndo: boolean;
167
+ canRedo: boolean;
168
+ fields: {
169
+ value: number;
170
+ id: string;
171
+ unit: import("@hatiolab/figure-model").FigureUnit;
172
+ min: number;
173
+ max: number;
174
+ role?: "design" | "state";
175
+ }[];
176
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
177
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
178
+ palette: Record<string, string>;
179
+ };
180
+ apply(changes: FigureInputChange[], origin?: "ui" | "ai", expectedRevision?: number, context?: string): {
181
+ context: null;
182
+ name: null;
183
+ fields: never[];
184
+ geometry: never[];
185
+ bindings: never[];
186
+ dirty: boolean;
187
+ revision: null;
188
+ canUndo?: undefined;
189
+ canRedo?: undefined;
190
+ palette?: undefined;
191
+ } | {
192
+ context: string;
193
+ name: string;
194
+ revision: number;
195
+ dirty: boolean;
196
+ canUndo: boolean;
197
+ canRedo: boolean;
198
+ fields: {
199
+ value: number;
200
+ id: string;
201
+ unit: import("@hatiolab/figure-model").FigureUnit;
202
+ min: number;
203
+ max: number;
204
+ role?: "design" | "state";
205
+ }[];
206
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
207
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
208
+ palette: Record<string, string>;
209
+ };
210
+ /**
211
+ * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
212
+ * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
213
+ */
214
+ applyProposal(steps: readonly FigureProposalStep[], expectedRevision: number, context: string): {
215
+ context: null;
216
+ name: null;
217
+ fields: never[];
218
+ geometry: never[];
219
+ bindings: never[];
220
+ dirty: boolean;
221
+ revision: null;
222
+ canUndo?: undefined;
223
+ canRedo?: undefined;
224
+ palette?: undefined;
225
+ } | {
226
+ context: string;
227
+ name: string;
228
+ revision: number;
229
+ dirty: boolean;
230
+ canUndo: boolean;
231
+ canRedo: boolean;
232
+ fields: {
233
+ value: number;
234
+ id: string;
235
+ unit: import("@hatiolab/figure-model").FigureUnit;
236
+ min: number;
237
+ max: number;
238
+ role?: "design" | "state";
239
+ }[];
240
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
241
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
242
+ palette: Record<string, string>;
243
+ };
244
+ /**
245
+ * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open
246
+ * now. The same path as a proposal; the person is its origin.
247
+ */
248
+ applyRecipe(steps: readonly FigureProposalStep[]): {
249
+ context: null;
250
+ name: null;
251
+ fields: never[];
252
+ geometry: never[];
253
+ bindings: never[];
254
+ dirty: boolean;
255
+ revision: null;
256
+ canUndo?: undefined;
257
+ canRedo?: undefined;
258
+ palette?: undefined;
259
+ } | {
260
+ context: string;
261
+ name: string;
262
+ revision: number;
263
+ dirty: boolean;
264
+ canUndo: boolean;
265
+ canRedo: boolean;
266
+ fields: {
267
+ value: number;
268
+ id: string;
269
+ unit: import("@hatiolab/figure-model").FigureUnit;
270
+ min: number;
271
+ max: number;
272
+ role?: "design" | "state";
273
+ }[];
274
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
275
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
276
+ palette: Record<string, string>;
277
+ };
278
+ add(part: FigurePrimitiveCreation): {
279
+ context: null;
280
+ name: null;
281
+ fields: never[];
282
+ geometry: never[];
283
+ bindings: never[];
284
+ dirty: boolean;
285
+ revision: null;
286
+ canUndo?: undefined;
287
+ canRedo?: undefined;
288
+ palette?: undefined;
289
+ } | {
290
+ context: string;
291
+ name: string;
292
+ revision: number;
293
+ dirty: boolean;
294
+ canUndo: boolean;
295
+ canRedo: boolean;
296
+ fields: {
297
+ value: number;
298
+ id: string;
299
+ unit: import("@hatiolab/figure-model").FigureUnit;
300
+ min: number;
301
+ max: number;
302
+ role?: "design" | "state";
303
+ }[];
304
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
305
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
306
+ palette: Record<string, string>;
307
+ };
308
+ edit(edit: FigurePartEdit): {
309
+ context: null;
310
+ name: null;
311
+ fields: never[];
312
+ geometry: never[];
313
+ bindings: never[];
314
+ dirty: boolean;
315
+ revision: null;
316
+ canUndo?: undefined;
317
+ canRedo?: undefined;
318
+ palette?: undefined;
319
+ } | {
320
+ context: string;
321
+ name: string;
322
+ revision: number;
323
+ dirty: boolean;
324
+ canUndo: boolean;
325
+ canRedo: boolean;
326
+ fields: {
327
+ value: number;
328
+ id: string;
329
+ unit: import("@hatiolab/figure-model").FigureUnit;
330
+ min: number;
331
+ max: number;
332
+ role?: "design" | "state";
333
+ }[];
334
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
335
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
336
+ palette: Record<string, string>;
337
+ } | {
338
+ removal: import("@hatiolab/figure-model").RemovalNotice;
339
+ context: null;
340
+ name: null;
341
+ fields: never[];
342
+ geometry: never[];
343
+ bindings: never[];
344
+ dirty: boolean;
345
+ revision: null;
346
+ canUndo?: undefined;
347
+ canRedo?: undefined;
348
+ palette?: undefined;
349
+ } | {
350
+ removal: import("@hatiolab/figure-model").RemovalNotice;
351
+ context: string;
352
+ name: string;
353
+ revision: number;
354
+ dirty: boolean;
355
+ canUndo: boolean;
356
+ canRedo: boolean;
357
+ fields: {
358
+ value: number;
359
+ id: string;
360
+ unit: import("@hatiolab/figure-model").FigureUnit;
361
+ min: number;
362
+ max: number;
363
+ role?: "design" | "state";
364
+ }[];
365
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
366
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
367
+ palette: Record<string, string>;
368
+ };
369
+ /** One authoring command — what a person means, not a graph edit — as one step of the same history. */
370
+ command(action: FigureAuthoringAction): {
371
+ context: null;
372
+ name: null;
373
+ fields: never[];
374
+ geometry: never[];
375
+ bindings: never[];
376
+ dirty: boolean;
377
+ revision: null;
378
+ canUndo?: undefined;
379
+ canRedo?: undefined;
380
+ palette?: undefined;
381
+ } | {
382
+ context: string;
383
+ name: string;
384
+ revision: number;
385
+ dirty: boolean;
386
+ canUndo: boolean;
387
+ canRedo: boolean;
388
+ fields: {
389
+ value: number;
390
+ id: string;
391
+ unit: import("@hatiolab/figure-model").FigureUnit;
392
+ min: number;
393
+ max: number;
394
+ role?: "design" | "state";
395
+ }[];
396
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
397
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
398
+ palette: Record<string, string>;
399
+ };
400
+ undo(): {
401
+ context: null;
402
+ name: null;
403
+ fields: never[];
404
+ geometry: never[];
405
+ bindings: never[];
406
+ dirty: boolean;
407
+ revision: null;
408
+ canUndo?: undefined;
409
+ canRedo?: undefined;
410
+ palette?: undefined;
411
+ } | {
412
+ context: string;
413
+ name: string;
414
+ revision: number;
415
+ dirty: boolean;
416
+ canUndo: boolean;
417
+ canRedo: boolean;
418
+ fields: {
419
+ value: number;
420
+ id: string;
421
+ unit: import("@hatiolab/figure-model").FigureUnit;
422
+ min: number;
423
+ max: number;
424
+ role?: "design" | "state";
425
+ }[];
426
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
427
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
428
+ palette: Record<string, string>;
429
+ };
430
+ redo(): {
431
+ context: null;
432
+ name: null;
433
+ fields: never[];
434
+ geometry: never[];
435
+ bindings: never[];
436
+ dirty: boolean;
437
+ revision: null;
438
+ canUndo?: undefined;
439
+ canRedo?: undefined;
440
+ palette?: undefined;
441
+ } | {
442
+ context: string;
443
+ name: string;
444
+ revision: number;
445
+ dirty: boolean;
446
+ canUndo: boolean;
447
+ canRedo: boolean;
448
+ fields: {
449
+ value: number;
450
+ id: string;
451
+ unit: import("@hatiolab/figure-model").FigureUnit;
452
+ min: number;
453
+ max: number;
454
+ role?: "design" | "state";
455
+ }[];
456
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
457
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
458
+ palette: Record<string, string>;
459
+ };
460
+ source(): string;
461
+ /**
462
+ * Saves the model, and the card's picture when the caller managed to take one.
463
+ *
464
+ * The picture is the caller's because taking it needs a canvas, and this session is the place that knows
465
+ * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the
466
+ * source matters more than the card (see `thumbnail.ts`).
467
+ */
468
+ save({ thumbnail, type }?: {
469
+ thumbnail?: string;
470
+ type?: string;
471
+ }): Promise<{
472
+ id: string | undefined;
473
+ loading: boolean;
474
+ saving: boolean;
475
+ error: string | undefined;
476
+ context: null;
477
+ name: null;
478
+ fields: never[];
479
+ geometry: never[];
480
+ bindings: never[];
481
+ dirty: boolean;
482
+ revision: null;
483
+ canUndo?: undefined;
484
+ canRedo?: undefined;
485
+ palette?: undefined;
486
+ } | {
487
+ id: string | undefined;
488
+ loading: boolean;
489
+ saving: boolean;
490
+ error: string | undefined;
491
+ context: string;
492
+ name: string;
493
+ revision: number;
494
+ dirty: boolean;
495
+ canUndo: boolean;
496
+ canRedo: boolean;
497
+ fields: {
498
+ value: number;
499
+ id: string;
500
+ unit: import("@hatiolab/figure-model").FigureUnit;
501
+ min: number;
502
+ max: number;
503
+ role?: "design" | "state";
504
+ }[];
505
+ geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
506
+ bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
507
+ palette: Record<string, string>;
508
+ }>;
509
+ };
510
+ export type FigureProductSession = ReturnType<typeof createFigureProductSession>;
@@ -0,0 +1,157 @@
1
+ import { createFigureAsset, createFigureEditorWorkspace, serializeFigureAsset, parseFigureAsset } from '@hatiolab/figure-model';
2
+ /** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
3
+ export function createFigureProductSession(store) {
4
+ const workspace = createFigureEditorWorkspace();
5
+ let epoch = 0, id, baseline;
6
+ let loading = false, saving = false, error;
7
+ let creationId;
8
+ function read() {
9
+ return { ...workspace.read(), id, loading, saving, error };
10
+ }
11
+ function writable() {
12
+ const view = workspace.read();
13
+ if (loading || error || !view.context || view.revision === null)
14
+ throw Error('먼저 모델을 정상적으로 열어 주세요.');
15
+ // Serializing writes also makes create retries safe: no second create while the first is pending.
16
+ if (saving)
17
+ throw Error('저장이 끝난 뒤 편집해 주세요.');
18
+ return { context: view.context, expectedRevision: view.revision, origin: 'ui' };
19
+ }
20
+ function clear() {
21
+ workspace.close({ discardDesignChanges: true });
22
+ id = undefined;
23
+ baseline = undefined;
24
+ error = undefined;
25
+ saving = false;
26
+ creationId = undefined;
27
+ }
28
+ function canLeave(discard) {
29
+ if (saving)
30
+ throw Error('저장이 끝난 뒤 모델을 전환해 주세요.');
31
+ if (workspace.read().dirty && !discard)
32
+ throw Error('저장하지 않은 변경이 있습니다.');
33
+ }
34
+ return {
35
+ read,
36
+ startNew({ discard = false } = {}) {
37
+ canLeave(discard);
38
+ ++epoch;
39
+ clear();
40
+ loading = false;
41
+ workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')));
42
+ creationId = crypto.randomUUID();
43
+ return read();
44
+ },
45
+ async open(nextId, { discard = false } = {}) {
46
+ canLeave(discard);
47
+ const requestEpoch = ++epoch;
48
+ clear();
49
+ loading = true;
50
+ try {
51
+ const record = await store.load(nextId);
52
+ if (requestEpoch !== epoch)
53
+ return read();
54
+ if (record.id !== nextId)
55
+ throw Error('요청한 Figure와 응답의 대상이 다릅니다.');
56
+ workspace.open(record.source);
57
+ id = nextId;
58
+ baseline = record.source;
59
+ }
60
+ catch (cause) {
61
+ if (requestEpoch === epoch)
62
+ error = cause instanceof Error ? cause.message : String(cause);
63
+ }
64
+ finally {
65
+ if (requestEpoch === epoch)
66
+ loading = false;
67
+ }
68
+ return read();
69
+ },
70
+ close({ discard = false } = {}) {
71
+ canLeave(discard);
72
+ ++epoch;
73
+ clear();
74
+ loading = false;
75
+ return read();
76
+ },
77
+ apply(changes, origin = 'ui', expectedRevision, context) {
78
+ const control = writable();
79
+ // AI must supply the context AND revision captured when it started. Never substitute current values.
80
+ if (origin === 'ai' && (context === undefined || expectedRevision === undefined))
81
+ throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.');
82
+ return workspace.apply({
83
+ ...control,
84
+ origin,
85
+ context: context ?? control.context,
86
+ expectedRevision: expectedRevision ?? control.expectedRevision,
87
+ changes
88
+ });
89
+ },
90
+ /**
91
+ * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
92
+ * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
93
+ */
94
+ applyProposal(steps, expectedRevision, context) {
95
+ writable();
96
+ return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps });
97
+ },
98
+ /**
99
+ * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open
100
+ * now. The same path as a proposal; the person is its origin.
101
+ */
102
+ applyRecipe(steps) {
103
+ const at = writable();
104
+ return workspace.applyProposal({ origin: 'ui', context: at.context, expectedRevision: at.expectedRevision, steps });
105
+ },
106
+ add(part) {
107
+ return workspace.addPrimitive({ ...writable(), part });
108
+ },
109
+ edit(edit) {
110
+ return workspace.editPart({ ...writable(), edit });
111
+ },
112
+ /** One authoring command — what a person means, not a graph edit — as one step of the same history. */
113
+ command(action) {
114
+ return workspace.command({ ...writable(), action });
115
+ },
116
+ undo() {
117
+ return workspace.undo(writable());
118
+ },
119
+ redo() {
120
+ return workspace.redo(writable());
121
+ },
122
+ source() {
123
+ const { context } = writable();
124
+ return workspace.export({ context }).text;
125
+ },
126
+ /**
127
+ * Saves the model, and the card's picture when the caller managed to take one.
128
+ *
129
+ * The picture is the caller's because taking it needs a canvas, and this session is the place that knows
130
+ * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the
131
+ * source matters more than the card (see `thumbnail.ts`).
132
+ */
133
+ async save({ thumbnail, type } = {}) {
134
+ const control = writable(), requestEpoch = epoch;
135
+ const exported = workspace.export(control);
136
+ saving = true;
137
+ try {
138
+ const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, ...(id || !type ? {} : { type }) });
139
+ if (requestEpoch !== epoch)
140
+ return read();
141
+ if (!record.id || (id !== undefined && record.id !== id))
142
+ throw Error('저장 응답의 Figure 대상이 다릅니다.');
143
+ if (serializeFigureAsset(parseFigureAsset(record.source)) !== exported.text)
144
+ throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.');
145
+ workspace.acknowledgeSaved(exported);
146
+ id = record.id;
147
+ baseline = record.source;
148
+ }
149
+ finally {
150
+ if (requestEpoch === epoch)
151
+ saving = false;
152
+ }
153
+ return read();
154
+ }
155
+ };
156
+ }
157
+ //# sourceMappingURL=product-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-session.js","sourceRoot":"","sources":["../../client/modeller/product-session.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,gBAAgB,EAMjB,MAAM,wBAAwB,CAAA;AAsB/B,iGAAiG;AACjG,MAAM,UAAU,0BAA0B,CAAC,KAAyB;IAClE,MAAM,SAAS,GAAG,2BAA2B,EAAE,CAAA;IAC/C,IAAI,KAAK,GAAG,CAAC,EACX,EAAsB,EACtB,QAA4B,CAAA;IAC9B,IAAI,OAAO,GAAG,KAAK,EACjB,MAAM,GAAG,KAAK,EACd,KAAyB,CAAA;IAC3B,IAAI,UAA8B,CAAA;IAClC,SAAS,IAAI;QACX,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC5D,CAAC;IACD,SAAS,QAAQ;QACf,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;QAC7B,IAAI,OAAO,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACpG,kGAAkG;QAClG,IAAI,MAAM;YAAE,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAa,EAAE,CAAA;IAC1F,CAAC;IACD,SAAS,KAAK;QACZ,SAAS,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,EAAE,GAAG,SAAS,CAAA;QACd,QAAQ,GAAG,SAAS,CAAA;QACpB,KAAK,GAAG,SAAS,CAAA;QACjB,MAAM,GAAG,KAAK,CAAA;QACd,UAAU,GAAG,SAAS,CAAA;IACxB,CAAC;IACD,SAAS,QAAQ,CAAC,OAAgB;QAChC,IAAI,MAAM;YAAE,MAAM,KAAK,CAAC,uBAAuB,CAAC,CAAA;QAChD,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAA;IAC1E,CAAC;IACD,OAAO;QACL,IAAI;QACJ,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YAC/B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,EAAE,KAAK,CAAA;YACP,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,KAAK,CAAA;YACf,SAAS,CAAC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;YACnE,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;YAChC,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YACjD,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,MAAM,YAAY,GAAG,EAAE,KAAK,CAAA;YAC5B,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,IAAI,CAAA;YACd,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBACvC,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,IAAI,EAAE,CAAA;gBACzC,IAAI,MAAM,CAAC,EAAE,KAAK,MAAM;oBAAE,MAAM,KAAK,CAAC,2BAA2B,CAAC,CAAA;gBAClE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBAC7B,EAAE,GAAG,MAAM,CAAA;gBACX,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAA;YAC1B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,YAAY,KAAK,KAAK;oBAAE,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5F,CAAC;oBAAS,CAAC;gBACT,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,GAAG,KAAK,CAAA;YAC7C,CAAC;YACD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE;YAC5B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,EAAE,KAAK,CAAA;YACP,KAAK,EAAE,CAAA;YACP,OAAO,GAAG,KAAK,CAAA;YACf,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QACD,KAAK,CAAC,OAA4B,EAAE,SAAsB,IAAI,EAAE,gBAAyB,EAAE,OAAgB;YACzG,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAA;YAC1B,qGAAqG;YACrG,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,gBAAgB,KAAK,SAAS,CAAC;gBAC9E,MAAM,KAAK,CAAC,mCAAmC,CAAC,CAAA;YAClD,OAAO,SAAS,CAAC,KAAK,CAAC;gBACrB,GAAG,OAAO;gBACV,MAAM;gBACN,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO;gBACnC,gBAAgB,EAAE,gBAAgB,IAAI,OAAO,CAAC,gBAAgB;gBAC9D,OAAO;aACR,CAAC,CAAA;QACJ,CAAC;QACD;;;WAGG;QACH,aAAa,CAAC,KAAoC,EAAE,gBAAwB,EAAE,OAAe;YAC3F,QAAQ,EAAE,CAAA;YACV,OAAO,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAA;QACpF,CAAC;QACD;;;WAGG;QACH,WAAW,CAAC,KAAoC;YAC9C,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAA;YACrB,OAAO,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAA;QACrH,CAAC;QACD,GAAG,CAAC,IAA6B;YAC/B,OAAO,SAAS,CAAC,YAAY,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACxD,CAAC;QACD,IAAI,CAAC,IAAoB;YACvB,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,CAAC;QACD,uGAAuG;QACvG,OAAO,CAAC,MAA6B;YACnC,OAAO,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QACrD,CAAC;QACD,IAAI;YACF,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACnC,CAAC;QACD,IAAI;YACF,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACnC,CAAC;QACD,MAAM;YACJ,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;YAC9B,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAA;QAC3C,CAAC;QACD;;;;;;WAMG;QACH,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,KAA4C,EAAE;YACxE,MAAM,OAAO,GAAG,QAAQ,EAAE,EACxB,YAAY,GAAG,KAAK,CAAA;YACtB,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,GAAG,IAAI,CAAA;YACb,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;gBACjJ,IAAI,YAAY,KAAK,KAAK;oBAAE,OAAO,IAAI,EAAE,CAAA;gBACzC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;oBAAE,MAAM,KAAK,CAAC,yBAAyB,CAAC,CAAA;gBAChG,IAAI,oBAAoB,CAAC,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI;oBACzE,MAAM,KAAK,CAAC,wCAAwC,CAAC,CAAA;gBACvD,SAAS,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAA;gBACpC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAA;gBACd,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAA;YAC1B,CAAC;oBAAS,CAAC;gBACT,IAAI,YAAY,KAAK,KAAK;oBAAE,MAAM,GAAG,KAAK,CAAA;YAC5C,CAAC;YACD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {\n createFigureAsset,\n createFigureEditorWorkspace,\n serializeFigureAsset,\n parseFigureAsset,\n type FigureInputChange,\n type FigurePrimitiveCreation,\n type FigurePartEdit,\n type FigureAuthoringAction,\n type FigureProposalStep\n} from '@hatiolab/figure-model'\n\nexport interface FigureProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface FigureProductStore {\n load(id: string): Promise<FigureProductRecord>\n save(request: {\n id?: string\n source: string\n expectedSource?: string\n creationId?: string\n /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */\n thumbnail?: string\n /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */\n type?: string\n }): Promise<FigureProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createFigureProductSession(store: FigureProductStore) {\n const workspace = createFigureEditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: FigureInputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n },\n /**\n * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.\n * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.\n */\n applyProposal(steps: readonly FigureProposalStep[], expectedRevision: number, context: string) {\n writable()\n return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps })\n },\n /**\n * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open\n * now. The same path as a proposal; the person is its origin.\n */\n applyRecipe(steps: readonly FigureProposalStep[]) {\n const at = writable()\n return workspace.applyProposal({ origin: 'ui', context: at.context, expectedRevision: at.expectedRevision, steps })\n },\n add(part: FigurePrimitiveCreation) {\n return workspace.addPrimitive({ ...writable(), part })\n },\n edit(edit: FigurePartEdit) {\n return workspace.editPart({ ...writable(), edit })\n },\n /** One authoring command — what a person means, not a graph edit — as one step of the same history. */\n command(action: FigureAuthoringAction) {\n return workspace.command({ ...writable(), action })\n },\n undo() {\n return workspace.undo(writable())\n },\n redo() {\n return workspace.redo(writable())\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n /**\n * Saves the model, and the card's picture when the caller managed to take one.\n *\n * The picture is the caller's because taking it needs a canvas, and this session is the place that knows\n * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the\n * source matters more than the card (see `thumbnail.ts`).\n */\n async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n saving = true\n try {\n const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, ...(id || !type ? {} : { type }) })\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeFigureAsset(parseFigureAsset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n workspace.acknowledgeSaved(exported)\n id = record.id\n baseline = record.source\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type FigureProductSession = ReturnType<typeof createFigureProductSession>\n"]}