@things-factory/figure-ui 10.1.75 → 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 (235) 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} +117 -117
  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-renderer.ts → renderer.ts} +16 -16
  37. package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
  38. package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
  39. package/client/pages/figure-list-page.ts +4 -4
  40. package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
  41. package/client/route.ts +1 -5
  42. package/dist-client/figure-assistant.js +3 -3
  43. package/dist-client/figure-assistant.js.map +1 -1
  44. package/dist-client/graphql/store.d.ts +3 -0
  45. package/dist-client/graphql/store.js +39 -0
  46. package/dist-client/graphql/store.js.map +1 -0
  47. package/dist-client/modeller/ai-proposal.d.ts +51 -0
  48. package/dist-client/modeller/ai-proposal.js +64 -0
  49. package/dist-client/modeller/ai-proposal.js.map +1 -0
  50. package/dist-client/modeller/align.d.ts +30 -0
  51. package/dist-client/modeller/align.js +75 -0
  52. package/dist-client/modeller/align.js.map +1 -0
  53. package/dist-client/modeller/canvas.d.ts +72 -0
  54. package/dist-client/modeller/canvas.js +379 -0
  55. package/dist-client/modeller/canvas.js.map +1 -0
  56. package/dist-client/modeller/clips.d.ts +39 -0
  57. package/dist-client/modeller/clips.js +58 -0
  58. package/dist-client/modeller/clips.js.map +1 -0
  59. package/dist-client/modeller/controls.d.ts +28 -0
  60. package/dist-client/modeller/controls.js +28 -0
  61. package/dist-client/modeller/controls.js.map +1 -0
  62. package/dist-client/modeller/editor.d.ts +203 -0
  63. package/dist-client/modeller/editor.js +1526 -0
  64. package/dist-client/modeller/editor.js.map +1 -0
  65. package/dist-client/modeller/fields.d.ts +45 -0
  66. package/dist-client/modeller/fields.js +83 -0
  67. package/dist-client/modeller/fields.js.map +1 -0
  68. package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
  69. package/dist-client/modeller/figure-capability-panel.js +141 -0
  70. package/dist-client/modeller/figure-capability-panel.js.map +1 -0
  71. package/dist-client/modeller/figure-clips.d.ts +31 -0
  72. package/dist-client/modeller/figure-clips.js +268 -0
  73. package/dist-client/modeller/figure-clips.js.map +1 -0
  74. package/dist-client/modeller/figure-controls.d.ts +36 -0
  75. package/dist-client/modeller/figure-controls.js +226 -0
  76. package/dist-client/modeller/figure-controls.js.map +1 -0
  77. package/dist-client/modeller/figure-facts.d.ts +35 -0
  78. package/dist-client/modeller/figure-facts.js +55 -0
  79. package/dist-client/modeller/figure-facts.js.map +1 -0
  80. package/dist-client/modeller/figure-first-save.d.ts +20 -0
  81. package/dist-client/modeller/figure-first-save.js +118 -0
  82. package/dist-client/modeller/figure-first-save.js.map +1 -0
  83. package/dist-client/modeller/figure-history-panel.d.ts +1 -1
  84. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  85. package/dist-client/modeller/figure-links.d.ts +77 -0
  86. package/dist-client/modeller/figure-links.js +649 -0
  87. package/dist-client/modeller/figure-links.js.map +1 -0
  88. package/dist-client/modeller/figure-material-panel.d.ts +39 -0
  89. package/dist-client/modeller/figure-material-panel.js +323 -0
  90. package/dist-client/modeller/figure-material-panel.js.map +1 -0
  91. package/dist-client/modeller/figure-parts.d.ts +69 -0
  92. package/dist-client/modeller/figure-parts.js +559 -0
  93. package/dist-client/modeller/figure-parts.js.map +1 -0
  94. package/dist-client/modeller/figure-preview.d.ts +34 -0
  95. package/dist-client/modeller/figure-preview.js +346 -0
  96. package/dist-client/modeller/figure-preview.js.map +1 -0
  97. package/dist-client/modeller/figure-properties.d.ts +45 -0
  98. package/dist-client/modeller/figure-properties.js +239 -0
  99. package/dist-client/modeller/figure-properties.js.map +1 -0
  100. package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
  101. package/dist-client/modeller/figure-recipe-catalog.js +399 -0
  102. package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
  103. package/dist-client/modeller/figure-verdict.d.ts +64 -0
  104. package/dist-client/modeller/figure-verdict.js +787 -0
  105. package/dist-client/modeller/figure-verdict.js.map +1 -0
  106. package/dist-client/modeller/first-save.d.ts +17 -0
  107. package/dist-client/modeller/first-save.js +26 -0
  108. package/dist-client/modeller/first-save.js.map +1 -0
  109. package/dist-client/modeller/gate-findings.d.ts +2 -2
  110. package/dist-client/modeller/gate-findings.js +2 -2
  111. package/dist-client/modeller/gate-findings.js.map +1 -1
  112. package/dist-client/modeller/gizmo.d.ts +72 -0
  113. package/dist-client/modeller/gizmo.js +111 -0
  114. package/dist-client/modeller/gizmo.js.map +1 -0
  115. package/dist-client/modeller/help.d.ts +18 -0
  116. package/dist-client/modeller/help.js +37 -0
  117. package/dist-client/modeller/help.js.map +1 -0
  118. package/dist-client/modeller/joint-drag.d.ts +19 -0
  119. package/dist-client/modeller/joint-drag.js +97 -0
  120. package/dist-client/modeller/joint-drag.js.map +1 -0
  121. package/dist-client/modeller/joint-fan.d.ts +9 -0
  122. package/dist-client/modeller/joint-fan.js +276 -0
  123. package/dist-client/modeller/joint-fan.js.map +1 -0
  124. package/dist-client/modeller/keys.d.ts +22 -0
  125. package/dist-client/modeller/keys.js +26 -0
  126. package/dist-client/modeller/keys.js.map +1 -0
  127. package/dist-client/modeller/links.d.ts +267 -0
  128. package/dist-client/modeller/links.js +480 -0
  129. package/dist-client/modeller/links.js.map +1 -0
  130. package/dist-client/modeller/live.d.ts +37 -0
  131. package/dist-client/modeller/live.js +68 -0
  132. package/dist-client/modeller/live.js.map +1 -0
  133. package/dist-client/modeller/maturity.d.ts +120 -0
  134. package/dist-client/modeller/maturity.js +313 -0
  135. package/dist-client/modeller/maturity.js.map +1 -0
  136. package/dist-client/modeller/modeller-route.d.ts +1 -1
  137. package/dist-client/modeller/modeller-route.js +1 -1
  138. package/dist-client/modeller/modeller-route.js.map +1 -1
  139. package/dist-client/modeller/occupancy.d.ts +18 -0
  140. package/dist-client/modeller/occupancy.js +24 -0
  141. package/dist-client/modeller/occupancy.js.map +1 -0
  142. package/dist-client/modeller/palette.d.ts +7 -0
  143. package/dist-client/modeller/palette.js +46 -0
  144. package/dist-client/modeller/palette.js.map +1 -0
  145. package/dist-client/modeller/preview-model.d.ts +47 -0
  146. package/dist-client/modeller/preview-model.js +68 -0
  147. package/dist-client/modeller/preview-model.js.map +1 -0
  148. package/dist-client/modeller/product-session.d.ts +510 -0
  149. package/dist-client/modeller/product-session.js +157 -0
  150. package/dist-client/modeller/product-session.js.map +1 -0
  151. package/dist-client/modeller/recipe-source.d.ts +8 -0
  152. package/dist-client/modeller/recipe-source.js +14 -0
  153. package/dist-client/modeller/recipe-source.js.map +1 -0
  154. package/dist-client/modeller/recipes.d.ts +37 -0
  155. package/dist-client/modeller/recipes.js +103 -0
  156. package/dist-client/modeller/recipes.js.map +1 -0
  157. package/dist-client/modeller/refusals.d.ts +68 -0
  158. package/dist-client/modeller/refusals.js +142 -0
  159. package/dist-client/modeller/refusals.js.map +1 -0
  160. package/dist-client/modeller/renderer.d.ts +40 -0
  161. package/dist-client/modeller/renderer.js +73 -0
  162. package/dist-client/modeller/renderer.js.map +1 -0
  163. package/dist-client/modeller/stage-model.d.ts +39 -0
  164. package/dist-client/modeller/stage-model.js +147 -0
  165. package/dist-client/modeller/stage-model.js.map +1 -0
  166. package/dist-client/modeller/thumbnail.d.ts +3 -0
  167. package/dist-client/modeller/thumbnail.js +75 -0
  168. package/dist-client/modeller/thumbnail.js.map +1 -0
  169. package/dist-client/pages/figure-list-page.d.ts +1 -1
  170. package/dist-client/pages/figure-list-page.js +4 -4
  171. package/dist-client/pages/figure-list-page.js.map +1 -1
  172. package/dist-client/pages/figure-modeller-page.d.ts +102 -0
  173. package/dist-client/pages/figure-modeller-page.js +548 -0
  174. package/dist-client/pages/figure-modeller-page.js.map +1 -0
  175. package/dist-client/route.d.ts +1 -1
  176. package/dist-client/route.js +1 -5
  177. package/dist-client/route.js.map +1 -1
  178. package/dist-client/tsconfig.tsbuildinfo +1 -1
  179. package/docs/product-cutover.md +25 -0
  180. package/package.json +5 -5
  181. package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
  182. package/test/{v3-align.test.ts → align.test.ts} +8 -8
  183. package/test/browser/modeller-page.js +30 -30
  184. package/test/browser/modeller.check.mjs +14 -14
  185. package/test/browser/serve.mjs +1 -1
  186. package/test/browser/thumbnail-page.js +6 -6
  187. package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
  188. package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
  189. package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
  190. package/test/figure-assistant-surface.test.ts +3 -3
  191. package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
  192. package/test/fixtures/joint-column.json +2 -2
  193. package/test/fixtures/joint-roller.json +2 -2
  194. package/test/fixtures/robot-arm-6dof.json +2 -2
  195. package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
  196. package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
  197. package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
  198. package/test/joints.ts +6 -6
  199. package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
  200. package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
  201. package/test/{v3-links.test.ts → links.test.ts} +201 -201
  202. package/test/{v3-live.test.ts → live.test.ts} +18 -18
  203. package/test/{v3-material.test.ts → material.test.ts} +12 -12
  204. package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
  205. package/test/modeller-route.test.ts +5 -6
  206. package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
  207. package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +5 -5
  208. package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
  209. package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
  210. package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
  211. package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
  212. package/test/{v3-recipes.test.ts → recipes.test.ts} +9 -9
  213. package/test/{v3-refusals.test.ts → refusals.test.ts} +43 -43
  214. package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
  215. package/test/renderer.test.ts +24 -0
  216. package/test/resolve-ts-siblings.mjs +1 -1
  217. package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
  218. package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
  219. package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
  220. package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
  221. package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
  222. package/test/{v3-view.test.ts → view.test.ts} +2 -2
  223. package/things-factory.config.js +2 -3
  224. package/translations/en.json +2 -2
  225. package/translations/ja.json +2 -2
  226. package/translations/ko.json +2 -2
  227. package/translations/ms.json +2 -2
  228. package/translations/zh.json +2 -2
  229. package/docs/v3-product-cutover.md +0 -25
  230. package/test/v3-renderer.test.ts +0 -24
  231. /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
  232. /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
  233. /package/client/modeller/{v3-help.ts → help.ts} +0 -0
  234. /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
  235. /package/client/modeller/{v3-refusals.ts → refusals.ts} +0 -0
