@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
@@ -4,41 +4,41 @@ import type { OxThreePane } from '@operato/layout'
4
4
  import '@operato/layout'
5
5
  import { customElement, property, state } from 'lit/decorators.js'
6
6
  import { live } from 'lit/directives/live.js'
7
- import { PART_LIMIT, parseV3Recipe, resolveV3Recipe, v3PartCountOf, v3RecipeIdFor } from '@hatiolab/figure-model'
8
- import type { RecipeTemplate } from './v3-recipes.js'
9
- import './figure-v3-recipe-catalog.js'
10
- import type { V3Asset, V3AuthoringAction, V3InputChange, V3PartEdit, V3PrimitiveCreation, V3ProposalStep, V3Recipe, V3RecipeValue } from '@hatiolab/figure-model'
11
- import type { V3ProductSession } from './v3-product-session.js'
12
- import { captureV3Thumbnail } from './v3-thumbnail.js'
13
- import { drawableV3Parts } from './v3-renderer.js'
14
- import './v3-canvas.js'
15
- import './figure-v3-parts.js'
16
- import './figure-v3-material.js'
17
- import './figure-v3-links.js'
18
- import './figure-v3-controls.js'
19
- import './figure-v3-clips.js'
20
- import './figure-v3-preview.js'
7
+ import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor } from '@hatiolab/figure-model'
8
+ import type { RecipeTemplate } from './recipes.js'
9
+ import './figure-recipe-catalog.js'
10
+ import type { FigureAsset, FigureAuthoringAction, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue } from '@hatiolab/figure-model'
11
+ import type { FigureProductSession } from './product-session.js'
12
+ import { captureFigureThumbnail } from './thumbnail.js'
13
+ import { drawableFigureParts } from './renderer.js'
14
+ import './canvas.js'
15
+ import './figure-parts.js'
16
+ import './figure-material-panel.js'
17
+ import './figure-links.js'
18
+ import './figure-controls.js'
19
+ import './figure-clips.js'
20
+ import './figure-preview.js'
21
21
  import './figure-settings.js'
22
22
  import { DEFAULT_VIEW, type ViewSettings } from './figure-view.js'
23
- import './figure-v3-capability.js'
24
- import { gizmoChanges, gizmoReachOf, keepBottomChanges, type GizmoReach, type GizmoResult } from './v3-gizmo.js'
25
- import { keyActionOf } from './v3-keys.js'
26
- import { groupFields, ownerOf, partLabelOf, repeatOf, type FieldLike, type FieldGroup } from './v3-fields.js'
23
+ import './figure-capability-panel.js'
24
+ import { gizmoChanges, gizmoReachOf, keepBottomChanges, type GizmoReach, type GizmoResult } from './gizmo.js'
25
+ import { keyActionOf } from './keys.js'
26
+ import { groupFields, ownerOf, partLabelOf, repeatOf, type FieldLike, type FieldGroup } from './fields.js'
27
27
  import { SidePanelStyles } from './side-panel-styles.js'
28
28
  import { ScrollbarStyles } from '@operato/styles'
