@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
@@ -1,18 +1,18 @@
1
1
  import {
2
- compileV3Asset,
3
- compileV3Document,
2
+ compileFigureAsset,
3
+ compileFigureDocument,
4
4
  LIMITS,
5
5
  SEGMENT_PRESETS,
6
- V3_BUDGET_KEYS,
7
- V3_GRADE_SCALE,
8
- v3CostOf,
9
- v3GradeOf,
10
- v3MaterialKeyOf,
11
- v3ScoreOf,
12
- v3WorldBoxesOf,
13
- v3WritersOf
6
+ FIGURE_BUDGET_KEYS,
7
+ FIGURE_GRADE_SCALE,
8
+ figureCostOf,
9
+ figureGradeOf,
10
+ figureMaterialKeyOf,
11
+ figureScoreOf,
12
+ figureWorldBoxesOf,
13
+ figureWritersOf
14
14
  } from '@hatiolab/figure-model'
15
- import type { V3Asset, V3AssetGeometry, V3BudgetKey, V3BudgetUse, V3Cost, V3Geometry, V3Score } from '@hatiolab/figure-model'
15
+ import type { FigureAsset, FigureAssetGeometry, FigureBudgetKey, FigureBudgetUse, FigureCost, FigureGeometry, FigureScore } from '@hatiolab/figure-model'
16
16
 
17
17
  import { capacityFrom } from './scene-budget.js'
18
18
  import type { FigureCapacity } from './scene-budget.js'
@@ -21,40 +21,40 @@ import type { FigureCapacity } from './scene-budget.js'
21
21
  * The modeller's maturity report, read off the evaluated asset (user, 2026-09-24: the tab showed a count and nothing
22
22
  * else — 「왜 이렇게 너프되었는지」):
23
23
  *