@@ -7,16 +7,16 @@ import assert from 'node:assert/strict'
7
7
  import { readFileSync } from 'node:fs'
8
8
  import { fileURLToPath } from 'node:url'
9
9
 
10
- import { addV3Box, applyV3Authoring, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
10
+ import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
11
11
 
12
- import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/v3-live.ts'
12
+ import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/live.ts'
13
13
  import { moving } from './joints.ts'
14
14
 
15
15
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
16
16
 
17
17
  const slider = () => {
18
- let asset = createV3Asset('Slider')
19
- asset = addV3Box(asset, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
18
+ let asset = createFigureAsset('Slider')
19
+ asset = addFigureBox(asset, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
20
20
  return moving(asset, 'block', { kind: 'slide', axis: 'x', id: 'shift', min: 0, max: 60, start: 0 })
21
21
  }
22
22
 
@@ -69,29 +69,29 @@ test('이름표를 끌면 값이 크기에 맞는 걸음으로 움직인다 —
69
69
  })
70
70
 
71
71
  test('속성창 숫자 칸은 치는 동안 알리고, 이름표를 끌 수 있다 — 기록은 change 에서', () => {
72
- const editor = read('../client/modeller/v3-editor.ts')
72
+ const editor = read('../client/modeller/editor.ts')
73
73
  assert.ok(editor.includes('@input=${(e: Event) => this.typing(group.cells[axis] as never, e)}'))
74
74
  assert.ok(editor.includes('@input=${(e: Event) => this.typing(field as never, e)}'))
75
75
  assert.ok(editor.includes('@pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}'))
76
- for (const event of ['@v3-state-live=', '@v3-states-live=', '@v3-part-live=']) assert.ok(editor.includes(event), event)
77
- assert.ok(editor.includes('liveOverSession(asset as V3Asset, this.currentInputs(), this.live)'), '캔버스는 세션 위에 끄는 값을 그린다 (행동은 아래 시험)')
76
+ for (const event of ['@state-live=', '@states-live=', '@part-live=']) assert.ok(editor.includes(event), event)
77
+ assert.ok(editor.includes('liveOverSession(asset as FigureAsset, this.currentInputs(), this.live)'), '캔버스는 세션 위에 끄는 값을 그린다 (행동은 아래 시험)')
78
78
  const run = editor.slice(editor.indexOf(' private run(action'), editor.indexOf(' private hold('))
79
79
  assert.ok(run.includes('this.live = NOTHING_LIVE'), '기록하거나 거절되면 끄는 값을 버린다')
80
- const material = read('../client/modeller/figure-v3-material.ts')
80
+ const material = read('../client/modeller/figure-material-panel.ts')
81
81
  assert.ok(material.includes('@input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}'))
82
82
  })
83
83
 
84
84
  test('슬라이더와 클립 막대는 끄는 동안 알리고, 놓을 때 한 번 기록한다', () => {
85
- const controls = read('../client/modeller/figure-v3-controls.ts')
85
+ const controls = read('../client/modeller/figure-controls.ts')
86
86
  assert.ok(controls.includes('@input=${(e: Event) => this.drag(c, number(e))}') && controls.includes('@change=${(e: Event) => this.try(c, number(e))}'))
87
- assert.ok(controls.includes("new CustomEvent('v3-state-live'"))
88
- const clips = read('../client/modeller/figure-v3-clips.ts')
87
+ assert.ok(controls.includes("new CustomEvent('state-live'"))
88
+ const clips = read('../client/modeller/figure-clips.ts')
89
89
  assert.ok(clips.includes('@input=${(e: Event) => this.scrub(clip, number(e), true)}'))
90
- assert.ok(clips.includes("new CustomEvent('v3-states-live'"))
90
+ assert.ok(clips.includes("new CustomEvent('states-live'"))
91
91
  })
92
92
 
93
93
  test('클립 트랙의 머리는 이름 한 줄, 그 아래 반복 방식과 지우기 — 이름이 한 글자씩 쌓이지 않는다', () => {
94
- const clips = read('../client/modeller/figure-v3-clips.ts')
94
+ const clips = read('../client/modeller/figure-clips.ts')
95
95
  const head = clips.slice(clips.indexOf(' div[track-head] {'), clips.indexOf(' div[keys] {'))
96
96
  assert.ok(head.includes('display: grid;') && !head.includes('display: flex;'), '공통 스타일의 폭 100% 가 이름을 누르지 않는다')
97
97
  assert.ok(head.includes('grid-column: 1 / -1;'), '이름은 제 줄')
@@ -102,14 +102,14 @@ test('끄는 동안은 세션이 지금 든 자세 위에 그린다 — 다른
102
102
  /* Two joints: the author turned one with its control; then drags the other. */
103
103
  let asset = slider()
104
104
  /* An arm on a post of its own, turning about the post's left edge -- so a swing moves it, and the slide does not. */
105
- asset = addV3Box(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 400, y: 50, z: 0 } })
106
- asset = applyV3Authoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' }, align: { x: { mm: 400 } } })
107
- asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 20, z: 20 }, position: { x: 400, y: 110, z: 0 } })
108
- asset = applyV3Authoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'post', face: 'top' }, align: { x: 'min' } })
105
+ asset = addFigureBox(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 400, y: 50, z: 0 } })
106
+ asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' }, align: { x: { mm: 400 } } })
107
+ asset = addFigureBox(asset, { id: 'arm', size: { x: 100, y: 20, z: 20 }, position: { x: 400, y: 110, z: 0 } })
108
+ asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'post', face: 'top' }, align: { x: 'min' } })
109
109
  asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'swing', min: -180, max: 180 })