29
- import { alignChanges, boxOf, canMirror, mirrorChanges, type AlignTool } from './v3-align.js'
30
- import type { V3PaletteKind, V3PartRow } from './figure-v3-parts.js'
31
- import { paletteRecipe } from './v3-palette.js'
32
- import type { FigureV3Canvas } from './v3-canvas.js'
33
- import { nudgeOf } from './v3-stage-model.js'
34
- import { inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, sizeRangeRefusalOf } from './v3-refusals.js'
35
- import type { V3RenderPart } from './v3-renderer.js'
36
- import type { V3BudgetLine } from './v3-maturity.js'
37
- import { instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './v3-links.js'
38
- import { jointFanOf } from './v3-joint-fan.js'
39
- import { jointDragOf } from './v3-joint-drag.js'
40
- import type { FirstSave } from './v3-first-save.js'
41
- import { isLive, livePartsOf, liveOverSession, NOTHING_LIVE, scrubbed, scrubStep, type LiveValues } from './v3-live.js'
29
+ import { alignChanges, boxOf, canMirror, mirrorChanges, type AlignTool } from './align.js'
30
+ import type { FigurePaletteKind, FigurePartRow } from './figure-parts.js'
31
+ import { paletteRecipe } from './palette.js'
32
+ import type { FigureCanvas } from './canvas.js'
33
+ import { nudgeOf } from './stage-model.js'
34
+ import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, sizeRangeRefusalOf } from './refusals.js'
35
+ import type { FigureRenderPart } from './renderer.js'
36
+ import type { FigureBudgetLine } from './maturity.js'
37
+ import { instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js'
38
+ import { jointFanOf } from './joint-fan.js'
39
+ import { jointDragOf } from './joint-drag.js'
40
+ import type { FirstSave } from './first-save.js'
41
+ import { isLive, livePartsOf, liveOverSession, NOTHING_LIVE, scrubbed, scrubStep, type LiveValues } from './live.js'
42
42
 
43
43
 
44
44
  /**
@@ -48,8 +48,8 @@ import { isLive, livePartsOf, liveOverSession, NOTHING_LIVE, scrubbed, scrubStep
48
48
  type SideTab = 'properties' | 'capability' | 'motion' | 'verdict' | 'view' | 'history'
49
49
 
50
50
  /** Product editor surface. The injected session is shared by page persistence and AI commands. */
51
- @customElement('figure-v3-editor')
52
- export class FigureV3Editor extends LitElement {
51
+ @customElement('figure-editor')
52
+ export class FigureEditor extends LitElement {
53
53
  /* The operato scrollbar, as every scrolling panel of the app has it -- the side panel scrolled with the browser's own. */
54
54
  static styles = [ScrollbarStyles, SidePanelStyles, css`
55
55
  :host {
@@ -105,7 +105,7 @@ export class FigureV3Editor extends LitElement {
105
105
  opacity: 0.38;
106
106
  cursor: default;
107
107
  }
108
- figure-v3-preview {
108
+ figure-preview {
109
109
  min-width: 0;
110
110
  min-height: 0;
111
111
  }
@@ -131,10 +131,10 @@ export class FigureV3Editor extends LitElement {
131
131
  flex: 1;
132
132
  min-height: 0;
133
133
  }
134
- figure-v3-parts {
134
+ figure-parts {
135
135
  min-height: 0;
136
136
  }
137
- figure-v3-canvas {
137
+ figure-canvas {
138
138
  min-width: 0;
139
139
  min-height: 0;
140
140
  }
@@ -263,7 +263,7 @@ export class FigureV3Editor extends LitElement {
263
263
  line-height: 1.5;
264
264
  }
265
265
  `]
266
- @property({ attribute: false }) session?: V3ProductSession
266
+ @property({ attribute: false }) session?: FigureProductSession
267
267
  /** The recipe templates (ADR-0095), read by the page from the server. */
268
268
  @property({ attribute: false }) templates: RecipeTemplate[] = []
269
269
  /** Reads a template's guide; given by the page. */
@@ -278,7 +278,7 @@ export class FigureV3Editor extends LitElement {
278
278
  /** The verdict's count, drawn on its tab so it stays in sight while another tab is open. */
279
279
  @property({ type: Number }) instances?: number
280
280
  /** The cost line the parts list shows, from the maturity report. */
281
- @property({ attribute: false }) budget?: V3BudgetLine
281
+ @property({ attribute: false }) budget?: FigureBudgetLine
282
282
  /**
283
283
  * Asks the type and name of a figure saved for the first time (the host has the catalogue's names); undefined
284
284
  * when the author cancels, and then nothing is saved. Without it a new figure is saved under a temporary type.
@@ -287,7 +287,7 @@ export class FigureV3Editor extends LitElement {
287
287
  @state() private selected = ''
288
288
  @state() private error = ''
289
289
  /** The same link offered again with the size range narrowed, after a link the declared range could not hold. */
290
- @state() private retry?: { label: string; action?: V3AuthoringAction; run?: () => void }
290
+ @state() private retry?: { label: string; action?: FigureAuthoringAction; run?: () => void }
291
291
  @state() private tab: SideTab = 'properties'
292
292
  /** The frame the bar folds - found after the first render, since the bar is drawn before it. */
293
293
  @state() private panes?: OxThreePane
@@ -301,10 +301,10 @@ export class FigureV3Editor extends LitElement {
301
301
  * How the workbench shows the figure, in the board's own state names. The viewer's, kept in
302
302
  * their browser, never in the figure.
303
303
  */
304
- @state() private view: ViewSettings = FigureV3Editor.storedView()
304
+ @state() private view: ViewSettings = FigureEditor.storedView()
305
305
  private static storedView(): ViewSettings {
306
306
  try {
307
- const stored = JSON.parse(localStorage.getItem('figure-v3-view') ?? 'null')
307
+ const stored = JSON.parse(localStorage.getItem('figure-view') ?? 'null')
308
308
  return stored && typeof stored === 'object' ? { ...DEFAULT_VIEW, ...stored } : { ...DEFAULT_VIEW }
309
309
  } catch {
310
310
  return { ...DEFAULT_VIEW }
@@ -313,7 +313,7 @@ export class FigureV3Editor extends LitElement {
313
313
  private setView(view: ViewSettings) {
314
314
  this.view = { ...DEFAULT_VIEW, ...view }
315
315
  try {
316
- localStorage.setItem('figure-v3-view', JSON.stringify(this.view))
316
+ localStorage.setItem('figure-view', JSON.stringify(this.view))
317
317
  } catch {
318
318
  /* A browser that keeps nothing still shows the change for this visit. */
319
319
  }
@@ -353,11 +353,11 @@ export class FigureV3Editor extends LitElement {
353
353
  )}
354
354
  </span>`
355
355
  }
356
- /** The modeller's keys (`v3-keys.ts`): undo, redo, duplicate, remove, back to the whole figure, the gizmo's modes. */
356
+ /** The modeller's keys (`keys.ts`): undo, redo, duplicate, remove, back to the whole figure, the gizmo's modes. */
357
357
  private onKey = (e: KeyboardEvent) => {
358
358
  const view = this.session?.read()
359
359
  if (!view || view.loading || view.saving || !view.context || !this.isConnected) return
360
- const canvas = this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')
360
+ const canvas = this.renderRoot.querySelector<FigureCanvas>('figure-canvas')
361
361
  const path = e.composedPath()
362
362
  const action = keyActionOf(
363
363
  { key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey, target: path[0] as HTMLElement, inScene: !!canvas && path.includes(canvas) },
@@ -399,32 +399,41 @@ export class FigureV3Editor extends LitElement {
399
399
  }
400
400
  /** The scene toggles: the mode already shown takes the gizmo away. */
401
401
  private useGizmo(mode: 'translate' | 'rotate' | 'scale') {
402
- this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')?.gizmo(mode)
402
+ this.renderRoot.querySelector<FigureCanvas>('figure-canvas')?.gizmo(mode)
403
403
  this.gizmoMode = this.gizmoMode === mode ? undefined : mode
404
404
  }
405
+ /**
406
+ * A refusal no particular sentence knows: figure-model's code said as a refusal, never its English; any other error
407
+ * (the network, the server) as it came.
408
+ */
409
+ private said(error: unknown): string {
410
+ return (error as { code?: string })?.code ? editRefusalOf(error, (key, params) => i18next.t(key, params)) : error instanceof Error ? error.message : String(error)
411
+ }
405
412
  private run(action: () => unknown) {
406
413
  try {
407
414
  action()
408
415
  this.error = ''
416
+ /* The next step stands on its own: what the last removal let go of is no longer the news. */
417
+ this.notice = ''
409
418
  this.changed()
410
419
  } catch (error) {
411
420
  const say = (key: string, params: Record<string, string>) => i18next.t(key, params)
412
- this.error = sizeRangeRefusalOf(error, say) ?? inputRangeRefusalOf(error, say) ?? (error instanceof Error ? error.message : String(error))
421
+ this.error = sizeRangeRefusalOf(error, say) ?? inputRangeRefusalOf(error, say) ?? this.said(error)
413
422
  } finally {
414
423
  /* Committed or refused, the value in hand is gone: the figure as it stands is drawn again. */
415
424
  this.live = NOTHING_LIVE
416
425
  }
417
426
  }
418
427
  /** What the canvas draws: the figure with the value in hand when there is one, and without the parts hidden on the workbench. */
419
- private drawnParts(geometry: Parameters<typeof drawableV3Parts>[0] | undefined, asset: unknown) {
420
- const parts = (asset ? liveOverSession(asset as V3Asset, this.currentInputs(), this.live) : null) ?? drawableV3Parts(geometry ?? [])
428
+ private drawnParts(geometry: Parameters<typeof drawableFigureParts>[0] | undefined, asset: unknown) {
429
+ const parts = (asset ? liveOverSession(asset as FigureAsset, this.currentInputs(), this.live) : null) ?? drawableFigureParts(geometry ?? [])
421
430
  return parts.filter(body => !this.hiddenParts.has(ownerOf((body as { id: string }).id)))
422
431
  }
423
432
  /**
424
433
  * Where the selected part turns and how far, with the pose as it is drawn now -- a value in hand included, so the
425
- * fan follows the arm while a control is dragged. Its size comes from what the joint turns (`v3-joint-fan`).
434
+ * fan follows the arm while a control is dragged. Its size comes from what the joint turns (`joint-fan`).
426
435
  */
427
- private fanOf(asset: V3Asset | undefined) {
436
+ private fanOf(asset: FigureAsset | undefined) {
428
437
  if (!asset || !this.selected || this.hiddenParts.has(this.selected)) return null
429
438
  try {
430
439
  return jointFanOf(asset, this.selected, { ...this.currentInputs(), ...this.live.values })
@@ -442,12 +451,12 @@ export class FigureV3Editor extends LitElement {
442
451
  return view ? Object.fromEntries(view.fields.map(field => [field.id, field.value as number])) : {}
443
452
  }
444
453
  /** A value in hand: an input's value (design or pose) or a part edit. Nothing is recorded. */
445
- private hold(values: Record<string, number>, edit?: V3PartEdit) {
454
+ private hold(values: Record<string, number>, edit?: FigurePartEdit) {
446
455
  this.live = { values: { ...this.live.values, ...values }, ...(edit ? { edit } : this.live.edit ? { edit: this.live.edit } : {}) }
447
456
  }
448
457
  private changed() {
449
458
  this.requestUpdate()
450
- this.dispatchEvent(new CustomEvent('v3-changed', { bubbles: true, composed: true }))
459
+ this.dispatchEvent(new CustomEvent('changed', { bubbles: true, composed: true }))
451
460
  }
452
461
  private async save() {
453
462
  if (!this.session) return
@@ -455,7 +464,7 @@ export class FigureV3Editor extends LitElement {
455
464
  /*
456
465
  The card's picture is taken before the save starts, from a canvas of its own at the card's size —
457
466
  not from the one on screen, which stands wherever the author left it turned. A picture that cannot
458
- be taken is not an error: the save goes on without it (see `v3-thumbnail.ts`).
467
+ be taken is not an error: the save goes on without it (see `thumbnail.ts`).
459
468
  */
460
469
  let type: string | undefined
461
470
  if (!this.session.read().id && this.firstSave) {
@@ -469,18 +478,18 @@ export class FigureV3Editor extends LitElement {
469
478
  type = answer.type
470
479
  }
471
480
  const view = this.session.read()
472
- const thumbnail = await captureV3Thumbnail(drawableV3Parts(view.geometry ?? []), view.palette ?? {})
481
+ const thumbnail = await captureFigureThumbnail(drawableFigureParts(view.geometry ?? []), view.palette ?? {})
473
482
  const promise = this.session.save({ thumbnail, type })
474
483
  this.changed()
475
484
  await promise
476
485
  this.error = ''
477
486
  } catch (error) {
478
- this.error = error instanceof Error ? error.message : String(error)
487
+ this.error = this.said(error)
479
488
  } finally {
480
489
  this.changed()
481
490
  }
482
491
  }
483
- private add(kind: V3PaletteKind) {
492
+ private add(kind: FigurePaletteKind) {
484
493
  if (!this.session) return
485
494
  this.run(() => {
486
495
  const ids = new Set(this.session!.read().geometry.map(g => g.id))
@@ -492,7 +501,7 @@ export class FigureV3Editor extends LitElement {
492
501
  })
493
502
  }
494
503
  /**
495
- * The list's own operations, as V3 part edits — each one step of the session's history. The copy and the moved
504
+ * The list's own operations, as part edits — each one step of the session's history. The copy and the moved
496
505
  * part stay selected.
497
506
  */
498
507
  private duplicatePart(id: string) {
@@ -508,13 +517,15 @@ export class FigureV3Editor extends LitElement {
508
517
  private removePart(id: string, refitRoom = false) {
509
518
  this.retry = undefined
510
519
  try {
511
- this.session!.edit({ kind: 'remove-part', target: id, ...(refitRoom ? { refitRoom: true } : {}) })
520
+ const view = this.session!.edit({ kind: 'remove-part', target: id, ...(refitRoom ? { refitRoom: true } : {}) }) as { removal?: Parameters<typeof removalNoticeOf>[0] }
512
521
  this.error = ''
522
+ /* What else the removal let go of, named (chief architect, 2026-09-27); undo brings it back. */
523
+ this.notice = removalNoticeOf(view.removal, (key, params) => i18next.t(key, params)) ?? ''
513
524
  if (this.selected === id) this.selected = ''
514
525
  this.changed()
515
526
  } catch (error) {
516
527
  /* The refusal a person meets most: another part stands on this one or follows its size. Said in their words. */
517
- this.error = partRefusalOf(error, id, (key, params) => i18next.t(key, params)) ?? (error instanceof Error ? error.message : String(error))
528
+ this.error = partRefusalOf(error, id, (key, params) => i18next.t(key, params)) ?? this.said(error)
518
529
  /* In the declared room only: the way on is to delete it and fit the room to the parts left, in one step. */
519
530
  if (!refitRoom && inDeclaredRoom(error)) this.retry = { label: i18next.t('figure.button.remove-and-refit-room', { part: id }), run: () => this.removePart(id, true) }
520
531
  }
@@ -534,19 +545,19 @@ export class FigureV3Editor extends LitElement {
534
545
  * A recipe put in (ADR-0095): resolved on the figure as it stands -- its parts take the next free name, onto the part
535
546
  * chosen -- and applied as one step. Refused, the kernel's sentence goes to the form as it is (condition 3).
536
547
  */
537
- private insertRecipe(recipe: V3Recipe, values: Record<string, V3RecipeValue>) {
548
+ private insertRecipe(recipe: FigureRecipe, values: Record<string, FigureRecipeValue>) {
538
549
  try {
539
- const asset = JSON.parse(this.session!.source()) as V3Asset
540
- const checked = parseV3Recipe(recipe)
541
- const id = v3RecipeIdFor(asset, checked)
542
- const steps = resolveV3Recipe(checked, { id, ...(checked.target === 'part' ? { target: this.selected } : {}), values })
550
+ const asset = JSON.parse(this.session!.source()) as FigureAsset
551
+ const checked = parseFigureRecipe(recipe)
552
+ const id = figureRecipeIdFor(asset, checked)
553
+ const steps = resolveFigureRecipe(checked, { id, ...(checked.target === 'part' ? { target: this.selected } : {}), values })
543
554
  this.session!.applyRecipe(steps)
544
555
  this.recipeError = ''
545
556
  this.catalogOpen = false
546
557
  this.error = ''
547
558
  this.changed()
548
559
  } catch (error) {
549
- this.recipeError = error instanceof Error ? error.message : String(error)
560
+ this.recipeError = recipeRefusalOf(error, (key, params) => i18next.t(key, params)) ?? this.said(error)
550
561
  }
551
562
  }
552
563
  /** The design values of the asset being edited. */
@@ -572,8 +583,8 @@ export class FigureV3Editor extends LitElement {
572
583
  private transformed(id: string, result: GizmoResult) {
573
584
  const view = this.session!.read()
574
585
  /* A part hanging from a joint: the drag edits the joint -- its value, or where it sits (ADR-0093). */
575
- const asset = JSON.parse(this.session!.source()) as V3Asset
576
- const drawn = view.geometry.find(g => g.id === id) as unknown as V3RenderPart | undefined
586
+ const asset = JSON.parse(this.session!.source()) as FigureAsset
587
+ const drawn = view.geometry.find(g => g.id === id) as unknown as FigureRenderPart | undefined
577
588
  const drag = drawn ? jointDragOf(asset, ownerOf(id), drawn, result, this.currentInputs()) : null
578
589
  if (drag) {
579
590
  if (drag.kind === 'value') this.run(() => this.session!.apply([{ kind: 'set-state', input: drag.input, unit: drag.unit, value: drag.value }]))
@@ -607,7 +618,7 @@ export class FigureV3Editor extends LitElement {
607
618
  its rule (`nudge-part`). A part a joint or a fastening places, or a size that is worked out, is refused -- said in
608
619
  words, not by the part silently springing back (user, 2026-09-24: 「기즈모 컨트롤도 전혀 작동이 안되는군」).
609
620
  */
610
- const body = view.geometry.find(g => g.id === id) as unknown as V3RenderPart | undefined
621
+ const body = view.geometry.find(g => g.id === id) as unknown as FigureRenderPart | undefined
611
622
  if (!body) return this.requestUpdate()
612
623
  const { by, turn } = nudgeOf(body, result)
613
624
  /* A size worked out from the figure's: the drag's factor beside its rule, so it still follows. */
@@ -630,7 +641,7 @@ export class FigureV3Editor extends LitElement {
630
641
  ? i18next.t('figure.text.gizmo-joint-places', { part: partLabelOf(id) })
631
642
  : /is fastened to/.test(message)
632
643
  ? i18next.t('figure.text.gizmo-fastening-places', { part: partLabelOf(id) })
633
- : message
644
+ : this.said(error)
634
645
  this.requestUpdate()
635
646
  }
636
647
  }
@@ -734,15 +745,15 @@ export class FigureV3Editor extends LitElement {
734
745
  } catch {
735
746
  dims = undefined
736
747
  }
737
- return html`<figure-v3-links
748
+ return html`<figure-links
738
749
  .asset=${asset}
739
750
  .section=${section}
740
751
  .partId=${this.selected}
741
752
  .dims=${dims}
742
753
  ?locked=${unavailable}
743
- @v3-command=${(e: CustomEvent) => this.linkCommand(e.detail.action)}
744
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
745
- ></figure-v3-links>`
754
+ @command=${(e: CustomEvent) => this.linkCommand(e.detail.action)}
755
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
756
+ ></figure-links>`
746
757
  }
747
758
  /** The figure's controls: try a value, name it, set its range and travel time, keep the pose. */
748
759
  private renderControls(view: { fields: Array<{ id: string; role?: string; value: number }> }, unavailable: boolean) {
@@ -753,25 +764,25 @@ export class FigureV3Editor extends LitElement {
753
764
  return null
754
765
  }
755
766
  const values = Object.fromEntries(view.fields.filter(f => f.role === 'state').map(f => [f.id, f.value]))
756
- return html`<figure-v3-controls
767
+ return html`<figure-controls
757
768
  .asset=${asset}
758
769
  .values=${values}
759
770
  ?locked=${unavailable}
760
- @v3-state=${(e: CustomEvent) => this.run(() => this.session!.apply([{ kind: 'set-state', input: e.detail.input, unit: e.detail.unit, value: e.detail.value }]))}
761
- @v3-state-live=${(e: CustomEvent) => this.hold({ [e.detail.input]: e.detail.value })}
762
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
763
- ></figure-v3-controls>
764
- <figure-v3-clips
771
+ @state=${(e: CustomEvent) => this.run(() => this.session!.apply([{ kind: 'set-state', input: e.detail.input, unit: e.detail.unit, value: e.detail.value }]))}
772
+ @state-live=${(e: CustomEvent) => this.hold({ [e.detail.input]: e.detail.value })}
773
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
774
+ ></figure-controls>
775
+ <figure-clips
765
776
  .asset=${asset}
766
777
  .values=${values}
767
778
  ?locked=${unavailable}
768
- @v3-states=${(e: CustomEvent) => this.run(() => this.session!.apply(e.detail.changes))}
769
- @v3-states-live=${(e: CustomEvent) => this.hold(e.detail.values)}
770
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
771
- ></figure-v3-clips>`
779
+ @states=${(e: CustomEvent) => this.run(() => this.session!.apply(e.detail.changes))}
780
+ @states-live=${(e: CustomEvent) => this.hold(e.detail.values)}
781
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
782
+ ></figure-clips>`
772
783
  }
773
784
  /** One command, one step of the history. A refusal a person can meet here is said in their words. */
774
- private linkCommand(action: V3AuthoringAction) {
785
+ private linkCommand(action: FigureAuthoringAction) {
775
786
  this.retry = undefined
776
787
  this.notice = ''
777
788
  const hadSize = this.hasSize()
@@ -793,7 +804,7 @@ export class FigureV3Editor extends LitElement {
793
804
  const narrowing = narrowingRetryOf(action, error, (key, params) => i18next.t(key, params))
794
805
  if (narrowing) {
795
806
  this.error = narrowing.message
796
- this.retry = { label: narrowing.label, action: narrowing.retry as V3AuthoringAction }
807
+ this.retry = { label: narrowing.label, action: narrowing.retry as FigureAuthoringAction }
797
808
  return
798
809
  }
799
810
  this.error =
@@ -803,10 +814,10 @@ export class FigureV3Editor extends LitElement {
803
814
  ? i18next.t('figure.text.fasten-would-loop')
804
815
  : sizeRangeRefusalOf(error, (key, params) => i18next.t(key, params)) ??
805
816
  placementRefusalOf(error, (key, params) => i18next.t(key, params)) ??
806
- (error instanceof Error ? error.message : String(error))
817
+ this.said(error)
807
818
  }
808
819
  }
809
- private changeField(field: { id: string; unit: V3InputChange['unit']; role?: string }, event: Event) {
820
+ private changeField(field: { id: string; unit: FigureInputChange['unit']; role?: string }, event: Event) {
810
821
  const input = event.target as HTMLInputElement,
811
822
  value = input.valueAsNumber
812
823
  /* A height typed in keeps the part's bottom where it is, as the scale gizmo does. */
@@ -830,7 +841,7 @@ export class FigureV3Editor extends LitElement {
830
841
  * Drag a field's label (or an axis letter) sideways to scrub its value, as vector tools do: the figure follows the
831
842
  * pointer, and the release records one step. The field's number follows too, so the value in hand is always read.
832
843
  */
833
- private scrub(field: { id: string; unit: V3InputChange['unit']; role?: string; value: number }, down: PointerEvent, unavailable: boolean) {
844
+ private scrub(field: { id: string; unit: FigureInputChange['unit']; role?: string; value: number }, down: PointerEvent, unavailable: boolean) {
834
845
  if (unavailable || down.button !== 0) return
835
846
  const label = down.currentTarget as HTMLElement
836
847
  const input = (label.closest('div[cell]')?.querySelector('input') ?? label.nextElementSibling) as HTMLInputElement | null
@@ -865,7 +876,7 @@ export class FigureV3Editor extends LitElement {
865
876
  /** The measured range, kept for the revision it was measured at: measuring evaluates the figure at its edges. */
866
877
  private range?: { at: string; of: ReturnType<typeof instanceRangeOf>; toDeclare?: ReturnType<typeof instanceRangeToDeclare> }
867
878
  /** The sizes a board may give the figure, and the way to measure them again after its parts changed. */
868
- private renderInstanceRange(asset: V3Asset | undefined, unavailable: boolean) {
879
+ private renderInstanceRange(asset: FigureAsset | undefined, unavailable: boolean) {
869
880
  if (!asset) return nothing
870
881
  /* Another figure, or another step of this one, is measured again. */
871
882
  const at = `${this.session?.read().context ?? ''}:${this.revision}`
@@ -940,7 +951,7 @@ export class FigureV3Editor extends LitElement {
940
951
  * How the figure grows. One ratio (`sizing: 'uniform'`) is said as it is; a figure with joints that has not declared
941
952
  * it is offered it -- a mechanism does not shear -- and the author decides (chief architect's ruling 2026-09-24).
942
953
  */
943
- private renderSizingRule(asset: V3Asset, unavailable: boolean) {
954
+ private renderSizingRule(asset: FigureAsset, unavailable: boolean) {
944
955
  if (asset.sizing === 'uniform') {
945
956
  /* One ratio reaches only what reads the size: every other length is offered to follow it, in one step. */
946
957
  const follow = proportionToFollow(asset)
@@ -962,8 +973,8 @@ export class FigureV3Editor extends LitElement {
962
973
  * The parts list, one row per part the author made: a repeat is one row with how many copies it makes now
963
974
  * (user, 2026-09-24: the conveyor's sixteen rollers were sixteen rows).
964
975
  */
965
- private partRows(geometry: readonly { id: string }[]): V3PartRow[] {
966
- const rows = new Map<string, V3PartRow>()
976
+ private partRows(geometry: readonly { id: string }[]): FigurePartRow[] {
977
+ const rows = new Map<string, FigurePartRow>()
967
978
  for (const g of geometry) {
968
979
  const id = ownerOf(g.id)
969
980
  const row = rows.get(id)
@@ -982,10 +993,10 @@ export class FigureV3Editor extends LitElement {
982
993
  return [...rows.values()]
983
994
  }
984
995
  /** The detail level's part budget for the list's head, from figure-model's count (a repeat once). */
985
- private partBudgetOf(asset: V3Asset | undefined) {
996
+ private partBudgetOf(asset: FigureAsset | undefined) {
986
997
  if (!asset) return undefined
987
998
  try {
988
- return { level: asset.detailLevel ?? null, used: v3PartCountOf(asset), limit: asset.detailLevel ? PART_LIMIT[asset.detailLevel] : null }
999
+ return { level: asset.detailLevel ?? null, used: figurePartCountOf(asset), limit: asset.detailLevel ? PART_LIMIT[asset.detailLevel] : null }
989
1000
  } catch {
990
1001
  return undefined
991
1002
  }
@@ -995,7 +1006,7 @@ export class FigureV3Editor extends LitElement {
995
1006
  * The pitch of a row that fills its length is edited as a number (`set-repeat` makes it the part's own the first
996
1007
  * time); a row of a fixed count says its pitch cannot be set yet (figure-model refuses it the same way).
997
1008
  */
998
- private renderRepeat(asset: V3Asset | undefined, geometry: readonly { id: string }[], unavailable: boolean) {
1009
+ private renderRepeat(asset: FigureAsset | undefined, geometry: readonly { id: string }[], unavailable: boolean) {
999
1010
  const repeat = repeatOf(this.selected, geometry)
1000
1011
  if (!asset || !repeat || repeat.index) return nothing
1001
1012
  /* The row is set on its part; what hangs from that part is copied with it and has no row of its own. */
@@ -1051,7 +1062,7 @@ export class FigureV3Editor extends LitElement {
1051
1062
  </section>`
1052
1063
  }
1053
1064
  /** The design input a row's pitch is read from, when it is read from one (ADR-0096). */
1054
- private pitchInputOf(asset: V3Asset, part: string): string | undefined {
1065
+ private pitchInputOf(asset: FigureAsset, part: string): string | undefined {
1055
1066
  const pitch = asset.joints?.find(j => j.body1.part === part && j.repeat)?.repeat?.pitch as { from?: { of?: string; input?: string } } | number | undefined
1056
1067
  return typeof pitch === 'object' && pitch?.from?.of === 'input' ? pitch.from.input : undefined
1057
1068
  }
@@ -1059,7 +1070,7 @@ export class FigureV3Editor extends LitElement {
1059
1070
  * 「보드에서 바꿀 수 있음」 (ADR-0096): the row's pitch made a design input the figure publishes -- a name on the board and
1060
1071
  * a range the board's value is held to -- in one step; changed or withdrawn the same way. Only a row with a record.
1061
1072
  */
1062
- private renderPublicPitch(asset: V3Asset, pitch: number, unavailable: boolean) {
1073
+ private renderPublicPitch(asset: FigureAsset, pitch: number, unavailable: boolean) {
1063
1074
  const part = this.selected
1064
1075
  if (!asset.joints?.some(j => j.body1.part === part && j.repeat)) return nothing
1065
1076
  const from = this.pitchInputOf(asset, part)
@@ -1077,25 +1088,25 @@ export class FigureV3Editor extends LitElement {
1077
1088
  const base = form ?? { part, label: '', min: 0, max: 0 }
1078
1089
  this.publishing = { ...base, [key]: key === 'label' ? target.value : target.valueAsNumber }
1079
1090
  }
1080
- const steps = (): V3ProposalStep[] => {
1091
+ const steps = (): FigureProposalStep[] => {
1081
1092
  const f = form!
1082
1093
  const id = from ?? `${part}.pitch`
1083
- if (listed) return [{ kind: 'set-input-range', input: id, min: f.min, max: f.max }, { kind: 'publish-input', input: id, label: f.label }] as V3ProposalStep[]
1094
+ if (listed) return [{ kind: 'set-input-range', input: id, min: f.min, max: f.max }, { kind: 'publish-input', input: id, label: f.label }] as FigureProposalStep[]
1084
1095
  const exists = asset.document.model.inputs.some(i => i.id === id)
1085
1096
  return [
1086
1097
  ...(exists ? [{ kind: 'set-input-range', input: id, min: f.min, max: f.max }] : [{ kind: 'add-design-input', id, unit: 'mm', min: f.min, max: f.max, value: pitch }]),
1087
1098
  ...(from ? [] : [{ kind: 'set-repeat', part, pitch: { from: { of: 'input', input: id } } }]),
1088
1099
  { kind: 'publish-input', input: id, label: f.label }
1089
- ] as V3ProposalStep[]
1100
+ ] as FigureProposalStep[]
1090
1101
  }
1091
- const run = (list: V3ProposalStep[]) => {
1102
+ const run = (list: FigureProposalStep[]) => {
1092
1103
  try {
1093
1104
  this.session!.applyRecipe(list)
1094
1105
  this.publishing = undefined
1095
1106
  this.error = ''
1096
1107
  this.changed()
1097
1108
  } catch (error) {
1098
- this.error = error instanceof Error ? error.message : String(error)
1109
+ this.error = this.said(error)
1099
1110
  }
1100
1111
  }
1101
1112
  return html`<div public-pitch>
@@ -1109,7 +1120,7 @@ export class FigureV3Editor extends LitElement {
1109
1120
  (e.target as HTMLInputElement).checked
1110
1121
  ? (this.publishing = { part, label: i18next.t('figure.label.repeat-gap'), min: Math.max(1, Math.round(pitch / 2)), max: Math.round(pitch * 2) })
1111
1122
  : listed
1112
- ? run([{ kind: 'unpublish-input', input: from }, { kind: 'set-repeat', part, pitch }] as V3ProposalStep[])
1123
+ ? run([{ kind: 'unpublish-input', input: from }, { kind: 'set-repeat', part, pitch }] as FigureProposalStep[])
1113
1124
  : (this.publishing = undefined)}
1114
1125
  />
1115
1126
  ${i18next.t('figure.label.board-sets')}
@@ -1134,7 +1145,7 @@ export class FigureV3Editor extends LitElement {
1134
1145
  const pitch = input.valueAsNumber
1135
1146
  if (!Number.isFinite(pitch) || pitch === field.value) return
1136
1147
  /* Read from a design input the board may set: the input's value changes, the row keeps reading it (ADR-0096). */
1137
- const from = this.pitchInputOf(JSON.parse(this.session!.source()) as V3Asset, this.selected)
1148
+ const from = this.pitchInputOf(JSON.parse(this.session!.source()) as FigureAsset, this.selected)
1138
1149
  if (from) {
1139
1150
  this.run(() => this.session!.apply([{ kind: 'set-design', input: from, unit: 'mm', value: pitch }]))
1140
1151
  input.value = String(this.session!.read().fields.find(i => i.id === from)?.value ?? field.value)
@@ -1152,7 +1163,7 @@ export class FigureV3Editor extends LitElement {
1152
1163
  this.error =
1153
1164
  code === 'INPUT_RANGE' && found
1154
1165
  ? i18next.t('figure.text.repeat-pitch-too-small', { length: Number(found[1]).toLocaleString(), min: found[2], limit: found[3] })
1155
- : message
1166
+ : this.said(error)
1156
1167
  }
1157
1168
  }
1158
1169
  /** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
@@ -1216,7 +1227,7 @@ export class FigureV3Editor extends LitElement {
1216
1227
  .pane=${this.panes}
1217
1228
  @canvas-undo=${() => this.run(() => this.session!.undo())}
1218
1229
  @canvas-redo=${() => this.run(() => this.session!.redo())}
1219
- @canvas-fit=${() => this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')?.fit()}
1230
+ @canvas-fit=${() => this.renderRoot.querySelector<FigureCanvas>('figure-canvas')?.fit()}
1220
1231
  >
1221
1232
  ${this.renderGizmoTools(!unavailable && this.middle === 'edit')}
1222
1233
  ${(['edit', 'preview'] as const).map(
@@ -1237,7 +1248,7 @@ export class FigureV3Editor extends LitElement {
1237
1248
  : null}
1238
1249
  ${this.notice ? html`<div role="status" class="notice">${this.notice}</div>` : null}
1239
1250
  ${this.catalogOpen
1240
- ? html`<figure-v3-recipe-catalog
1251
+ ? html`<figure-recipe-catalog
1241
1252
  .templates=${this.templates}
1242
1253
  .selected=${this.selected}
1243
1254
  .error=${this.recipeError}
@@ -1246,15 +1257,15 @@ export class FigureV3Editor extends LitElement {
1246
1257
  ?locked=${unavailable}
1247
1258
  @insert-recipe=${(e: CustomEvent) => this.insertRecipe(e.detail.recipe, e.detail.values)}
1248
1259
  @close-catalog=${() => (this.catalogOpen = false)}
1249
- ></figure-v3-recipe-catalog>`
1260
+ ></figure-recipe-catalog>`
1250
1261
  : null}
1251
1262
  <ox-three-pane>
1252
- <figure-v3-parts
1263
+ <figure-parts
1253
1264
  slot="start"
1254
1265
  .budget=${this.budget}
1255
1266
  @show-maturity=${() => (this.tab = 'verdict')}
1256
1267
  .parts=${this.partRows(view.geometry)}
1257
- .partBudget=${this.partBudgetOf(asset as V3Asset | undefined)}
1268
+ .partBudget=${this.partBudgetOf(asset as FigureAsset | undefined)}
1258
1269
  @set-detail-level=${(e: CustomEvent) => this.linkCommand({ kind: 'set-detail-level', level: e.detail.level })}
1259
1270
  .selected=${this.selected}
1260
1271
  ?locked=${unavailable}
@@ -1269,18 +1280,18 @@ export class FigureV3Editor extends LitElement {
1269
1280
  @move-part-down=${(e: CustomEvent) => this.movePart(e.detail.id, e.detail.to)}
1270
1281
  @duplicate-part=${(e: CustomEvent) => this.duplicatePart(e.detail.id)}
1271
1282
  @remove-part=${(e: CustomEvent) => this.removePart(e.detail.id)}
1272
- ></figure-v3-parts>
1283
+ ></figure-parts>
1273
1284
  ${this.middle === 'preview'
1274
- ? html`<figure-v3-preview
1285
+ ? html`<figure-preview
1275
1286
  .asset=${asset}
1276
1287
  .context=${view.context ?? ''}
1277
1288
  .view=${this.view}
1278
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1279
- ></figure-v3-preview>`
1280
- : html`<figure-v3-canvas
1289
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1290
+ ></figure-preview>`
1291
+ : html`<figure-canvas
1281
1292
  .view=${this.view}
1282
1293
  .parts=${this.drawnParts(view.geometry, asset)}
1283
- .fan=${this.fanOf(asset as V3Asset | undefined)}
1294
+ .fan=${this.fanOf(asset as FigureAsset | undefined)}
1284
1295
  .palette=${view.palette ?? {}}
1285
1296
  .context=${view.context ?? ''}
1286
1297
  .selected=${this.selected}
@@ -1290,7 +1301,7 @@ export class FigureV3Editor extends LitElement {
1290
1301
  @render-error=${(e: CustomEvent<Error>) => {
1291
1302
  this.error = e.detail.message
1292
1303
  }}
1293
- ></figure-v3-canvas>`}
1304
+ ></figure-canvas>`}
1294
1305
  <aside slot="end">
1295
1306
  <div tabs>
1296
1307
  ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
@@ -1318,30 +1329,30 @@ export class FigureV3Editor extends LitElement {
1318
1329
  </div>
1319
1330
  ${this.renderRepeatNote(view.geometry)}
1320
1331
  </section>
1321
- ${this.renderRepeat(asset as V3Asset | undefined, view.geometry, unavailable)}
1332
+ ${this.renderRepeat(asset as FigureAsset | undefined, view.geometry, unavailable)}
1322
1333
  ${this.renderGroups(groupFields(own, this.selected), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable))}
1323
1334
  ${this.renderLinks(unavailable, 'shape')}
1324
- <figure-v3-material
1335
+ <figure-material-panel
1325
1336
  .body=${{ ...part, id: this.selected }}
1326
1337
  .palette=${view.palette ?? {}}
1327
1338
  ?locked=${unavailable}
1328
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1329
- @v3-part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
1330
- ></figure-v3-material>`
1339
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1340
+ @part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
1341
+ ></figure-material-panel>`
1331
1342
  : html`<slot name="figure"></slot>
1332
1343
  ${this.renderGroups(groupFields(common), 'figure.label.group-figure-size', 'figure.label.design-inputs', unavailable)}
1333
- ${this.renderInstanceRange(asset as V3Asset | undefined, unavailable)}`}
1344
+ ${this.renderInstanceRange(asset as FigureAsset | undefined, unavailable)}`}
1334
1345
  </div>
1335
1346
  <div panel ?off=${tab !== 'capability'}>
1336
1347
  ${this.renderSubject()}
1337
1348
  ${this.selected && part ? this.renderLinks(unavailable, 'places') : null}
1338
- <figure-v3-capability
1349
+ <figure-capability-panel
1339
1350
  .asset=${asset}
1340
1351
  .selected=${this.selected}
1341
1352
  ?locked=${unavailable}
1342
1353
  @select-part=${(e: CustomEvent) => this.choose(e.detail.id)}
1343
- @v3-part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1344
- ></figure-v3-capability>
1354
+ @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1355
+ ></figure-capability-panel>
1345
1356
  </div>
1346
1357
  <div panel ?off=${tab !== 'motion'}>
1347
1358
  ${this.renderSubject()}