24
- * grade A–D and its scale figure-model's `v3ScoreOf`: one screen's target shared per instance (groups,
24
+ * grade A–D and its scale figure-model's `figureScoreOf`: one screen's target shared per instance (groups,
25
25
  * triangles) and the declared rule limits (independent, transparent)
26
- * radar · axes six axes; reactive is not measured, and detail has no V3 field
26
+ * radar · axes six axes; reactive is not measured, and detail has no field
27
27
  * drawing cost triangles, distinct shapes, distinct materials
28
28
  * trend what grows with the instance count and what the instances share
29
29
  *
30
30
  * Kept apart from the element so it can be tested without a browser.
31
31
  *
32
- * ## What the V3 renderer shares, which is what the numbers count
32
+ * ## What the renderer shares, which is what the numbers count
33
33
  *
34
- * things-scene draws one mesh per drawn body (`figure-v3-rig.ts`) — no merging by material — so a body is
35
- * a draw call. It shares geometry by provider, dimensions, segments and profile (`figure-v3-geometry.ts`, `keyOf`)
34
+ * things-scene draws one mesh per drawn body (`figure-rig.ts`) — no merging by material — so a body is
35
+ * a draw call. It shares geometry by provider, dimensions, segments and profile (`figure-geometry.ts`, `keyOf`)
36
36
  * and materials by token, preset, transparency and light (`materialFor`). The keys below are those keys; a
37
37
  * distinct-shape count that used another key would describe a renderer that does not exist.
38
38
  *
39
- * ## Budgets V3 does not have
39
+ * ## Budgets does not have
40
40
  *
41
41
  * Two budgets are not measured here, and are not shown as zero:
42
- * - off-palette surfaces: the V3 material has no surface field, so there is nothing to count;
43
- * - part count by detail level: V3 does not carry the detail level (chief architect, 2026-09-24, ADR-0087).
42
+ * - off-palette surfaces: the material has no surface field, so there is nothing to count;
43
+ * - part count by detail level: does not carry the detail level (chief architect, 2026-09-24, ADR-0087).
44
44
  */
45
45
 
46
46
  /*
47
- The grade, its budgets and its bounds are figure-model's (`v3ScoreOf`), so the catalogue grades a figure as this tab
47
+ The grade, its budgets and its bounds are figure-model's (`figureScoreOf`), so the catalogue grades a figure as this tab
48
48
  does. The grade is the headroom against one screen's target (`LIMITS.screenInstances`), not a draw-call limit.
49
49
  */
50
- export { V3_BUDGET_KEYS, V3_GRADE_SCALE as GRADE_SCALE, v3GradeOf as gradeOf }
51
- export type { V3BudgetKey, V3BudgetUse, V3Score }
50
+ export { FIGURE_BUDGET_KEYS, FIGURE_GRADE_SCALE as GRADE_SCALE, figureGradeOf as gradeOf }
51
+ export type { FigureBudgetKey, FigureBudgetUse, FigureScore }
52
52
 
53
- export type V3AxisKey = 'scale' | 'consistency' | 'reuse' | 'detail' | 'reactive' | 'readable'
53
+ export type FigureAxisKey = 'scale' | 'consistency' | 'reuse' | 'detail' | 'reactive' | 'readable'
54
54
 
55
55
  /** One maturity axis. */
56
- export interface V3MaturityAxis {
57
- key: V3AxisKey
56
+ export interface FigureMaturityAxis {
57
+ key: FigureAxisKey
58
58
  /** 0–1. Absent when the axis cannot be measured. */
59
59
  ratio?: number
60
60
  reading?: string
@@ -66,7 +66,7 @@ export interface V3MaturityAxis {
66
66
  }
67
67
 
68
68
  /** Instances on one screen, and what each series costs at that count. */
69
- export interface V3CurvePoint {
69
+ export interface FigureCurvePoint {
70
70
  instances: number
71
71
  triangles: number
72
72
  drawCalls: number
@@ -75,16 +75,16 @@ export interface V3CurvePoint {
75
75
  materials: number
76
76
  }
77
77
 
78
- export interface V3Report {
79
- cost: V3Cost
78
+ export interface FigureReport {
79
+ cost: FigureCost
80
80
  capacity: FigureCapacity
81
- score: V3Score
81
+ score: FigureScore
82
82
  /** Distinct shapes and materials among the drawn bodies — the 「그리는 비용」 rows. */
83
83
  distinctShapes: number
84
84
  distinctMaterials: number
85
85
  reuse: { distinct: number; parts: number; saved: number }
86
- axes: V3MaturityAxis[]
87
- curve: V3CurvePoint[]
86
+ axes: FigureMaturityAxis[]
87
+ curve: FigureCurvePoint[]
88
88
  }
89
89
 
90
90
  /** The board target for the scale axis: 500 assets on one drawing (design document §4.15). */
@@ -94,27 +94,27 @@ export const BOARD_ASSETS = 500
94
94
  * The report for one asset, or nothing when no body is drawn — then there is nothing to measure, and the screen
95
95
  * says so rather than drawing zeros.
96
96
  *
97
- * Evaluated at the asset's own design and state defaults, as `v3CostOf` is.
97
+ * Evaluated at the asset's own design and state defaults, as `figureCostOf` is.
98
98
  */
99
- export function v3ReportOf(asset: V3Asset): V3Report | undefined {
100
- const cost = v3CostOf(asset)
99
+ export function figureReportOf(asset: FigureAsset): FigureReport | undefined {
100
+ const cost = figureCostOf(asset)
101
101
  const capacity = capacityFrom(cost.groups, cost.triangles)
102
102
  if (!capacity) return undefined
103
103
 
104
- const drawn = compileV3Asset(asset)
104
+ const drawn = compileFigureAsset(asset)
105
105
  .evaluate()
106
106
  .geometry.filter(body => body.visible !== false)
107
- const plan = compileV3Document(asset.document)
108
- const writers = v3WritersOf(asset)
107
+ const plan = compileFigureDocument(asset.document)
108
+ const writers = figureWritersOf(asset)
109
109
 
110
110
  const shapes = new Set(drawn.map(shapeKeyOf))
111
111
  const materials = new Set(drawn.map(materialKeyOf))
112
112
 
113
- const score = v3ScoreOf(asset)!
113
+ const score = figureScoreOf(asset)!
114
114
 
115
115
  const reuse = { distinct: shapes.size, parts: drawn.length, saved: drawn.length - shapes.size }
116
116
 
117
- const axes: V3MaturityAxis[] = [
117
+ const axes: FigureMaturityAxis[] = [
118
118
  {
119
119
  key: 'scale',
120
120
  // The board target filled is 1; room beyond it is better spent on the other axes, so it does not count more.
@@ -154,10 +154,10 @@ export function v3ReportOf(asset: V3Asset): V3Report | undefined {
154
154
  }
155
155
 
156
156
  /**
157
- * The key things-scene shares a body's geometry by (`figure-v3-geometry.ts` `keyOf`): provider, dimensions to a
157
+ * The key things-scene shares a body's geometry by (`figure-geometry.ts` `keyOf`): provider, dimensions to a
158
158
  * micrometre, segments (12 when unset), and a polygon's corners.
159
159
  */
160
- export function shapeKeyOf(body: V3AssetGeometry | V3Geometry): string {
160
+ export function shapeKeyOf(body: FigureAssetGeometry | FigureGeometry): string {
161
161
  if (!('shape' in body)) {
162
162
  // A scalar body has no mesh of its own; it is still one drawn body, told apart by what it declares.
163
163
  const dims = Object.keys(body.dimensions).sort().map(k => `${k}=${body.dimensions[k]}`).join(',')
@@ -175,21 +175,21 @@ export function shapeKeyOf(body: V3AssetGeometry | V3Geometry): string {
175
175
  }
176
176
 
177
177
  /**
178
- * The key things-scene shares a material by (`figure-v3-rig.ts` `materialFor`), within one asset: token, preset,
178
+ * The key things-scene shares a material by (`figure-rig.ts` `materialFor`), within one asset: token, preset,
179
179
  * transparency, and the light when it is on. The colour follows from the token, so it is not a second key.
180
180
  */
181
- export function materialKeyOf(body: V3AssetGeometry): string {
182
- return v3MaterialKeyOf(body)
181
+ export function materialKeyOf(body: FigureAssetGeometry): string {
182
+ return figureMaterialKeyOf(body)
183
183
  }
184
184
 
185
185
  /**
186
186
  * Does it look like one set — baselines 3 and 4, three checks.
187
187
  *
188
- * Two of them hold by construction in V3: the material has no surface field, so there is no texture, and the
189
- * format refuses a segment count outside the presets (`compileV3Asset`, `QUALITY`). They are still counted, so
188
+ * Two of them hold by construction: the material has no surface field, so there is no texture, and the
189
+ * format refuses a segment count outside the presets (`compileFigureAsset`, `QUALITY`). They are still counted, so
190
190
  * the ratio stays one fault in three.
191
191
  */
192
- function consistency(asset: V3Asset, distinctMaterials: number): V3MaturityAxis {
192
+ function consistency(asset: FigureAsset, distinctMaterials: number): FigureMaturityAxis {
193
193
  const textures = 0
194
194
  const offPreset = asset.appearance.filter(look => look.segments !== undefined && !SEGMENT_PRESETS.includes(look.segments)).length
195
195
  const faults = (textures > 0 ? 1 : 0) + (offPreset > 0 ? 1 : 0) + (distinctMaterials > LIMITS.distinctMaterials ? 1 : 0)
@@ -201,7 +201,7 @@ function consistency(asset: V3Asset, distinctMaterials: number): V3MaturityAxis
201
201
  }
202
202
  }
203
203
 
204
- /** Seen from above, these do not fill their footprint box, by V3 provider name. */
204
+ /** Seen from above, these do not fill their footprint box, by provider name. */
205
205
  const ROUND_FOOTPRINT: readonly string[] = ['cylinder', 'ellipticCylinder', 'frustum', 'sphere']
206
206
 
207
207
  /**
@@ -211,20 +211,20 @@ const ROUND_FOOTPRINT: readonly string[] = ['cylinder', 'ellipticCylinder', 'fru
211
211
  * finer segment count does not raise it: H = −Σ p·log p over each material's share of the footprint plus the
212
212
  * background, divided by log(`LIMITS.distinctMaterials` + 1), the most this measure can reach.
213
213
  *
214
- * The footprint is the base box: the space the asset declares when it declares one (ADR-0044's box is V3's
214
+ * The footprint is the base box: the space the asset declares when it declares one (ADR-0044's box is the model's
215
215
  * occupancy), otherwise the drawn bodies' own extent. Bodies are their axis-aligned world boxes
216
- * (`v3WorldBoxesOf`, the gate's reading); the higher one covers the lower.
216
+ * (`figureWorldBoxesOf`, the gate's reading); the higher one covers the lower.
217
217
  */
218
218
  function readable(
219
- asset: V3Asset,
220
- plan: ReturnType<typeof compileV3Document>,
221
- drawn: V3AssetGeometry[],
219
+ asset: FigureAsset,
220
+ plan: ReturnType<typeof compileFigureDocument>,
221
+ drawn: FigureAssetGeometry[],
222
222
  writers: Map<string, string>
223
- ): V3MaturityAxis {
223
+ ): FigureMaturityAxis {
224
224
  const placed = drawn
225
225
  .filter(body => 'pose' in body)
226
226
  // One placed, drawn body gives exactly one box; boxes are taken one by one to keep each with its material.
227
- .map(body => ({ body, box: v3WorldBoxesOf([body], writers)[0]! }))
227
+ .map(body => ({ body, box: figureWorldBoxesOf([body], writers)[0]! }))
228
228
  if (placed.length === 0) return { key: 'readable', blocked: 'figure.text.maturity-blocked-no-parts' }
229
229
 
230
230
  let frame: { x0: number; x1: number; z0: number; z1: number }
@@ -260,7 +260,7 @@ function readable(
260
260
 
261
261
  /** Which material shows from above, on a 64 × 64 grid over the footprint, over world boxes. */
262
262
  function topViewRegions(
263
- placed: { body: V3AssetGeometry; box: ReturnType<typeof v3WorldBoxesOf>[number] }[],
263
+ placed: { body: FigureAssetGeometry; box: ReturnType<typeof figureWorldBoxesOf>[number] }[],
264
264
  frame: { x0: number; x1: number; z0: number; z1: number }
265
265
  ) {
266
266
  const GRID = 64
@@ -320,13 +320,13 @@ export function decadeSteps(from: number, to: number): number[] {
320
320
 
321
321
  // ─── The trend chart ─────────────────────────────────────────────────────────
322
322
 
323
- export type V3CurveSeries = 'triangles' | 'drawCalls' | 'geometries' | 'materials'
323
+ export type FigureCurveSeries = 'triangles' | 'drawCalls' | 'geometries' | 'materials'
324
324
 
325
325
  /**
326
326
  * The four lines: two that grow with the instance count and two that stay flat — the flat ones are what the
327
327
  * instances share. Materials are dashed and drawn last, so a line under them still shows where the two meet.
328
328
  */
329
- export const CURVE_SERIES: readonly { key: V3CurveSeries; flat: boolean; dashed?: boolean }[] = [
329
+ export const CURVE_SERIES: readonly { key: FigureCurveSeries; flat: boolean; dashed?: boolean }[] = [
330
330
  { key: 'triangles', flat: false },
331
331
  { key: 'drawCalls', flat: false },
332
332
  { key: 'geometries', flat: true },
@@ -336,10 +336,10 @@ export const CURVE_SERIES: readonly { key: V3CurveSeries; flat: boolean; dashed?
336
336
  /** The chart's size. The chart is shown at this size in a sheet; shrunk into the side, its words are unreadable. */
337
337
  export const CURVE_SIZE = { width: 680, height: 380, pad: { top: 52, right: 208, bottom: 44, left: 62 } } as const
338
338
 
339
- export interface V3CurveLayout {
339
+ export interface FigureCurveLayout {
340
340
  xTicks: { x: number; label: string }[]
341
341
  yTicks: { y: number; label: string }[]
342
- lines: { key: V3CurveSeries; d: string; flat: boolean; dashed: boolean; last: number }[]
342
+ lines: { key: FigureCurveSeries; d: string; flat: boolean; dashed: boolean; last: number }[]
343
343
  /** The value the flat lines share when they lie on one another, so the screen can say why one line is missing. */
344
344
  overlap?: number
345
345
  }
@@ -354,7 +354,7 @@ export function shortNumber(v: number): string {
354
354
  }
355
355
 
356
356
  /** Where the chart puts each line and tick. Both axes are logarithmic. */
357
- export function curveLayout(points: V3CurvePoint[]): V3CurveLayout | undefined {
357
+ export function curveLayout(points: FigureCurvePoint[]): FigureCurveLayout | undefined {
358
358
  if (points.length === 0) return undefined
359
359
  const { width, height, pad } = CURVE_SIZE
360
360
  const minX = points[0]!.instances
@@ -369,9 +369,9 @@ export function curveLayout(points: V3CurvePoint[]): V3CurveLayout | undefined {
369
369
  const px = (v: number) => pad.left + ((log10(v) - log10(minX)) / spanX) * plotW
370
370
  const py = (v: number) => pad.top + plotH - (log10(v) / spanY) * plotH
371
371
 
372
- const yTicks: V3CurveLayout['yTicks'] = []
372
+ const yTicks: FigureCurveLayout['yTicks'] = []
373
373
  for (let e = 0; Math.pow(10, e) <= maxY * 1.001; e++) yTicks.push({ y: py(Math.pow(10, e)), label: shortNumber(Math.pow(10, e)) })
374
- const xTicks: V3CurveLayout['xTicks'] = []
374
+ const xTicks: FigureCurveLayout['xTicks'] = []
375
375
  for (let e = 0; Math.pow(10, e) <= maxX * 1.001; e++) {
376
376
  const v = Math.pow(10, e)
377
377
  if (v >= minX) xTicks.push({ x: px(v), label: shortNumber(v) })
@@ -390,7 +390,7 @@ export function curveLayout(points: V3CurvePoint[]): V3CurveLayout | undefined {
390
390
  }
391
391
 
392
392
  /** What the parts list shows under its title: the cost, how many fit, the grade and what decides it. */
393
- export interface V3BudgetLine {
393
+ export interface FigureBudgetLine {
394
394
  triangles: number
395
395
  groups: number
396
396
  instances: number
@@ -398,7 +398,7 @@ export interface V3BudgetLine {
398
398
  tightest: { key: string; used: number; limit: number }
399
399
  }
400
400
 
401
- export function budgetOf(report: V3Report | undefined): V3BudgetLine | undefined {
401
+ export function budgetOf(report: FigureReport | undefined): FigureBudgetLine | undefined {
402
402
  if (!report) return undefined
403
403
  const { tightest } = report.score
404
404
  return {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The one modeller. The catalogue, a new figure and an old address all open the same page. `figure-v3-modeller`
2
+ * The one modeller. The catalogue, a new figure and an old address all open the same page. `figure-modeller`
3
3
  * stays as an alias so a link written under that name still lands.
4
4
  */
5
5
  export const MODELLER = 'figure-modeller'
@@ -1,9 +1,9 @@
1
- import { parseV3Asset, proposeV3Occupancy, type V3AuthoringAction } from '@hatiolab/figure-model'
1
+ import { parseFigureAsset, proposeFigureOccupancy, type FigureAuthoringAction } from '@hatiolab/figure-model'
2
2
 
3
3
  /**
4
- * 「부품에 맞추기」의 V3 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.
4
+ * 「부품에 맞추기」의 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.
5
5
  *
6
- * figure-model 은 이것을 둘로 나눠 두었다: 제안(`proposeV3Occupancy`)은 무엇이 선언될지를 보여 주고, 확인
6
+ * figure-model 은 이것을 둘로 나눠 두었다: 제안(`proposeFigureOccupancy`)은 무엇이 선언될지를 보여 주고, 확인
7
7
  * (`declare-occupancy`)은 그 제안이 읽은 모양 그대로일 때만 받는다. 저작자가 누른 단추가 그 확인이다. 제안과 확인
8
8
  * 사이에 모델이 바뀌면 figure-model 이 `OCCUPANCY_STALE` 로 거절하므로, 여기서는 누른 순간의 원본으로 둘을 한
9
9
  * 번에 만든다.
@@ -16,11 +16,11 @@ export function occupancyFromParts(
16
16
  source: string,
17
17
  /** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */
18
18
  placement?: 'floor' | 'ceiling' | 'center' | 'wall'
19
- ): Extract<V3AuthoringAction, { kind: 'declare-occupancy' }> {
20
- const asset = parseV3Asset(source)
19
+ ): Extract<FigureAuthoringAction, { kind: 'declare-occupancy' }> {
20
+ const asset = parseFigureAsset(source)
21
21
  return {
22
22
  kind: 'declare-occupancy',
23
- proposal: proposeV3Occupancy(asset, { over: 'range' }),
23
+ proposal: proposeFigureOccupancy(asset, { over: 'range' }),
24
24
  placement: placement ?? asset.occupancy?.placement ?? 'floor'
25
25
  }
26
26
  }
@@ -1,12 +1,12 @@
1
- import type { V3PrimitiveCreation } from '@hatiolab/figure-model'
1
+ import type { FigurePrimitiveCreation } from '@hatiolab/figure-model'
2
2
 
3
- import type { V3PaletteKind } from './figure-v3-parts.js'
3
+ import type { FigurePaletteKind } from './figure-parts.js'
4
4
 
5
5
  /**
6
6
  * What a palette button makes: the part at a starting size a person then changes. Standing on the floor (its lowest
7
7
  * point at y 0) where the provider's frame is centred; a portal's frame already rises from its bottom.
8
8
  */
9
- export function paletteRecipe(kind: V3PaletteKind, id: string): V3PrimitiveCreation {
9
+ export function paletteRecipe(kind: FigurePaletteKind, id: string): FigurePrimitiveCreation {
10
10
  const standing = (height: number) => ({ id, position: { x: 0, y: height / 2, z: 0 } })
11
11
  switch (kind) {
12
12
  case 'box':
@@ -1,7 +1,7 @@
1
- import { compileV3Asset, v3OccupiedBoxesOf, v3WritersOf, V3_INSTANCE_SIZE_INPUTS } from '@hatiolab/figure-model'
2
- import type { V3Asset } from '@hatiolab/figure-model'
1
+ import { compileFigureAsset, figureOccupiedBoxesOf, figureWritersOf, FIGURE_INSTANCE_SIZE_INPUTS } from '@hatiolab/figure-model'
2
+ import type { FigureAsset } from '@hatiolab/figure-model'
3
3
 
4
- /* The preview's arithmetic, apart from the element so it is tested without a browser (see figure-v3-preview.ts). */
4
+ /* The preview's arithmetic, apart from the element so it is tested without a browser (see figure-preview.ts). */
5
5
 
6
6
  export const PREVIEW_FACTOR = 2
7
7
 
@@ -17,12 +17,12 @@ export const PREVIEW_WAYS: readonly { name: string; axes: readonly ('x' | 'y' |
17
17
  ]
18
18
 
19
19
  /** The size the figure was made at: its instance size when it declares one, else the room its bodies take. */
20
- export function authoredSizeOf(asset: V3Asset): { x: number; y: number; z: number } {
20
+ export function authoredSizeOf(asset: FigureAsset): { x: number; y: number; z: number } {
21
21
  const d = asset.designInputs
22
- const declared = (['x', 'y', 'z'] as const).map(axis => d[V3_INSTANCE_SIZE_INPUTS[axis]])
22
+ const declared = (['x', 'y', 'z'] as const).map(axis => d[FIGURE_INSTANCE_SIZE_INPUTS[axis]])
23
23
  if (declared.every(v => typeof v === 'number' && v > 0)) return { x: declared[0]!, y: declared[1]!, z: declared[2]! }
24
24
  /* No declared size: the room its bodies take up, so the instance is placed at the size it draws. */
25
- const boxes = v3OccupiedBoxesOf(compileV3Asset(asset).evaluate().geometry, v3WritersOf(asset))
25
+ const boxes = figureOccupiedBoxesOf(compileFigureAsset(asset).evaluate().geometry, figureWritersOf(asset))
26
26
  const span = (axis: 'x' | 'y' | 'z') =>
27
27
  boxes.length ? Math.max(...boxes.map(b => b.centre[axis] + b.extent[axis] / 2)) - Math.min(...boxes.map(b => b.centre[axis] - b.extent[axis] / 2)) : 0
28
28
  return { x: Math.max(1, Math.round(span('x'))), y: Math.max(1, Math.round(span('y'))), z: Math.max(1, Math.round(span('z'))) }
@@ -59,13 +59,13 @@ export function previewInstances(type: string, size: { x: number; y: number; z:
59
59
  * it is drawn as asked. A figure that follows its height only is drawn at its one width in the wider variants, and
60
60
  * the preview says so rather than drawing them as though they grew.
61
61
  */
62
- export function previewClampsOf(asset: V3Asset, size: { x: number; y: number; z: number }): Record<string, Array<{ axis: 'x' | 'y' | 'z'; asked: number; used: number }>> {
62
+ export function previewClampsOf(asset: FigureAsset, size: { x: number; y: number; z: number }): Record<string, Array<{ axis: 'x' | 'y' | 'z'; asked: number; used: number }>> {
63
63
  const inputs = asset.document.model.inputs
64
64
  return Object.fromEntries(
65
65
  PREVIEW_WAYS.map(way => [
66
66
  way.name,
67
67
  (['x', 'y', 'z'] as const).flatMap(axis => {
68
- const input = inputs.find(i => i.id === V3_INSTANCE_SIZE_INPUTS[axis])
68
+ const input = inputs.find(i => i.id === FIGURE_INSTANCE_SIZE_INPUTS[axis])
69
69
  if (!input) return []
70
70
  const asked = Math.round(size[axis] * (way.axes.includes(axis) ? PREVIEW_FACTOR : 1))
71
71
  const used = Math.round(Math.min(Math.max(asked, input.min ?? -Infinity), input.max ?? Infinity))
@@ -1,23 +1,23 @@
1
1
  import {
2
- createV3Asset,
3
- createV3EditorWorkspace,
4
- serializeV3Asset,
5
- parseV3Asset,
6
- type V3InputChange,
7
- type V3PrimitiveCreation,
8
- type V3PartEdit,
9
- type V3AuthoringAction,
10
- type V3ProposalStep
2
+ createFigureAsset,
3
+ createFigureEditorWorkspace,
4
+ serializeFigureAsset,
5
+ parseFigureAsset,
6
+ type FigureInputChange,
7
+ type FigurePrimitiveCreation,
8
+ type FigurePartEdit,
9
+ type FigureAuthoringAction,
10
+ type FigureProposalStep
11
11
  } from '@hatiolab/figure-model'
12
12
 
13
- export interface V3ProductRecord {
13
+ export interface FigureProductRecord {
14
14
  id: string
15
15
  source: string
16
16
  }
17
17
 
18
18
  /** Persistence is injected; neither the model engine nor the renderer owns the product ID. */
19
- export interface V3ProductStore {
20
- load(id: string): Promise<V3ProductRecord>
19
+ export interface FigureProductStore {
20
+ load(id: string): Promise<FigureProductRecord>
21
21
  save(request: {
22
22
  id?: string
23
23
  source: string
@@ -27,12 +27,12 @@ export interface V3ProductStore {
27
27
  thumbnail?: string
28
28
  /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */
29
29
  type?: string
30
- }): Promise<V3ProductRecord>
30
+ }): Promise<FigureProductRecord>
31
31
  }
32
32
 
33
33
  /** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
34
- export function createV3ProductSession(store: V3ProductStore) {
35
- const workspace = createV3EditorWorkspace()
34
+ export function createFigureProductSession(store: FigureProductStore) {
35
+ const workspace = createFigureEditorWorkspace()
36
36
  let epoch = 0,
37
37
  id: string | undefined,
38
38
  baseline: string | undefined
@@ -69,7 +69,7 @@ export function createV3ProductSession(store: V3ProductStore) {
69
69
  ++epoch
70
70
  clear()
71
71
  loading = false
72
- workspace.open(serializeV3Asset(createV3Asset('새 Figure')))
72
+ workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')))
73
73
  creationId = crypto.randomUUID()
74
74
  return read()
75
75
  },
@@ -99,7 +99,7 @@ export function createV3ProductSession(store: V3ProductStore) {
99
99
  loading = false
100
100
  return read()
101
101
  },
102
- apply(changes: V3InputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {
102
+ apply(changes: FigureInputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {
103
103
  const control = writable()
104
104
  // AI must supply the context AND revision captured when it started. Never substitute current values.
105
105
  if (origin === 'ai' && (context === undefined || expectedRevision === undefined))
@@ -116,7 +116,7 @@ export function createV3ProductSession(store: V3ProductStore) {
116
116
  * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
117
117
  * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
118
118
  */
119
- applyProposal(steps: readonly V3ProposalStep[], expectedRevision: number, context: string) {
119
+ applyProposal(steps: readonly FigureProposalStep[], expectedRevision: number, context: string) {
120
120
  writable()
121
121
  return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps })
122
122
  },
@@ -124,18 +124,18 @@ export function createV3ProductSession(store: V3ProductStore) {
124
124
  * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open
125
125
  * now. The same path as a proposal; the person is its origin.
126
126
  */
127
- applyRecipe(steps: readonly V3ProposalStep[]) {
127
+ applyRecipe(steps: readonly FigureProposalStep[]) {
128
128
  const at = writable()
129
129
  return workspace.applyProposal({ origin: 'ui', context: at.context, expectedRevision: at.expectedRevision, steps })
130
130
  },
131
- add(part: V3PrimitiveCreation) {
131
+ add(part: FigurePrimitiveCreation) {
132
132
  return workspace.addPrimitive({ ...writable(), part })
133
133
  },
134
- edit(edit: V3PartEdit) {
134
+ edit(edit: FigurePartEdit) {
135
135
  return workspace.editPart({ ...writable(), edit })
136
136
  },
137
137
  /** One authoring command — what a person means, not a graph edit — as one step of the same history. */
138
- command(action: V3AuthoringAction) {
138
+ command(action: FigureAuthoringAction) {
139
139
  return workspace.command({ ...writable(), action })
140
140
  },
141
141
  undo() {
@@ -153,7 +153,7 @@ export function createV3ProductSession(store: V3ProductStore) {
153
153
  *
154
154
  * The picture is the caller's because taking it needs a canvas, and this session is the place that knows
155
155
  * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the
156
- * source matters more than the card (see `v3-thumbnail.ts`).
156
+ * source matters more than the card (see `thumbnail.ts`).
157
157
  */
158
158
  async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {
159
159
  const control = writable(),
@@ -164,7 +164,7 @@ export function createV3ProductSession(store: V3ProductStore) {
164
164
  const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, ...(id || !type ? {} : { type }) })
165
165
  if (requestEpoch !== epoch) return read()
166
166
  if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')
167
- if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)
167
+ if (serializeFigureAsset(parseFigureAsset(record.source)) !== exported.text)
168
168
  throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')
169
169
  workspace.acknowledgeSaved(exported)
170
170
  id = record.id
@@ -176,4 +176,4 @@ export function createV3ProductSession(store: V3ProductStore) {
176
176
  }
177
177
  }
178
178
  }
179
- export type V3ProductSession = ReturnType<typeof createV3ProductSession>
179
+ export type FigureProductSession = ReturnType<typeof createFigureProductSession>
@@ -1,9 +1,9 @@
1
1
  import { fetchRecipeGuide, fetchRecipeTemplates } from '../graphql/index.js'
2
- import { templatesOf, type RecipeTemplate } from './v3-recipes.js'
2
+ import { templatesOf, type RecipeTemplate } from './recipes.js'
3
3
 
4
4
  /**
5
5
  * Where the modeller gets its recipe templates: the server's static files (ADR-0095, amended 2026-09-26). The one place
6
- * they are read -- the rules in `v3-recipes.ts` stay free of the network so they are tested without it.
6
+ * they are read -- the rules in `recipes.ts` stay free of the network so they are tested without it.
7
7
  */
8
8
  export async function availableRecipes(lang: string): Promise<RecipeTemplate[]> {
9
9
  return templatesOf(await fetchRecipeTemplates(lang))
@@ -1,11 +1,11 @@
1
- import { describeV3Recipe } from '@hatiolab/figure-model'
2
- import type { V3Recipe, V3RecipeParam, V3RecipeValue } from '@hatiolab/figure-model'
1
+ import { describeFigureRecipe } from '@hatiolab/figure-model'
2
+ import type { FigureRecipe, FigureRecipeParam, FigureRecipeValue } from '@hatiolab/figure-model'
3
3
 
4
4
  import type { RecipeTemplateRow } from '../graphql/index.js'
5
5
 
6
6
  /** A template in the catalog: the server's row, its recipe parsed. */
7
7
  export interface RecipeTemplate extends Omit<RecipeTemplateRow, 'source'> {
8
- recipe: V3Recipe
8
+ recipe: FigureRecipe
9
9
  }
10
10
 
11
11
  /*
@@ -30,11 +30,11 @@ export function templatesOf(rows: ReadonlyArray<RecipeTemplateRow>): RecipeTempl
30
30
  export const recipePartName = (part: string) => part.replace(/^\{id\}-/, '')
31
31
 
32
32
  /**
33
- * How a template works, in lines the catalog shows -- worked out from its steps (figure-model `describeV3Recipe`), each
33
+ * How a template works, in lines the catalog shows -- worked out from its steps (figure-model `describeFigureRecipe`), each
34
34
  * a translation key and its words.
35
35
  */
36
- export function recipeWorkings(recipe: V3Recipe): Array<{ key: string; words: Record<string, string | number> }> {
37
- const d = describeV3Recipe(recipe)
36
+ export function recipeWorkings(recipe: FigureRecipe): Array<{ key: string; words: Record<string, string | number> }> {
37
+ const d = describeFigureRecipe(recipe)
38
38
  const lines: Array<{ key: string; words: Record<string, string | number> }> = []
39
39
  for (const m of d.moves)
40
40
  lines.push(
@@ -55,7 +55,7 @@ export function recipeWord(words: Record<string, string> | undefined, language:
55
55
  }
56
56
 
57
57
  /** Whether a recipe can be put in now: one for a part needs a part chosen. */
58
- export function recipeReady(recipe: Pick<V3Recipe, 'target'>, selected: string): boolean {
58
+ export function recipeReady(recipe: Pick<FigureRecipe, 'target'>, selected: string): boolean {
59
59
  return recipe.target === 'figure' || !!selected
60
60
  }
61
61
 
@@ -63,7 +63,7 @@ export function recipeReady(recipe: Pick<V3Recipe, 'target'>, selected: string):
63
63
  * A field's value as the recipe takes it. A ratio is shown and typed as a percentage (35 for 0.35), since that is how a
64
64
  * person says a share; the rest are taken as they are. A number field left empty or not a number keeps the default.
65
65
  */
66
- export function recipeValueOf(param: V3RecipeParam, typed: string | boolean): V3RecipeValue {
66
+ export function recipeValueOf(param: FigureRecipeParam, typed: string | boolean): FigureRecipeValue {
67
67
  if (param.kind === 'flag') return typed === true || typed === 'true'
68
68
  if (param.kind === 'ratio' || param.kind === 'mm') {
69
69
  const n = typeof typed === 'string' && typed.trim() !== '' ? Number(typed) : NaN
@@ -75,7 +75,7 @@ export function recipeValueOf(param: V3RecipeParam, typed: string | boolean): V3
75
75
  }
76
76
 
77
77
  /** What a field shows for a value: a ratio as a percentage. */
78
- export function recipeFieldOf(param: V3RecipeParam, value: V3RecipeValue): string {
78
+ export function recipeFieldOf(param: FigureRecipeParam, value: FigureRecipeValue): string {
79
79
  if (param.kind === 'ratio' && typeof value === 'number') return String(Math.round(value * 1e6) / 1e4)
80
80
  return String(value)
81
81
  }