110
110
  const current = { ...asset.designInputs, ...asset.stateDefaults, swing: 90 }
111
111
  const armAt = (parts: { id: string; pose: { t: number[] } }[]) => parts.find(p => p.id === 'arm')!.pose.t.map(v => Math.round(v))
112
- const committed = armAt(compileV3Asset({ ...asset, stateDefaults: { ...asset.stateDefaults, swing: 90 } }).evaluate().geometry as never)
112
+ const committed = armAt(compileFigureAsset({ ...asset, stateDefaults: { ...asset.stateDefaults, swing: 90 } }).evaluate().geometry as never)
113
113
  const dragging = armAt(liveOverSession(asset, current, { values: { shift: 30 } }) as never)
114
114
  assert.deepEqual(dragging, committed, '사용자 2026-09-24: 「조작을 끝내면 완전히 다른 위치로 된다」 — swing 90 이 끄는 동안에도 남는다')
115
115
  const overSaved = armAt(livePartsOf(asset, { values: { shift: 30 } }) as never)
@@ -9,23 +9,23 @@ import assert from 'node:assert/strict'
9
9
  import { readFileSync } from 'node:fs'
10
10
  import { fileURLToPath } from 'node:url'
11
11
 
12
- import { addV3Box, addV3Primitive, compileV3Asset, createV3Asset, editV3Part, v3CostOf } from '@hatiolab/figure-model'
12
+ import { addFigureBox, addFigurePrimitive, compileFigureAsset, createFigureAsset, editFigurePart, figureCostOf } from '@hatiolab/figure-model'
13
13
 
14
14
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
15
- const material = read('../client/modeller/figure-v3-material.ts')
16
- const editor = read('../client/modeller/v3-editor.ts')
15
+ const material = read('../client/modeller/figure-material-panel.ts')
16
+ const editor = read('../client/modeller/editor.ts')
17
17
 
18
18
  const probe = () => {
19
- let asset = createV3Asset('Probe')
20
- asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
21
- asset = addV3Primitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 50, z: 0 } })
19
+ let asset = createFigureAsset('Probe')
20
+ asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
21
+ asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 50, z: 0 } })
22
22
  return asset
23
23
  }
24
24
  const drawn = (asset: ReturnType<typeof probe>, id: string) =>
25
- compileV3Asset(asset).evaluate().geometry.find(g => (g as { id: string }).id === id)!
25
+ compileFigureAsset(asset).evaluate().geometry.find(g => (g as { id: string }).id === id)!
26
26
 
27
27
  test('재질 칸이 보내는 모양 그대로 figure-model 이 받고, 그려지는 재질이 바뀐다', () => {
28
- const edited = editV3Part(probe(), {
28
+ const edited = editFigurePart(probe(), {
29
29
  kind: 'set-material',
30
30
  target: 'body',
31
31
  material: { token: 'palette.accent', preset: 'metal', transparent: true, emissive: { token: 'palette.accent', intensity: 0.5, on: true } },
@@ -47,8 +47,8 @@ test('발광 세기 0 은 발광을 없앤다 — 빈 칸을 남기지 않는다
47
47
 
48
48
  test('곡면 분할은 둥근 몸체에만 내고, 고르면 삼각형 수가 그만큼 바뀐다', () => {
49
49
  assert.match(material, /const ROUND = new Set\(\['cylinder', 'ellipticCylinder', 'sphere', 'frustum'\]\)/)
50
- const coarse = v3CostOf(editV3Part(probe(), { kind: 'set-segments', target: 'drum', segments: 8 })).triangles
51
- const fine = v3CostOf(editV3Part(probe(), { kind: 'set-segments', target: 'drum', segments: 48 })).triangles
50
+ const coarse = figureCostOf(editFigurePart(probe(), { kind: 'set-segments', target: 'drum', segments: 8 })).triangles
51
+ const fine = figureCostOf(editFigurePart(probe(), { kind: 'set-segments', target: 'drum', segments: 48 })).triangles
52
52
  assert.ok(fine > coarse, `48 분할이 8 분할보다 삼각형이 많다 (${coarse} → ${fine})`)
53
53
  })
54
54
 
@@ -58,7 +58,7 @@ test('색 역할은 날것의 이름이 아니라 쓰임새로 부른다', () =>
58
58
  })
59
59
 
60
60
  test('편집기는 재질 칸의 편집을 세션의 이력 하나로 돌린다', () => {
61
- assert.match(editor, /<figure-v3-material[\s\S]*?@v3-part-edit=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.edit\(e\.detail\.edit\)\)\}/)
61
+ assert.match(editor, /<figure-material-panel[\s\S]*?@part-edit=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.edit\(e\.detail\.edit\)\)\}/)
62
62
  })
63
63
 
64
64
  test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형의 팔레트는 기본색 하나다', () => {
@@ -68,7 +68,7 @@ test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형
68
68
  })
69
69
 
70
70
  test('분할 수 — 흔한 넷은 다각형 윤곽과 삼각형 수로, 나머지는 접어 둔다', () => {
71
- const source = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-material.ts', import.meta.url)), 'utf8')
71
+ const source = readFileSync(fileURLToPath(new URL('../client/modeller/figure-material-panel.ts', import.meta.url)), 'utf8')
72
72
  assert.ok(source.includes('SEGMENT_PRESETS.slice(0, 4).map(') && source.includes('${silhouette(preset)}'))
73
73
  assert.ok(source.includes('<details free') && source.includes('SEGMENT_PRESETS.slice(4)'))
74
74
  })
@@ -2,16 +2,16 @@
2
2
  * **성숙도 탭** (사용자 2026-09-24: 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」).
3
3
  *
4
4
  * 탭은 개수 · 등급과 눈금 · 등급을 정한 한도 · 여섯 축 radar · 한도 막대 · 그리는 비용 · 재사용 추이를 보인다. 여기서는
5
- * 그 수들이 V3 자산에서 바르게 나오는지, V3 렌더러가 실제로 나눠 쓰는 것을 세는지, 화면이 그 순서와 말로 그리는지를 본다.
5
+ * 그 수들이 자산에서 바르게 나오는지, 렌더러가 실제로 나눠 쓰는 것을 세는지, 화면이 그 순서와 말로 그리는지를 본다.
6
6
  */
7
7
  import { test } from 'node:test'
8
8
  import assert from 'node:assert/strict'
9
9
  import { readFileSync } from 'node:fs'
10
10
  import { fileURLToPath } from 'node:url'
11
11
 
12
- import { addV3Box, addV3Primitive, applyV3Authoring, compileV3Asset, createV3Asset, LIMITS, v3ScoreOf } from '@hatiolab/figure-model'
13
- import type { V3Asset } from '@hatiolab/figure-model'
14
- import { acquireV3Geometry, releaseV3Geometry } from '@hatiolab/things-scene'
12
+ import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, compileFigureAsset, createFigureAsset, LIMITS, figureScoreOf } from '@hatiolab/figure-model'
13
+ import type { FigureAsset } from '@hatiolab/figure-model'
14
+ import { acquireFigureGeometry, releaseFigureGeometry } from '@hatiolab/things-scene'
15
15
 
16
16
  import {
17
17
  BOARD_ASSETS,
@@ -20,19 +20,19 @@ import {
20
20
  decadeSteps,
21
21
  GRADE_SCALE,
22
22
  gradeOf,
23
- V3_BUDGET_KEYS,
24
- v3ReportOf
25
- } from '../client/modeller/v3-maturity.ts'
23
+ FIGURE_BUDGET_KEYS,
24
+ figureReportOf
25
+ } from '../client/modeller/maturity.ts'
26
26
  import { capacityFrom } from '../client/modeller/scene-budget.ts'
27
27
  import { moving } from './joints.ts'
28
28
 
29
29
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
30
- const verdict = read('../client/modeller/figure-v3-verdict.ts')
31
- const maturity = read('../client/modeller/v3-maturity.ts')
30
+ const verdict = read('../client/modeller/figure-verdict.ts')
31
+ const maturity = read('../client/modeller/maturity.ts')
32
32
 
33
33
  /** An asset with the given tokens in its palette, so boxes can differ in colour. */
34
- function withPalette(...tokens: string[]): V3Asset {
35
- const asset = createV3Asset('Probe')
34
+ function withPalette(...tokens: string[]): FigureAsset {
35
+ const asset = createFigureAsset('Probe')
36
36
  for (const token of tokens) asset.palette[token] = '#336699'
37
37
  return asset
38
38
  }
@@ -45,7 +45,7 @@ const cube = (id: string, x: number, token = 'palette.primary', extra: Record<st
45
45
  })
46
46
 
47
47
  test('부품이 없으면 리포트가 없다 — 0 으로 그리지 않는다', () => {
48
- assert.equal(v3ReportOf(createV3Asset('Empty')), undefined)
48
+ assert.equal(figureReportOf(createFigureAsset('Empty')), undefined)
49
49
  })
50
50
 
51
51
  test('등급의 눈금 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘치면 D', () => {
@@ -61,76 +61,76 @@ test('등급의 눈금 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘
61
61
  assert.equal(gradeOf(1.01), 'D')
62
62
  })
63
63
 
64
- test('등급은 figure-model 의 v3ScoreOf 그대로다 — 목록과 이 탭이 한 셈으로 매긴다', () => {
65
- let asset = createV3Asset('Probe')
66
- asset = addV3Box(asset, cube('a', 0))
67
- const one = v3ReportOf(asset)!
68
- assert.deepEqual(one.score, v3ScoreOf(asset))
69
- assert.deepEqual(V3_BUDGET_KEYS.filter(k => k !== 'parts'), one.score.budgets.map(b => b.key), '화면이 말을 찾는 키와 셈의 키가 같다 — 등급을 안 정하면 부품 수 한도는 없다')
64
+ test('등급은 figure-model 의 figureScoreOf 그대로다 — 목록과 이 탭이 한 셈으로 매긴다', () => {
65
+ let asset = createFigureAsset('Probe')
66
+ asset = addFigureBox(asset, cube('a', 0))
67
+ const one = figureReportOf(asset)!
68
+ assert.deepEqual(one.score, figureScoreOf(asset))
69
+ assert.deepEqual(FIGURE_BUDGET_KEYS.filter(k => k !== 'parts'), one.score.budgets.map(b => b.key), '화면이 말을 찾는 키와 셈의 키가 같다 — 등급을 안 정하면 부품 수 한도는 없다')
70
70
  })
71
71
 
72
72
  test('draw call 한도 3 은 없다 — 한 화면 166 대 목표에 견준다(수석 아키텍트 판정 09-24)', () => {
73
- let asset = createV3Asset('Probe')
74
- for (let i = 0; i < 4; i++) asset = addV3Box(asset, cube(`b${i}`, i * 200))
75
- const four = v3ReportOf(asset)!
73
+ let asset = createFigureAsset('Probe')
74
+ for (let i = 0; i < 4; i++) asset = addFigureBox(asset, cube(`b${i}`, i * 200))
75
+ const four = figureReportOf(asset)!
76
76
  assert.equal(four.score.grade, 'A', '예전 한도 3 으로는 D 였다')
77
77
  const groups = four.score.budgets.find(b => b.key === 'groups')!
78
78
  assert.equal(groups.limit, LIMITS.groupsOnScreen / LIMITS.screenInstances)
79
79
  assert.ok(four.capacity.instances >= 2 * LIMITS.screenInstances, 'A 는 목표의 두 배가 들어간다는 뜻')
80
80
  })
81
81
 
82
- test('감춘 부품은 그리지 않으므로 한도에 들지 않는다 — v3CostOf 와 같은 셈', () => {
83
- let asset = createV3Asset('Probe')
84
- asset = addV3Box(asset, cube('a', 0))
85
- asset = addV3Box(asset, cube('b', 200))
86
- const report = v3ReportOf(asset)!
82
+ test('감춘 부품은 그리지 않으므로 한도에 들지 않는다 — figureCostOf 와 같은 셈', () => {
83
+ let asset = createFigureAsset('Probe')
84
+ asset = addFigureBox(asset, cube('a', 0))
85
+ asset = addFigureBox(asset, cube('b', 200))
86
+ const report = figureReportOf(asset)!
87
87
  assert.equal(report.score.budgets.find(b => b.key === 'groups')!.used, report.cost.groups)
88
88
  assert.equal(report.capacity.instances, capacityFrom(report.cost.groups, report.cost.triangles)!.instances)
89
89
  })
90
90
 
91
91
  test('투명 재질은 서로 다른 재질로 센다 — 둘이면 한도 1 을 넘고, 그것이 등급을 정한다', () => {
92
92
  let asset = withPalette('glass.a', 'glass.b')
93
- asset = addV3Box(asset, cube('a', 0, 'glass.a', { transparent: true }))
94
- asset = addV3Box(asset, cube('b', 200, 'glass.b', { transparent: true }))
95
- const report = v3ReportOf(asset)!
93
+ asset = addFigureBox(asset, cube('a', 0, 'glass.a', { transparent: true }))
94
+ asset = addFigureBox(asset, cube('b', 200, 'glass.b', { transparent: true }))
95
+ const report = figureReportOf(asset)!
96
96
  assert.equal(report.score.budgets.find(b => b.key === 'transparent')!.used, 2)
97
97
  assert.equal(report.score.tightest.key, 'transparent')
98
98
  assert.equal(report.score.grade, 'D')
99
99
 
100
100
  let same = withPalette('glass.a')
101
- same = addV3Box(same, cube('a', 0, 'glass.a', { transparent: true }))
102
- same = addV3Box(same, cube('b', 200, 'glass.a', { transparent: true }))
103
- assert.equal(v3ReportOf(same)!.score.budgets.find(b => b.key === 'transparent')!.used, 1, '같은 재질은 하나로 나눠 쓴다')
101
+ same = addFigureBox(same, cube('a', 0, 'glass.a', { transparent: true }))
102
+ same = addFigureBox(same, cube('b', 200, 'glass.a', { transparent: true }))
103
+ assert.equal(figureReportOf(same)!.score.budgets.find(b => b.key === 'transparent')!.used, 1, '같은 재질은 하나로 나눠 쓴다')
104
104
  })
105
105
 
106
106
  test('상태를 따라 움직이는 부품을 센다 — 「따로 움직이는 부품」', () => {
107
- let asset = createV3Asset('Probe')
108
- asset = addV3Box(asset, cube('base', 0))
109
- asset = addV3Box(asset, cube('arm', 200))
110
- assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 0, '전제: 움직임이 없으면 0')
107
+ let asset = createFigureAsset('Probe')
108
+ asset = addFigureBox(asset, cube('base', 0))
109
+ asset = addFigureBox(asset, cube('arm', 200))
110
+ assert.equal(figureReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 0, '전제: 움직임이 없으면 0')
111
111
 
112
112
  asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'arm.turn', min: 0, max: 90 })
113
- assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 1, '도는 팔 하나')
113
+ assert.equal(figureReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 1, '도는 팔 하나')
114
114
  })
115
115
 
116
116
  test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는 수와 같다', () => {
117
- let asset = createV3Asset('Probe')
118
- asset = addV3Box(asset, cube('a', 0))
119
- asset = addV3Box(asset, cube('b', 200))
120
- asset = addV3Box(asset, { id: 'c', size: { x: 100, y: 300, z: 100 }, position: { x: 400, y: 150, z: 0 } })
121
- asset = addV3Primitive(asset, { id: 'd', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 600, y: 50, z: 0 } })
122
- asset = addV3Primitive(asset, { id: 'e', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 800, y: 50, z: 0 } })
123
- asset = addV3Primitive(asset, { id: 'f', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 1000, y: 50, z: 0 } })
117
+ let asset = createFigureAsset('Probe')
118
+ asset = addFigureBox(asset, cube('a', 0))
119
+ asset = addFigureBox(asset, cube('b', 200))
120
+ asset = addFigureBox(asset, { id: 'c', size: { x: 100, y: 300, z: 100 }, position: { x: 400, y: 150, z: 0 } })
121
+ asset = addFigurePrimitive(asset, { id: 'd', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 600, y: 50, z: 0 } })
122
+ asset = addFigurePrimitive(asset, { id: 'e', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 800, y: 50, z: 0 } })
123
+ asset = addFigurePrimitive(asset, { id: 'f', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 1000, y: 50, z: 0 } })
124
124
  // The same cylinder, rounder: another vertex buffer in the renderer, so another shape here.
125
125
  asset.appearance.find(look => look.target === 'f')!.segments = 24
126
- const report = v3ReportOf(asset)!
126
+ const report = figureReportOf(asset)!
127
127
 
128
- const bodies = compileV3Asset(asset).evaluate().geometry
129
- const held = bodies.map(body => acquireV3Geometry((body as { shape: never }).shape, (body as { segments?: number }).segments))
128
+ const bodies = compileFigureAsset(asset).evaluate().geometry
129
+ const held = bodies.map(body => acquireFigureGeometry((body as { shape: never }).shape, (body as { segments?: number }).segments))
130
130
  try {
131
131
  assert.equal(report.distinctShapes, new Set(held).size, '렌더러의 은행과 같은 키')
132
132
  } finally {
133
- held.forEach(geometry => releaseV3Geometry(geometry))
133
+ held.forEach(geometry => releaseFigureGeometry(geometry))
134
134
  }
135
135
  assert.equal(report.distinctShapes, 4, '같은 상자 둘 · 키 큰 상자 · 같은 원기둥 둘 · 분할이 다른 원기둥')
136
136
  assert.deepEqual(report.reuse, { distinct: 4, parts: 6, saved: 2 })
@@ -139,9 +139,9 @@ test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는
139
139
 
140
140
  test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까닭을 단다', () => {
141
141
  let asset = withPalette('palette.accent')
142
- asset = addV3Box(asset, cube('a', 50))
143
- asset = addV3Box(asset, cube('b', 150, 'palette.accent'))
144
- const axes = v3ReportOf(asset)!.axes
142
+ asset = addFigureBox(asset, cube('a', 50))
143
+ asset = addFigureBox(asset, cube('b', 150, 'palette.accent'))
144
+ const axes = figureReportOf(asset)!.axes
145
145
 
146
146
  assert.deepEqual(
147
147
  axes.map(axis => axis.key),
@@ -149,7 +149,7 @@ test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까
149
149
  )
150
150
  const byKey = Object.fromEntries(axes.map(axis => [axis.key, axis]))
151
151
  assert.equal(byKey.detail!.ratio, undefined)
152
- assert.equal(byKey.detail!.blocked, 'figure.text.maturity-blocked-no-detail-level', '디테일 등급은 V3 에 없다(ADR-0087)')
152
+ assert.equal(byKey.detail!.blocked, 'figure.text.maturity-blocked-no-detail-level', '디테일 등급은 이 모델에 없다(ADR-0087)')
153
153
  assert.equal(byKey.reactive!.ratio, undefined)
154
154
  assert.equal(byKey.reactive!.blocked, 'figure.text.maturity-blocked-no-binding')
155
155
 
@@ -162,11 +162,11 @@ test('여섯 축 — 정해진 순서, 잴 수 없는 둘은 비율 없이 까
162
162
 
163
163
  test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — 셋 중 하나', () => {
164
164
  let asset = withPalette('t1', 't2', 't3')
165
- asset = addV3Box(asset, cube('a', 0))
166
- asset = addV3Box(asset, cube('b', 200, 't1'))
167
- asset = addV3Box(asset, cube('c', 400, 't2'))
168
- asset = addV3Box(asset, cube('d', 600, 't3'))
169
- const report = v3ReportOf(asset)!
165
+ asset = addFigureBox(asset, cube('a', 0))
166
+ asset = addFigureBox(asset, cube('b', 200, 't1'))
167
+ asset = addFigureBox(asset, cube('c', 400, 't2'))
168
+ asset = addFigureBox(asset, cube('d', 600, 't3'))
169
+ const report = figureReportOf(asset)!
170
170
  assert.equal(report.distinctMaterials, 4)
171
171
  const consistency = report.axes.find(axis => axis.key === 'consistency')!
172
172
  assert.equal(consistency.ratio, 2 / 3)
@@ -174,18 +174,18 @@ test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — 셋
174
174
  })
175
175
 
176
176
  test('한 색이 발자국을 다 덮으면 식별성은 0 이다 — 위에서 보면 한 덩어리', () => {
177
- let asset = createV3Asset('Probe')
178
- asset = addV3Box(asset, { id: 'slab', size: { x: 400, y: 20, z: 400 }, position: { x: 0, y: 10, z: 0 } })
179
- asset = addV3Box(asset, { id: 'under', size: { x: 100, y: 5, z: 100 }, position: { x: 0, y: 2.5, z: 0 } })
180
- assert.equal(v3ReportOf(asset)!.axes.find(axis => axis.key === 'readable')!.ratio, 0, '아래 부품은 가려진다')
177
+ let asset = createFigureAsset('Probe')
178
+ asset = addFigureBox(asset, { id: 'slab', size: { x: 400, y: 20, z: 400 }, position: { x: 0, y: 10, z: 0 } })
179
+ asset = addFigureBox(asset, { id: 'under', size: { x: 100, y: 5, z: 100 }, position: { x: 0, y: 2.5, z: 0 } })
180
+ assert.equal(figureReportOf(asset)!.axes.find(axis => axis.key === 'readable')!.ratio, 0, '아래 부품은 가려진다')
181
181
  })
182
182
 
183
183
  test('추이 — 1 에서 1만까지 1·2·5, 삼각형과 draw call 은 오르고 정점 버퍼와 재질은 평평하다', () => {
184
184
  assert.deepEqual(decadeSteps(1, 100), [1, 2, 5, 10, 20, 50, 100])
185
- let asset = createV3Asset('Probe')
186
- asset = addV3Box(asset, cube('a', 0))
187
- asset = addV3Box(asset, cube('b', 200))
188
- const report = v3ReportOf(asset)!
185
+ let asset = createFigureAsset('Probe')
186
+ asset = addFigureBox(asset, cube('a', 0))
187
+ asset = addFigureBox(asset, cube('b', 200))
188
+ const report = figureReportOf(asset)!
189
189
  const last = report.curve[report.curve.length - 1]!
190
190
  assert.equal(report.curve[0]!.instances, 1)
191
191
  assert.equal(last.instances, 10000)
@@ -223,7 +223,7 @@ test('radar 와 추이 그림의 조각은 svg 템플릿이다 — html 로 만
223
223
 
224
224
  test('화면이 키를 이어 붙여 찾는 말도 다섯 말 모두에 있다', () => {
225
225
  const keys = [
226
- ...V3_BUDGET_KEYS.map(key => `figure.label.budget-${key}`),
226
+ ...FIGURE_BUDGET_KEYS.map(key => `figure.label.budget-${key}`),
227
227
  ...['scale', 'consistency', 'reuse', 'detail', 'reactive', 'readable'].flatMap(key => [`figure.label.axis-${key}`, `figure.label.axis-${key}-short`]),
228
228
  'figure.text.bound-by-groups',
229
229
  'figure.text.bound-by-triangles',
@@ -237,9 +237,9 @@ test('화면이 키를 이어 붙여 찾는 말도 다섯 말 모두에 있다',
237
237
 
238
238
  test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면 몇 대 · 등급, D 면 넘친 한도를 적는다', () => {
239
239
  assert.equal(budgetOf(undefined), undefined, '잴 것이 없으면 줄도 없다')
240
- let asset = createV3Asset('Probe')
241
- asset = addV3Box(asset, cube('a', 0))
242
- const line = budgetOf(v3ReportOf(asset))!
240
+ let asset = createFigureAsset('Probe')
241
+ asset = addFigureBox(asset, cube('a', 0))
242
+ const line = budgetOf(figureReportOf(asset))!
243
243
  assert.deepEqual(
244
244
  { triangles: line.triangles, groups: line.groups, grade: line.grade },
245
245
  { triangles: 12, groups: 1, grade: 'A' }
@@ -247,17 +247,17 @@ test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면
247
247
  assert.equal(line.instances, capacityFrom(1, 12)!.instances)
248
248
 
249
249
  let glass = withPalette('glass.a', 'glass.b')
250
- glass = addV3Box(glass, cube('a', 0, 'glass.a', { transparent: true }))
251
- glass = addV3Box(glass, cube('b', 200, 'glass.b', { transparent: true }))
252
- const over = budgetOf(v3ReportOf(glass))!
250
+ glass = addFigureBox(glass, cube('a', 0, 'glass.a', { transparent: true }))
251
+ glass = addFigureBox(glass, cube('b', 200, 'glass.b', { transparent: true }))
252
+ const over = budgetOf(figureReportOf(glass))!
253
253
  assert.equal(over.grade, 'D')
254
254
  assert.deepEqual(over.tightest, { key: 'transparent', used: 2, limit: 1 })
255
255
 
256
- const parts = read('../client/modeller/figure-v3-parts.ts')
256
+ const parts = read('../client/modeller/figure-parts.ts')
257
257
  const drawn = parts.slice(parts.indexOf(' private renderBudget()'), parts.indexOf(' /** 한꺼번에 감추고'))
258
258
  assert.ok(drawn.includes("const over = b.grade === 'D'") && drawn.includes("'figure.text.parts-budget-over'"), 'D 에서 넘친 한도를 적는다')
259
259
  assert.ok(parts.includes("new CustomEvent('show-maturity'"), '누르면 성숙도로')
260
- const editor = read('../client/modeller/v3-editor.ts')
260
+ const editor = read('../client/modeller/editor.ts')
261
261
  assert.ok(editor.includes('.budget=${this.budget}') && editor.includes("@show-maturity=${() => (this.tab = 'verdict')}"))
262
262
  assert.ok(verdict.includes('budget: budgetOf(report)'), '성숙도 탭이 같은 보고서에서 알린다')
263
263
  })
@@ -15,13 +15,12 @@ test('every figure and a new one open the one modeller', () => {
15
15
  assert.equal(modellerPathOf(), 'figure-modeller')
16
16
  })
17
17
 
18
- test('the route and the shell table send both addresses to the V3 page, and no `figure-modeller-page` is registered', () => {
18
+ test('one modeller page at one address: the route and the shell table agree', () => {
19
19
  const route = read('../client/route.ts')
20
- assert.doesNotMatch(route, /figure-modeller-page/)
20
+ assert.equal(route.match(/case 'figure-modeller':/g)?.length, 1)
21
+ assert.match(route, /import\('\.\/pages\/figure-modeller-page'\)/)
21
22
  const config = read('../things-factory.config.js')
22
- assert.match(config, /tagname: 'figure-v3-modeller-page', page: 'figure-modeller'/)
23
- assert.match(config, /tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller'/)
24
- assert.doesNotMatch(config, /'figure-modeller-page'/)
23
+ assert.equal(config.match(/tagname: 'figure-modeller-page', page: 'figure-modeller'/g)?.length, 1)
25
24
  })
26
25
 
27
26
  test('the catalogue opens figures through the one path', () => {
@@ -30,7 +29,7 @@ test('the catalogue opens figures through the one path', () => {
30
29
  })
31
30
 
32
31
  test('reloading or closing the tab with unsaved work asks first — the shell does not ask canDeactivate then (09-26)', () => {
33
- const page = read('../client/pages/figure-v3-modeller-page.ts')
32
+ const page = read('../client/pages/figure-modeller-page.ts')
34
33
  const hold = page.slice(page.indexOf('private holdUnsaved'), page.indexOf('connectedCallback()'))
35
34
  assert.match(hold, /session\.read\(\)\.dirty/, 'only when there is something unsaved')
36
35
  assert.match(hold, /event\.preventDefault\(\)/)
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url'
14
14
  const RENDERS = new Set(['deployed_code', 'database', 'crop_square', 'circle', 'change_history', 'hexagon', 'check_box_outline_blank', 'crop_portrait', 'door_front'])
15
15
 
16
16
  test('every shape in the palette has an icon the font draws', () => {
17
- const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-parts.ts', import.meta.url)), 'utf8')
17
+ const parts = readFileSync(fileURLToPath(new URL('../client/modeller/figure-parts.ts', import.meta.url)), 'utf8')
18
18
  const recipes = parts.slice(parts.indexOf('const RECIPES'), parts.indexOf(']', parts.indexOf('const RECIPES')))
19
19
  const icons = [...recipes.matchAll(/icon: '([a-z_]+)'/g)].map(m => m[1]!)
20
20
  assert.ok(icons.length >= 9)
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * **편집기의 부품 목록** (2026-09-24).
3
3
  *
4
- * `figure-v3-parts` 는 지어졌지만(fdc15e162e) 편집기 어디에도
4
+ * `figure-parts` 는 지어졌지만(fdc15e162e) 편집기 어디에도
5
5
  * 걸리지 않았다. 편집기는 맨 단추 네 개와 이름 단추 목록이었다. 패널이 내는 사건마다 받는 자리가 있어야 한다 —
6
6
  * 없으면 눌러도 아무 일이 없다.
7
7
  */
@@ -11,14 +11,14 @@ import { readFileSync } from 'node:fs'
11
11
  import { fileURLToPath } from 'node:url'
12
12
 
13
13
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
14
- const editor = read('../client/modeller/v3-editor.ts')
15
- const panel = read('../client/modeller/figure-v3-parts.ts')
14
+ const editor = read('../client/modeller/editor.ts')
15
+ const panel = read('../client/modeller/figure-parts.ts')
16
16
 
17
17
  test('패널이 내는 사건마다 편집기에 받는 자리가 있다', () => {
18
18
  const asked = new Set([...panel.matchAll(/this\.ask\('([a-z-]+)'/g)].map(m => m[1]))
19
19
  for (const name of [...panel.matchAll(/op\('([a-z-]+)'/g)].map(m => m[1])) asked.add(name)
20
20
  assert.ok(asked.size >= 8, `전제: 패널의 사건을 읽었다 (${[...asked].join(', ')})`)
21
- const listener = editor.slice(editor.indexOf('<figure-v3-parts'), editor.indexOf('</figure-v3-parts>'))
21
+ const listener = editor.slice(editor.indexOf('<figure-parts'), editor.indexOf('</figure-parts>'))
22
22
  for (const name of asked) assert.ok(listener.includes(`@${name}=`), `${name} 을 받는 자리가 없다`)
23
23
  })
24
24
 
@@ -37,7 +37,7 @@ test('작업대에서 가린 부품은 캔버스에서 빠지고, 다른 도형
37
37
  })
38
38
 
39
39
  test('지울 수 없는 부품은 사람의 말로 거절하고, 지우며 풀어 준 것은 사람의 말로 알린다 — 다섯 말 모두', () => {
40
- /* Since 2026-09-25 the reason is told apart (partRefusalOf, tested in v3-refusals); the editor hands the refusal to it. */
40
+ /* Since 2026-09-25 the reason is told apart (partRefusalOf, tested in refusals); the editor hands the refusal to it. */
41
41
  assert.match(editor, /private removePart[\s\S]*?partRefusalOf\(error, id,/)
42
42
  assert.match(editor, /private removePart[\s\S]*?this\.notice = removalNoticeOf\(view\.removal,/, '지운 뒤 풀어 준 것을 알린다')
43
43
  for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
@@ -7,9 +7,9 @@ import assert from 'node:assert/strict'
7
7
  import { readFileSync } from 'node:fs'
8
8
  import { fileURLToPath } from 'node:url'
9
9
 
10
- import { addV3Box, createV3Asset } from '@hatiolab/figure-model'
10
+ import { addFigureBox, createFigureAsset } from '@hatiolab/figure-model'
11
11
 
12
- import { authoredSizeOf, PREVIEW_WAYS, previewClampsOf, previewInstances } from '../client/modeller/v3-preview-model.ts'
12
+ import { authoredSizeOf, PREVIEW_WAYS, previewClampsOf, previewInstances } from '../client/modeller/preview-model.ts'
13
13
 
14
14
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
15
15
 
@@ -26,31 +26,31 @@ test('여덟 — 기준과 일곱 조합, 한 줄에 넷, 늘린 축만 두 배
26
26
  })
27
27
 
28
28
  test('크기를 선언하지 않은 도형은 부품이 차지하는 범위를 기준 크기로', () => {
29
- const asset = addV3Box(createV3Asset('Fixed'), { id: 'body', size: { x: 300, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
29
+ const asset = addFigureBox(createFigureAsset('Fixed'), { id: 'body', size: { x: 300, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
30
30
  assert.deepEqual(authoredSizeOf(asset), { x: 300, y: 200, z: 100 })
31
31
  })
32
32
 
33
33
  test('편집 | 미리보기 — 띠에서 오가고, 미리보기는 도면처럼 등록해 세우며 제 타입으로 남의 도면을 건드리지 않는다', () => {
34
- const editor = read('../client/modeller/v3-editor.ts')
34
+ const editor = read('../client/modeller/editor.ts')
35
35
  assert.ok(editor.includes("(['edit', 'preview'] as const).map("))
36
- assert.ok(editor.includes('<figure-v3-preview'))
37
- const preview = read('../client/modeller/figure-v3-preview.ts')
38
- assert.ok(preview.includes('registerFigureV3(type, asset)') && preview.includes('unregisterFigureV3(this.registered)'))
39
- assert.ok(preview.includes('`figure-v3-preview-${this.context || \'new\'}`'))
36
+ assert.ok(editor.includes('<figure-preview'))
37
+ const preview = read('../client/modeller/figure-preview.ts')
38
+ assert.ok(preview.includes('registerFigure(type, asset)') && preview.includes('unregisterFigure(this.registered)'))
39
+ assert.ok(preview.includes('`figure-preview-${this.context || \'new\'}`'))
40
40
  assert.ok(preview.includes("kind: 'set-state-defaults'"), '이 자세를 기본으로')
41
41
  assert.ok(preview.includes('dialShape('), '각도는 다이얼로')
42
42
  })
43
43
 
44
44
  test('미리보기 — 선언하지 않은 크기를 묻는 변형은 그려지는 크기를 말한다(높이만 따르는 조명탑, 아키텍트 판정 09-25)', async () => {
45
45
  const fm = await import('@hatiolab/figure-model')
46
- let a = fm.addV3Box(fm.createV3Asset('Tower'), { id: 'mast', size: { x: 200, y: 2000, z: 300 }, position: { x: 0, y: 0, z: 0 } })
47
- a = fm.applyV3Authoring(a, { kind: 'attach', part: 'mast', face: 'bottom', to: { plane: 'mounting-plane' } })
48
- a = fm.applyV3Authoring(a, { kind: 'declare-instance-size', range: { x: { min: 200, max: 200 }, y: { min: 1000, max: 3000 }, z: { min: 300, max: 300 } } })
46
+ let a = fm.addFigureBox(fm.createFigureAsset('Tower'), { id: 'mast', size: { x: 200, y: 2000, z: 300 }, position: { x: 0, y: 0, z: 0 } })
47
+ a = fm.applyFigureAuthoring(a, { kind: 'attach', part: 'mast', face: 'bottom', to: { plane: 'mounting-plane' } })
48
+ a = fm.applyFigureAuthoring(a, { kind: 'declare-instance-size', range: { x: { min: 200, max: 200 }, y: { min: 1000, max: 3000 }, z: { min: 300, max: 300 } } })
49
49
  const clamps = previewClampsOf(a, authoredSizeOf(a))
50
50
  assert.deepEqual(clamps['base'], [], '기준은 그대로')
51
51
  assert.deepEqual(clamps['x'], [{ axis: 'x', asked: 400, used: 200 }])
52
52
  assert.deepEqual(clamps['y'], [{ axis: 'y', asked: 4000, used: 3000 }], '높이도 선언 최대까지')
53
53
  assert.deepEqual(clamps['x·y·z'].map(c => c.axis), ['x', 'y', 'z'])
54
- const view = readFileSync(fileURLToPath(new URL('../client/modeller/figure-v3-preview.ts', import.meta.url)), 'utf8')
54
+ const view = readFileSync(fileURLToPath(new URL('../client/modeller/figure-preview.ts', import.meta.url)), 'utf8')
55
55
  assert.ok(view.includes("i18next.t('figure.text.preview-clamped'"), '범례에 적는다')
56
56
  })