@things-factory/figure-ui 10.1.44 → 10.1.45

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 (128) hide show
  1. package/client/modeller/figure-v3-links.ts +303 -0
  2. package/client/modeller/modeller-route.ts +6 -21
  3. package/client/modeller/v3-editor.ts +44 -3
  4. package/client/modeller/v3-gizmo.ts +13 -2
  5. package/client/modeller/v3-links.ts +138 -0
  6. package/client/pages/figure-v3-modeller-page.ts +2 -5
  7. package/client/route.ts +2 -6
  8. package/dist-client/modeller/figure-v3-links.d.ts +42 -0
  9. package/dist-client/modeller/figure-v3-links.js +306 -0
  10. package/dist-client/modeller/figure-v3-links.js.map +1 -0
  11. package/dist-client/modeller/modeller-route.d.ts +4 -11
  12. package/dist-client/modeller/modeller-route.js +5 -22
  13. package/dist-client/modeller/modeller-route.js.map +1 -1
  14. package/dist-client/modeller/v3-editor.d.ts +5 -0
  15. package/dist-client/modeller/v3-editor.js +46 -2
  16. package/dist-client/modeller/v3-editor.js.map +1 -1
  17. package/dist-client/modeller/v3-gizmo.js +11 -1
  18. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  19. package/dist-client/modeller/v3-links.d.ts +54 -0
  20. package/dist-client/modeller/v3-links.js +110 -0
  21. package/dist-client/modeller/v3-links.js.map +1 -0
  22. package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -5
  23. package/dist-client/pages/figure-v3-modeller-page.js +2 -5
  24. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  25. package/dist-client/route.d.ts +1 -1
  26. package/dist-client/route.js +2 -5
  27. package/dist-client/route.js.map +1 -1
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +4 -4
  30. package/test/ai-proposal-contract.test.ts +0 -88
  31. package/test/figure-source.test.ts +0 -102
  32. package/test/i18n-prefix-guard.test.ts +0 -24
  33. package/test/modeller-route.test.ts +14 -25
  34. package/test/part-edits.test.ts +0 -7
  35. package/test/scene-budget.test.ts +0 -7
  36. package/test/v3-gizmo.test.ts +10 -0
  37. package/test/v3-links.test.ts +123 -0
  38. package/things-factory.config.js +2 -5
  39. package/translations/en.json +6 -0
  40. package/translations/ja.json +6 -0
  41. package/translations/ko.json +6 -0
  42. package/translations/ms.json +6 -0
  43. package/translations/zh.json +6 -0
  44. package/client/modeller/angle-dial.ts +0 -70
  45. package/client/modeller/base-box.ts +0 -175
  46. package/client/modeller/figure-animations.ts +0 -1249
  47. package/client/modeller/figure-canvas.ts +0 -567
  48. package/client/modeller/figure-capabilities.ts +0 -379
  49. package/client/modeller/figure-inspector.ts +0 -2208
  50. package/client/modeller/figure-parts.ts +0 -479
  51. package/client/modeller/figure-preview.ts +0 -598
  52. package/client/modeller/figure-report.ts +0 -966
  53. package/client/modeller/figure-settings.ts +0 -328
  54. package/client/modeller/figure-side.ts +0 -272
  55. package/client/modeller/figure-thumbnail.ts +0 -123
  56. package/client/modeller/figure-view.ts +0 -152
  57. package/client/modeller/joint-edits.ts +0 -262
  58. package/client/modeller/joint-gizmo.ts +0 -180
  59. package/client/modeller/maturity.ts +0 -288
  60. package/client/modeller/new-figure-entry.ts +0 -4
  61. package/client/modeller/pose-defaults.ts +0 -67
  62. package/client/pages/figure-modeller-page.ts +0 -2082
  63. package/dist-client/modeller/angle-dial.d.ts +0 -32
  64. package/dist-client/modeller/angle-dial.js +0 -41
  65. package/dist-client/modeller/angle-dial.js.map +0 -1
  66. package/dist-client/modeller/base-box.d.ts +0 -71
  67. package/dist-client/modeller/base-box.js +0 -110
  68. package/dist-client/modeller/base-box.js.map +0 -1
  69. package/dist-client/modeller/figure-animations.d.ts +0 -159
  70. package/dist-client/modeller/figure-animations.js +0 -1132
  71. package/dist-client/modeller/figure-animations.js.map +0 -1
  72. package/dist-client/modeller/figure-canvas.d.ts +0 -150
  73. package/dist-client/modeller/figure-canvas.js +0 -506
  74. package/dist-client/modeller/figure-canvas.js.map +0 -1
  75. package/dist-client/modeller/figure-capabilities.d.ts +0 -42
  76. package/dist-client/modeller/figure-capabilities.js +0 -349
  77. package/dist-client/modeller/figure-capabilities.js.map +0 -1
  78. package/dist-client/modeller/figure-inspector.d.ts +0 -329
  79. package/dist-client/modeller/figure-inspector.js +0 -2095
  80. package/dist-client/modeller/figure-inspector.js.map +0 -1
  81. package/dist-client/modeller/figure-parts.d.ts +0 -64
  82. package/dist-client/modeller/figure-parts.js +0 -478
  83. package/dist-client/modeller/figure-parts.js.map +0 -1
  84. package/dist-client/modeller/figure-preview.d.ts +0 -138
  85. package/dist-client/modeller/figure-preview.js +0 -549
  86. package/dist-client/modeller/figure-preview.js.map +0 -1
  87. package/dist-client/modeller/figure-report.d.ts +0 -165
  88. package/dist-client/modeller/figure-report.js +0 -952
  89. package/dist-client/modeller/figure-report.js.map +0 -1
  90. package/dist-client/modeller/figure-settings.d.ts +0 -39
  91. package/dist-client/modeller/figure-settings.js +0 -317
  92. package/dist-client/modeller/figure-settings.js.map +0 -1
  93. package/dist-client/modeller/figure-side.d.ts +0 -78
  94. package/dist-client/modeller/figure-side.js +0 -317
  95. package/dist-client/modeller/figure-side.js.map +0 -1
  96. package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
  97. package/dist-client/modeller/figure-thumbnail.js +0 -95
  98. package/dist-client/modeller/figure-thumbnail.js.map +0 -1
  99. package/dist-client/modeller/figure-view.d.ts +0 -118
  100. package/dist-client/modeller/figure-view.js +0 -78
  101. package/dist-client/modeller/figure-view.js.map +0 -1
  102. package/dist-client/modeller/joint-edits.d.ts +0 -49
  103. package/dist-client/modeller/joint-edits.js +0 -223
  104. package/dist-client/modeller/joint-edits.js.map +0 -1
  105. package/dist-client/modeller/joint-gizmo.d.ts +0 -34
  106. package/dist-client/modeller/joint-gizmo.js +0 -158
  107. package/dist-client/modeller/joint-gizmo.js.map +0 -1
  108. package/dist-client/modeller/maturity.d.ts +0 -55
  109. package/dist-client/modeller/maturity.js +0 -212
  110. package/dist-client/modeller/maturity.js.map +0 -1
  111. package/dist-client/modeller/new-figure-entry.d.ts +0 -2
  112. package/dist-client/modeller/new-figure-entry.js +0 -5
  113. package/dist-client/modeller/new-figure-entry.js.map +0 -1
  114. package/dist-client/modeller/pose-defaults.d.ts +0 -9
  115. package/dist-client/modeller/pose-defaults.js +0 -35
  116. package/dist-client/modeller/pose-defaults.js.map +0 -1
  117. package/dist-client/pages/figure-modeller-page.d.ts +0 -419
  118. package/dist-client/pages/figure-modeller-page.js +0 -2060
  119. package/dist-client/pages/figure-modeller-page.js.map +0 -1
  120. package/test/angle-dial.test.ts +0 -78
  121. package/test/base-box.test.ts +0 -235
  122. package/test/joint-edits.test.ts +0 -165
  123. package/test/joint-gizmo.test.ts +0 -174
  124. package/test/joint-needle.test.ts +0 -162
  125. package/test/modeller-undo-redo.test.ts +0 -33
  126. package/test/new-figure-entry.test.ts +0 -15
  127. package/test/pose-defaults.test.ts +0 -71
  128. package/test/preview-board-depth.test.ts +0 -27
@@ -1 +0,0 @@
1
- {"version":3,"file":"figure-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../modeller/figure-side.js'\nimport '../modeller/figure-canvas.js'\nimport { baseFittingParts, partsSettledOnFloor, resizeBase as resizedToFit } from '../modeller/base-box.js'\nimport { captureThumbnail } from '../modeller/figure-thumbnail.js'\nimport '../modeller/figure-preview.js'\nimport { shouldStartNewFigure } from '../modeller/new-figure-entry.js'\nimport '../modeller/figure-parts.js'\nimport '../modeller/figure-inspector.js'\n\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { consumePendingFigureProposal } from '../modeller/figure-ai-target.js'\nimport { contractChanges, isPlaceholderType, placeholderType, TYPE_LENGTH, TYPE_PATTERN, validate } from '@hatiolab/figure-model'\nimport type { ContractChange, DetailLevel, FigureSource, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport * as edits from '../modeller/part-edits.js'\nimport * as proposals from '../modeller/proposal.js'\nimport * as proposalSessions from '../modeller/proposal-session.js'\nimport type { ProposalSession } from '../modeller/proposal-session.js'\nimport { toFigureSource, fromFigureSource } from '../modeller/figure-source.js'\nimport type { FigureDraft, PartModel, PlaneAxis } from '../modeller/figure-source.js'\nimport { jointOf } from '../modeller/joint-edits.js'\nimport { poseAsDefaults } from '../modeller/pose-defaults.js'\nimport { gizmoPart } from '../modeller/joint-gizmo.js'\nimport { DEFAULT_VIEW } from '../modeller/figure-view.js'\nimport type { ViewSettings } from '../modeller/figure-view.js'\nimport { createFigure, fetchFigure, fetchFigureTypeNames, fetchReleasedSource, updateFigure } from '../graphql/index.js'\nimport type { Figure, FigureFinding, FigureGateFix, FigureGateFixPlan, FigureProposal, ProposalFeedback } from '../types.js'\nimport { isV3Source, modellerPathOf } from '../modeller/modeller-route.js'\n\nconst FigureModellerPageBase = localize(i18next)(PageView) as typeof PageView\n\n/**\n * 어느 칸이 접혀 있나. 왼쪽은 부품 목록, 오른쪽은 속성·판정·설정이다.\n *\n * 이름에 fold 를 안 쓴다. 이 파일에서 그 낱말은 이미 fold.ts 의 것이다 — 편집 상태를\n * 저장 형식으로 접는다는 뜻이라 칸을 접는 것과 아무 상관이 없다. 한 파일에서 한 낱말이\n * 두 가지를 뜻하면 읽는 사람이 매번 어느 쪽인지 가려야 한다.\n */\ninterface Panes {\n left: boolean\n right: boolean\n}\n\n/**\n * 접어 둔 것은 **이 브라우저에만** 남는다.\n *\n * 저작한 Figure 에 실리는 값이 아니다. 화면을 어떻게 벌려 놓고 일하느냐는 사람마다\n * 다르고, 같은 Figure 를 다른 사람이 열었을 때 내 화면 배치가 따라가면 곤란하다.\n * 화면 설정(`figure-view`)을 저장하지 않는 것과 같은 이유다.\n *\n * 시크릿 창이나 사이트 데이터를 막은 브라우저에서는 **읽는 것 자체가 던진다.** 그때는\n * 둘 다 펴진 채로 시작한다 — 저작면이 안 뜨는 것보다 낫다.\n */\nconst PANES_KEY = 'figure-modeller.collapsed'\n\nfunction recallPanes(): Panes {\n try {\n const said = JSON.parse(localStorage.getItem(PANES_KEY) ?? 'null')\n return { left: said?.left === true, right: said?.right === true }\n } catch {\n return { left: false, right: false }\n }\n}\n\nfunction rememberPanes(panes: Panes): void {\n try {\n localStorage.setItem(PANES_KEY, JSON.stringify(panes))\n } catch {\n // 못 적어도 이번 화면은 그대로 동작한다. 다음에 열 때 펴져 있을 뿐이다.\n }\n}\n\n/**\n * 새로 만든 Figure 의 시작 상태 — **빈 상자 하나뿐이다.**\n *\n * 전에는 부품(300 상자)을 하나 놓아 주었다. 형식이 부품 0개를 오류로 보기 때문이다\n * (`missing-parts` — parts 가 없다). 그래서 아무것도 안 했는데 붉은 판정이 뜨는 것을 피하려고\n * 상자를 깔았다.\n *\n * **그런데 「무조건 상자」는 대개 첫 동작이 그것을 지우는 일이 된다** — 컨베이어를 만들 사람도,\n * 시그널타워를 만들 사람도 상자부터 치운다. 그래서 비운다. 형식은 그대로 두고(부품 없는 것은\n * 발행할 수 없는 것이 맞다) **화면이 「아직 저작 안 함」과 「형식을 어김」을 가른다.**\n *\n * 빈 화면이 가르치는 자리다: 부품 목록이 「위에서 도형을 골라 시작하세요」를 이미 말하고 팔레트가\n * 바로 그 위에 있다(`figure-parts`).\n */\nfunction startingModel(type: string): { draft: FigureDraft; parts: PartModel[] } {\n return {\n draft: { width: 300, height: 300, depth: 300, figureType: type, detailLevel: 'M' },\n parts: []\n }\n}\n\n/**\n * Figure 저작면.\n *\n * ## 세 칸이다\n *\n * 왼쪽 도형 팔레트 · 부품 목록 · 속성\n * 가운데 미리보기 — `FigureInstance` 를 실제로 세운다\n * 오른쪽 판단 — 늘 떠 있다\n *\n * 미리보기가 씬 위에서 실제로 동작하는 것이 이 도구의 전제다. **편집한 것이 곧 실제 결과물**이라야\n * \"모델러에서는 되는데 도면에서는 다르다\"가 생기지 않는다.\n *\n * ## 편집 데이터는 씬 모델 하나뿐이다\n *\n * 이 페이지가 갖는 것은 초안 하나와 부품 목록 — 미리보기에 그대로 세워지는 씬 모델이다.\n * 저장 형식(`FigureSource`)으로 바꾸는 것은 **경계에서만** 한다: 저장할 때, 판단할 때,\n * AI 후보와 비교할 때. 그 변환은 `figure-source.ts` 한 곳에서만 일어난다.\n *\n * 팔레트도 속성 편집기도 이 데이터를 직접 수정하지 않는다. 무엇을 하겠다는 것만 알리고,\n * 바꾸는 것은 이 페이지다. 두 곳에서 고치면 실행 취소도 저장도 어느 쪽이 맞는지 알 수\n * 없게 된다.\n */\n/**\n * Whether this name could be released, by the format's own rules (`type-not-identifier`,\n * `type-is-placeholder`). Saving is not blocked, but the type cannot change after creation, so the\n * author has to hear it now to avoid a draft that can never be released.\n */\nfunction releasableType(type: string): boolean {\n return (\n !isPlaceholderType(type) &&\n TYPE_PATTERN.test(type) &&\n type.length >= TYPE_LENGTH.min &&\n type.length <= TYPE_LENGTH.max\n )\n}\n\n@customElement('figure-modeller-page')\nexport class FigureModellerPage extends FigureModellerPageBase {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n /*\n 이 화면의 글자 크기를 여기서 정한다.\n\n 안 적어 두면 브라우저 기본 크기(16px)를 물려받는다. 아래 패널들은 전부 12px\n 언저리라 위 두 줄만 커 보이고, 버튼이 화면 전체와 따로 놀았다.\n\n form 요소(input · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n */\n font-size: 0.76rem;\n }\n\n /* 세 칸. 3D 를 가장 크게 둔다 — 이 도구의 주어는 형상이지 속성표가 아니다 */\n div[edits] {\n flex: 1;\n display: grid;\n grid-template-columns: 220px 1fr 320px;\n min-height: 0;\n overflow: hidden;\n }\n\n /*\n **세 칸의 자리를 못 박는다.**\n\n 이것이 없으면 왼쪽을 접었을 때 오른쪽 칸이 가운데 자리로 밀려 들어와 캔버스가\n 통째로 사라진다. display none 인 형제는 그리드 자리를 차지하지 않으므로, 첫째가\n 빠지면 나머지가 한 칸씩 당겨진다. 오른쪽은 마지막이라 빠져도 밀 것이 없어서\n 멀쩡했다 — 한쪽만 고장 나는 종류다.\n\n 이 파일이 이미 한 번 적어 둔 함정이다. 아래 div[lanes] 주석에 같은 말이 있다.\n */\n div[edits] > figure-parts {\n grid-column: 1;\n }\n div[edits] > div[middle] {\n grid-column: 2;\n }\n div[edits] > figure-side {\n grid-column: 3;\n }\n\n /*\n 접힌 칸은 **자리를 아예 안 쓴다.**\n\n 폭만 0 으로 줄이면 안쪽이 넘쳐 나와 가운데를 덮는다. 그렇다고 떼어 내면 오른쪽\n 칸이 펼쳐 둔 추이 그림 같은 상태를 잃는다 — figure-side 가 탭을 감출 때도 DOM 에는\n 남기는 것과 같은 이유다. display none 이 둘을 다 지킨다.\n\n 가운데는 접을 때마다 넓이가 바뀌는데, 편집 캔버스와 미리보기가 각자 ResizeObserver\n 를 갖고 있어 3D 가 알아서 따라온다.\n */\n div[edits][collapse-left] {\n grid-template-columns: 0 1fr 320px;\n }\n div[edits][collapse-right] {\n grid-template-columns: 220px 1fr 0;\n }\n div[edits][collapse-left][collapse-right] {\n grid-template-columns: 0 1fr 0;\n }\n div[edits][collapse-left] > figure-parts,\n div[edits][collapse-right] > figure-side {\n display: none;\n }\n\n div[spacer] {\n flex: 1;\n }\n\n /*\n 칸을 벡터 저작도구의 관례대로 놓는다 — 왼쪽에 무엇이 있나(레이어), 오른쪽에\n 고른 것의 속성. 쓰는 사람이 이미 아는 자리라 배울 것이 하나 줄어든다.\n 차별성은 배치가 아니라 가운데의 로우폴리 3D 에 있으므로, 배치로 낯설게 하지\n 않는다.\n\n 오른쪽은 figure-side 가 속성과 판단을 탭으로 겹쳐 둔다. 위아래로 나누면 둘 다\n 잘려서 속성은 재질까지, 판단은 무게까지 못 내려간다. 개수는 탭 이름표에 남아\n 판단을 덮어 두어도 보인다.\n */\n figure-side {\n min-height: 0;\n }\n\n figure-canvas,\n figure-preview {\n min-width: 0;\n min-height: 0;\n }\n\n /*\n 편집 · 미리보기 전환 및 조작 띠.\n\n 가운데 칸 위에 얇게 둔다. 무엇을 보고 있는지가 늘 보여야 하고, 두 화면이 같은\n 자리를 쓰므로 어느 쪽인지 모르면 편집이 안 먹는 것처럼 읽힌다.\n */\n div[modes] {\n display: flex;\n align-items: center;\n flex: none;\n gap: 2px;\n padding: 4px var(--spacing-medium, 8px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n div[modes] button[mode] {\n padding: 3px 12px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n div[modes] button[mode]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n div[modes] button[mode][on] {\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n div[modes] div[separator] {\n width: 1px;\n height: 16px;\n margin: 0 4px;\n background-color: var(--md-sys-color-outline-variant);\n }\n div[modes] button[history] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n div[modes] button[history]:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container);\n color: var(--md-sys-color-on-surface);\n }\n div[modes] button[history][disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[modes] button[history] md-icon {\n --md-icon-size: 18px;\n }\n div[modes] button[collapse] {\n display: flex;\n align-items: center;\n flex: none;\n padding: 2px 3px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n div[modes] button[collapse]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n div[modes] button[collapse] md-icon {\n --md-icon-size: 18px;\n }\n div[modes] span[dirty] {\n align-self: center;\n margin-right: 6px;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-tertiary);\n }\n div[modes] span[save-failed] {\n align-self: center;\n max-width: 240px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-right: 6px;\n padding: 3px 7px;\n font: var(--label-font, inherit);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-error-container);\n border-radius: 4px;\n }\n div[modes] button[save] {\n padding: 4px 14px;\n margin-right: 4px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n div[modes] button[save]:hover:not([disabled]) {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n }\n div[modes] button[save][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n /*\n 후보가 오면 가운데를 둘로 나눈다.\n\n 숫자와 JSON 으로 승낙을 받지 않는다 — 저작한 것이 곧 실물이므로 **둘 다 세워\n 놓고** 고르게 한다. 나란히 두지 않으면 무엇이 달라졌는지 기억으로 견줘야 한다.\n */\n /*\n 세로로 쌓아 둔다. 그리드로 두었더니 후보가 없을 때 미리보기가 첫 줄(auto)에\n 떨어져 **높이가 0** 이 됐다 — 3D 가 통째로 사라졌다. display:none 인 형제는\n 그리드 자리를 차지하지 않는다.\n */\n div[middle] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n }\n /*\n 가운데를 채운다. 둘 다 적어야 한다 — 하나만 적으면 감춰졌다 나타난 쪽이 높이 0 이\n 되어 화면이 하얗게 뜬다. 실제로 그렇게 났다.\n */\n div[middle] > figure-canvas,\n div[middle] > figure-preview {\n flex: 1;\n }\n div[lanes] {\n flex: 1;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1px;\n min-height: 0;\n background-color: var(--md-sys-color-outline-variant);\n }\n div[lane] {\n display: grid;\n grid-template-rows: auto 1fr;\n min-width: 0;\n min-height: 0;\n background-color: var(--md-sys-color-surface);\n }\n div[lane] h4 {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n padding: 5px var(--spacing-medium, 8px);\n font: var(--label-font, inherit);\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[lane][candidate] h4 {\n color: var(--md-sys-color-primary);\n }\n div[lane] h4 small {\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n\n /* 받거나 버린다. 그 사이에 다른 것은 없다 */\n div[decide] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-primary-container);\n color: var(--md-sys-color-on-primary-container);\n font-size: 0.76rem;\n }\n div[decide] div[spacer] {\n flex: 1;\n }\n div[decide] button {\n padding: 5px 14px;\n font: var(--label-font, inherit);\n border-radius: 6px;\n cursor: pointer;\n }\n button[take] {\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n border: none;\n }\n button[drop] {\n color: inherit;\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n }\n button[take][disabled] {\n opacity: 0.4;\n cursor: default;\n }\n\n /* AI의 평가는 판정문이 아니라 수락 전에 읽을 근거다. 숨기지 않고 변경 칩 위에 둔다. */\n div[quality-review] {\n padding: 7px var(--spacing-large, 12px);\n background-color: var(--md-sys-color-primary-container);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-primary-container);\n font-size: 0.72rem;\n line-height: 1.45;\n }\n div[quality-review][review] {\n background-color: var(--md-sys-color-tertiary-container);\n color: var(--md-sys-color-on-tertiary-container);\n }\n div[quality-review] strong { display: block; font-size: 0.74rem; }\n div[quality-review] ul { margin: 4px 0 0; padding-left: 18px; }\n div[proposal-note] {\n padding: 6px var(--spacing-large, 12px) 8px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface-container-low);\n }\n div[proposal-note] label { display: block; font-size: 0.71rem; color: var(--md-sys-color-on-surface-variant); }\n div[proposal-note] input {\n box-sizing: border-box;\n width: 100%;\n margin-top: 4px;\n padding: 5px 7px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 5px;\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface);\n font: inherit;\n }\n\n /*\n 무엇을 바꾸자는 것인가 — 칩 하나가 변경 하나다.\n\n 통째로만 받게 두면 「몸통은 좋은데 모터 자리가 틀렸다」에 답할 수 없다. 그게\n 가장 흔한 반응이다. 처음에는 전부 골라 둔다 — 통째로 받는 것이 보통의 길이고,\n 빼고 싶은 것만 끄면 된다.\n */\n div[changes] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);\n background-color: var(--md-sys-color-primary-container);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[change] {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 9px 2px 5px;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 999em;\n cursor: pointer;\n }\n button[change][on] {\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n border-color: transparent;\n }\n button[change] md-icon {\n --md-icon-size: 14px;\n }\n /* 안 고른 것은 글자에 줄을 그어 「안 받는다」를 말한다 */\n button[change]:not([on]) span[key] {\n text-decoration: line-through;\n }\n\n /* 고른 조합이 형식을 어기면 미리 알린다. 선택한 뒤에 거절당하는 것보다 낫다 */\n /* 정책을 넘은 것은 막지 않는다 — 알리기만 한다. 색으로 갈라 둔다 */\n p[blocked][warn] {\n color: var(--md-sys-color-tertiary);\n }\n p[blocked] {\n margin: 0;\n padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);\n background-color: var(--md-sys-color-primary-container);\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n .save-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(2px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n }\n .save-modal-card {\n background: var(--md-sys-color-surface-container-high, #fff);\n color: var(--md-sys-color-on-surface, #1f1f1f);\n border-radius: 12px;\n padding: 24px;\n width: 380px;\n max-width: 90vw;\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.28);\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n .save-modal-title {\n margin: 0;\n font-size: 1.15rem;\n font-weight: 700;\n }\n .save-modal-desc {\n margin: 0;\n font-size: 0.8rem;\n color: var(--md-sys-color-on-surface-variant, #666);\n }\n .save-modal-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .save-modal-field label {\n font-size: 0.76rem;\n font-weight: 600;\n color: var(--md-sys-color-on-surface-variant, #555);\n }\n .save-modal-field input {\n padding: 8px 10px;\n border: 1px solid var(--md-sys-color-outline, #ccc);\n border-radius: 6px;\n font-size: 0.88rem;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n }\n .save-modal-error {\n color: var(--md-sys-color-error, #b00020);\n font-size: 0.78rem;\n }\n .save-modal-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 8px;\n }\n .save-modal-actions button {\n padding: 6px 14px;\n border-radius: 6px;\n font-size: 0.82rem;\n cursor: pointer;\n border: none;\n }\n .save-modal-actions .btn-cancel {\n background: none;\n color: var(--md-sys-color-on-surface, inherit);\n }\n .save-modal-actions .btn-confirm {\n background: var(--md-sys-color-primary, #0066cc);\n color: var(--md-sys-color-on-primary, #fff);\n font-weight: 600;\n }\n .save-modal-actions .btn-confirm:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n `\n\n /** 편집 중인 Figure. 새로 만드는 중이면 `id` 가 없다. */\n @state() private figure?: Figure\n /** 편집 데이터 — 미리보기에 그대로 세워지는 씬 모델이다. */\n @state() private draft?: FigureDraft\n @state() private parts: PartModel[] = []\n @state() private selected = -1\n /*\n 저작 화면의 **보기 설정**. 조명 · 바닥 · 축 가이드 같은 것이다.\n\n 자산이 아니라 작업대라 저장되지 않는다 — `toFigureSource` 가 집어 가지 않는다.\n 자세한 것은 `modeller/figure-view.ts` 를 보라.\n */\n @state() private view: ViewSettings = DEFAULT_VIEW\n /**\n * 가운데 칸이 무엇을 보이나.\n *\n * 편집 캔버스는 늘 기준 크기다. 그런데 이 형식의 값 절반은 크기가 달라졌을 때 어떻게\n * 반응하는가에 있다. 그것을 볼 자리가 없었다.\n */\n @state() private mode: 'edit' | 'preview' = 'edit'\n /**\n * 어느 칸이 접혀 있나.\n *\n * 통째로 갈아 끼운다 — 안쪽 값만 바꾸면 Lit 이 같은 객체로 보고 다시 그리지 않는다.\n */\n @state() private collapsed: Panes = recallPanes()\n @state() private dirty = false\n @state() private saving = false\n @state() private violations: FigureFinding[] = []\n /**\n * 저장이 왜 안 됐나.\n *\n * **`save()` 에 `catch` 가 없었다.** `try … finally` 뿐이라 서버가 거절하면(형식 위반 · 타입 이름\n * 중복 · 권한 없음 · 네트워크) 단추만 멈추고 화면은 **아무 말도 하지 않았다.** 저작자는 눌렀는데\n * 아무 일도 안 난 것으로 보고, 저장됐는지 아닌지도 알 수 없었다.\n *\n * 미저장 표시(`dirty`)는 그대로 남으므로 데이터를 잃지는 않았다. 잃은 것은 **사유**다.\n */\n @state() private saveFailure = ''\n @state() private sourceLoadFailure = ''\n @state() private showSaveModal = false\n @state() private saveModalType = ''\n @state() private saveModalName = ''\n @state() private saveModalError = ''\n /** 저작 보조 또는 발행 게이트가 연 후보 검토 세션. 받기 전에는 정본이 아니다. */\n @state() private proposalSession?: ProposalSession\n /** 인라인 요청창과 AI 도크가 함께 쓰는 현재 Figure의 제한된 세션 피드백. */\n @state() private proposalFeedback: ProposalFeedback[] = []\n\n @state() private undoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []\n @state() private redoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []\n @state() private sceneRevision = 0\n private lastCoalesceKey?: string\n private lastHistoryTime = 0\n\n /**\n * 저장 형식으로 변환한 것. 씬 모델이 바뀔 때만 다시 만든다.\n *\n * 저장·판단·AI 후보 비교가 이것을 쓴다. 렌더링할 때마다 새로 만들면 판단 패널이 매번\n * 다시 계산하므로, 변환은 `willUpdate` 에서 한 번만 한다.\n */\n private folded?: FigureSource\n\n /**\n * 직전 발행판의 정본. 한 번도 발행 안 한 도형에는 없다.\n *\n * 발행된 도형을 고치는 동안 「이 고침이 이미 놓인 것을 깨나」를 견주려면 이것이 있어야 한다.\n * 저장 전 초안은 서버가 모르므로 판정도 여기서 한다 — 형식의 같은 함수를 쓴다(ADR-0063 결정 3).\n */\n @state() private releasedSource?: FigureSource\n\n /** 지금 고친 것이 놓인 것에 무엇을 하나. 발행 전에는 비어 있다. */\n @state() private contract: ContractChange[] = []\n\n /** 견주지 못한 까닭. **비어 있는 목록과 다르다** — 「깨지 않는다」가 아니라 「모른다」다. */\n @state() private contractFailure?: string\n\n willUpdate(changed: Map<string, unknown>) {\n if (changed.has('draft') || changed.has('parts') || changed.has('releasedSource')) {\n this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined\n this.contract = this.compareWithReleased()\n }\n }\n\n /**\n * 고치는 중의 정본은 **잠깐 형식을 못 지날 수 있다** — 부품 이름을 지웠다가 다시 적는 사이가\n * 그렇다. 그때 견주기가 던지면 화면이 통째로 멈추므로 목록은 비운다.\n *\n * **빈 목록을 「깨지 않는다」로 보이게 두지 않는다.** 까닭을 함께 들고 있다가 화면이 「확인하지\n * 못했다」로 말한다. 조용히 비우면 저작자는 안전하다고 읽는다 — 실제로 그런 일이 났다: 앱에\n * 든 형식이 옛 판이라 견주는 함수 자체가 없었는데, 화면은 아무 말도 안 하고 조용했다.\n */\n private compareWithReleased(): ContractChange[] {\n this.contractFailure = undefined\n if (!this.releasedSource || !this.folded) return []\n\n try {\n return contractChanges(this.releasedSource, this.folded)\n } catch (e) {\n this.contractFailure = (e as Error).message\n return []\n }\n }\n\n /**\n * 페이지 밖 AI 도크에도 **지금 손에 든 정본**을 알려 준다.\n *\n * asidebar 도크는 주소의 id만\n * 알았으므로 DB에 저장된 옛 판을 읽었다. 저장 전 변경을 두고 \"왜 발행이 안 되나\" 또는\n * \"이 부품을 줄여 달라\"고 물으면 대화가 엉뚱한 판을 보게 되는 결함이었다.\n *\n * 이 이벤트는 저장하지 않는다. 도크가 대화 인자로만 잠시 들고 있고, 서버 도구도 도메인 소속\n * Figure를 먼저 확인한 뒤에만 이 source를 사용한다. 페이지를 떠날 때는 아래에서 지워 오래된\n * 초안이 다음 Figure에 섞이지 않게 한다.\n */\n updated(changed: Map<string, unknown>) {\n if (changed.has('figure') && (changed.get('figure') as Figure | undefined)?.id !== this.figure?.id) {\n this.proposalFeedback = []\n }\n if (changed.has('draft') || changed.has('parts') || changed.has('figure')) {\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: { figureId: this.figure?.id, source: this.folded }\n })\n )\n }\n }\n\n private announceAiContext = () => {\n if (!this.active) return\n window.dispatchEvent(new CustomEvent('figure-ai-context', {\n detail: { figureId: this.figure?.id, source: this.folded }\n }))\n }\n\n /*\n `detail.staged` is the dock's answer to \"did anything actually happen to my candidate\". The\n listener is synchronous, so the dock reads it right after dispatching and only then tells the\n chat card to say so. It used to be called `accepted`, which claimed more than it knew — the\n page had put the candidate somewhere, not the person had agreed to it.\n */\n private receiveDockProposal = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!detail) return\n\n const source =\n detail?.source ||\n (detail?.proposal?.source\n ? typeof detail.proposal.source === 'string'\n ? JSON.parse(detail.proposal.source)\n : detail.proposal.source\n : undefined)\n\n if (source) {\n detail.staged = this.openProposalForReview(source, detail.proposal)\n }\n }\n\n private onFigureAiStage = (event: Event) => {\n const detail = (event as CustomEvent).detail\n const source = detail?.source || (detail?.proposal?.source ? (typeof detail.proposal.source === 'string' ? JSON.parse(detail.proposal.source) : detail.proposal.source) : undefined)\n if (source) {\n this.openProposalForReview(source, detail.proposal)\n }\n }\n\n /**\n * A candidate from the dock opens the review lane. It does not become the draft.\n *\n * This used to write `draft` and `parts` straight onto the working draft and then set\n * `proposalSession = undefined` — the dock's candidate overwrote what the author had open and\n * erased the lane built to review it. There was no moment where the person said yes, and a\n * draft has no version history to go back to, so the previous work was simply gone.\n *\n * `ai-client-base`'s analysis contract states this as a literal type: `approvalRequired: true`,\n * and of `proposal`, \"It is never an instruction to mutate canonical state.\" The in-page\n * The in-page entrance honoured this through `openAiProposal` before it was removed, and the dock\n * entrance is now the same door. Applying is what the take button does, from the lane, per change.\n *\n * ## Why the overwrite was there — so it does not come back\n *\n * The dock opened the review lane originally. Before `c42a0b63df` this handler read:\n *\n * if (!this.active || (detail?.figureId ?? '') !== (this.figure?.id ?? '')) return\n * if (detail?.draftType && detail.draftType !== this.folded?.type) return\n * if (detail?.baseSource && detail.baseSource !== JSON.stringify(this.folded)) return\n * this.receive(detail.proposal)\n *\n * Three guards, each a silent `return`, and the last compared a serialized source byte for byte.\n * Any difference that changed nothing at all dropped the candidate with no message, so the dock\n * would report a candidate and the screen would show none.\n *\n * The `ox-assistant-chat` migration fixed that by replacing the path rather than the guards, and\n * the review lane went out with them — `applySourceDirectly` wrote straight to the draft, and\n * cleared `proposalSession` on the way past. **Nobody decided that reviews were unwanted here.**\n * It was the cost of loosening a guard by deleting what stood around it.\n *\n * So the guards are gone and stay gone, and the lane is back. If a candidate ever seems not to\n * arrive, the thing to look for is a silent `return` on this path — not the lane.\n *\n * Returns whether the lane opened, so the dock can tell the chat whether to say it staged.\n */\n private openProposalForReview(rawSource: any, proposal?: any): boolean {\n const source = typeof rawSource === 'string' ? JSON.parse(rawSource) : rawSource\n if (!source || !Array.isArray(source.parts)) return false\n\n try {\n /*\n The dock sends a FigureProposal whose metrics sit at the top level; the staged-on-navigate\n path carries the raw tool result, which nests them under score/cost. Read both rather than\n making one of the two entrances reshape its payload.\n */\n this.proposalSession = proposalSessions.openAiProposal(this.folded, {\n source: JSON.stringify(source),\n grade: proposal?.grade ?? proposal?.score?.grade,\n triangles: proposal?.triangles ?? proposal?.cost?.triangles,\n groups: proposal?.groups ?? proposal?.cost?.groups,\n attempts: proposal?.attempts,\n quality: proposal?.quality\n } as FigureProposal)\n\n this.mode = 'edit'\n this.requestUpdate()\n return true\n } catch (e) {\n console.error('[FigureModeller] Failed to open the AI candidate for review:', e)\n return false\n }\n }\n\n private proposalBaseSource?: string\n\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announceAiContext)\n window.addEventListener('figure-ai-review', this.receiveDockProposal)\n window.addEventListener('figure-ai-stage', this.onFigureAiStage)\n window.addEventListener('figure-save-request', this.onSaveRequest)\n window.addEventListener('keydown', this.handleKeyDown)\n }\n\n disconnectedCallback() {\n window.removeEventListener('keydown', this.handleKeyDown)\n window.removeEventListener('figure-save-request', this.onSaveRequest)\n window.removeEventListener('figure-ai-stage', this.onFigureAiStage)\n window.removeEventListener('figure-ai-context-request', this.announceAiContext)\n window.removeEventListener('figure-ai-review', this.receiveDockProposal)\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n super.disconnectedCallback()\n }\n\n private onSaveRequest = () => {\n if (this.active) this.save()\n }\n\n private handleKeyDown = (e: KeyboardEvent) => {\n if (!this.active || this.mode !== 'edit' || this.proposalSession) return\n const target = e.composedPath()[0] as HTMLElement | undefined\n const tag = target?.tagName?.toLowerCase()\n if (tag === 'input' || tag === 'textarea' || target?.isContentEditable) return\n\n if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n if (e.key === 'z' && !e.shiftKey) {\n e.preventDefault()\n this.undo()\n } else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') {\n e.preventDefault()\n this.redo()\n }\n }\n }\n\n get context() {\n return {\n title: this.figure?.name ?? i18next.t('figure.title.new-figure'),\n /*\n **제목 줄은 끈다.** 이름은 그대로 둔다.\n\n 이 화면의 주인공은 캔버스다. 이름은 속성 패널에서 보고 고치고, 브라우저 탭에도\n 「operato figure · 작업자」로 나온다. 그 위에 제목 줄을 한 겹 더 얹으면 같은 말이 세 번이고,\n 그만큼 캔버스가 낮아진다.\n\n `title` 을 비우는 것으로 끄지 않는 이유는 그 값을 MDI 탭도 읽기 때문이다 — 비우면 탭 이름이\n 함께 사라진다. 이름은 하나이고 그리는 자리마다 켜고 끈다(`ox-page-title-bar`).\n */\n heading: false\n }\n }\n\n render() {\n if (this.sourceLoadFailure) return html`<section role=\"alert\" style=\"padding:24px\">\n <h2>Figure를 열지 못했습니다</h2><p>${this.sourceLoadFailure}</p>\n <button @click=${() => navigate('figure-list')}>Figure 목록으로</button>\n </section>`\n const source = this.folded\n\n return html`\n <div\n edits\n ?collapse-left=${this.collapsed.left}\n ?collapse-right=${this.collapsed.right}\n @figure-rename=${(e: CustomEvent) => this.rename(e.detail.name)}\n @figure-type-change=${(e: CustomEvent) => this.typeChanged(e.detail.type)}\n @figure-catalog=${(e: CustomEvent) => this.catalog(e.detail)}\n @figure-released=${(e: CustomEvent) => this.released(e.detail.figure)}\n @figure-reverted=${() => this.figure?.id && this.load(this.figure.id)}\n @preview-sizing-fix=${(e: CustomEvent) => this.previewSizingFix(e.detail)}\n @apply-sizing-fix=${(e: CustomEvent) => this.applySizingFix(e.detail)}\n @apply-sizing-fix-plan=${(e: CustomEvent) => this.applySizingFixPlan(e.detail)}\n @restore-released-footprint=${(e: CustomEvent) => this.restoreReleasedFootprint(e.detail)}\n @fit-base-to-parts=${() => this.fitBaseToParts()}\n @settle-parts-on-floor=${() => this.settlePartsOnFloor()}\n @add-part=${(e: CustomEvent) => this.addPart(e.detail.primitive)}\n @select-part=${(e: CustomEvent) => (this.selected = e.detail.index)}\n @remove-part=${(e: CustomEvent) => this.removePart(e.detail.index)}\n @duplicate-part=${(e: CustomEvent) => this.duplicatePart(e.detail.index)}\n @move-part-up=${(e: CustomEvent) => this.movePart(e.detail.index, e.detail.to)}\n @move-part-down=${(e: CustomEvent) => this.movePart(e.detail.index, e.detail.to)}\n @update-part=${(e: CustomEvent) => this.updatePart(e.detail.index, e.detail.part)}\n @part-changed=${(e: CustomEvent) => this.partChanged(e.detail.name, e.detail.part)}\n @toggle-part-hidden=${(e: CustomEvent) => this.toggleHidden(e.detail.index)}\n @set-all-parts-hidden=${(e: CustomEvent) => this.setAllHidden(e.detail.hidden)}\n @change-detail-level=${(e: CustomEvent) => this.changeDetailLevel(e.detail.level)}\n @update-draft=${(e: CustomEvent) => this.updateDraft(e.detail.draft)}\n @resize-base=${(e: CustomEvent) => this.resizeBase(e.detail.draft, e.detail.parts)}\n @part-tool=${(e: CustomEvent) => this.runTool(e.detail.index, e.detail.tool, e.detail.axis)}\n @pick-part=${(e: CustomEvent) => this.pickPart(e.detail.name)}\n >\n <figure-parts\n .parts=${this.parts}\n .level=${(this.draft?.detailLevel ?? 'M') as DetailLevel}\n .selected=${this.selected}\n ></figure-parts>\n\n <div middle>\n ${this.renderBar()}\n ${this.proposalSession ? this.renderDecision() : ''}\n ${this.proposalSession\n ? html`\n <div lanes>\n <div lane>\n <h4>${i18next.t('figure.label.now')}</h4>\n <figure-canvas\n .figureId=${this.figure?.id || this.figure?.type}\n .draft=${this.draft}\n .parts=${this.parts}\n .picked=${this.pickedName}\n .view=${this.view}\n .revision=${this.sceneRevision}\n ></figure-canvas>\n </div>\n <div lane candidate>\n <h4>\n ${i18next.t('figure.label.proposed')}\n ${this.proposalSession.metrics\n ? html`<small>\n ${this.proposalSession.metrics.grade} ·\n ${i18next.t('figure.text.n-triangles', { count: this.proposalSession.metrics.triangles })}\n ${this.proposalSession.metrics.quality ? html` · ${this.proposalSession.metrics.quality.summary}` : ''}\n </small>`\n : ''}\n </h4>\n ${this.renderProposed()}\n </div>\n </div>\n `\n : this.mode === 'preview'\n ? html`<figure-preview\n .source=${source}\n .draft=${this.draft}\n .view=${this.view}\n @pose-as-default=${(e: CustomEvent) => this.poseAsDefault(e.detail.values)}\n ></figure-preview>`\n : html`<figure-canvas\n .figureId=${this.figure?.id || this.figure?.type}\n .draft=${this.draft}\n .parts=${this.parts}\n .picked=${this.pickedName}\n .gizmo=${this.jointGizmo}\n .view=${this.view}\n .revision=${this.sceneRevision}\n ></figure-canvas>`}\n </div>\n\n <figure-side\n .figureName=${this.figure?.name ?? ''}\n .figureCategory=${this.figure?.category}\n .figureTags=${this.figure?.tags}\n .figureId=${this.figure?.id ?? ''}\n .figureState=${this.figure?.state}\n .figureVersion=${this.figure?.version ?? 0}\n .dirty=${this.dirty}\n .draft=${this.draft}\n .parts=${this.parts}\n .source=${source}\n .contract=${this.contract}\n .contractFailure=${this.contractFailure}\n .releasedBase=${this.releasedSource?.base}\n .selected=${this.selected}\n .view=${this.view}\n @update-view=${(e: CustomEvent) => (this.view = e.detail.view)}\n ></figure-side>\n </div>\n ${this.renderSaveModal()}\n `\n }\n\n /**\n * 가운데 칸 위의 띠 — 무엇을 보고 있나와, 양쪽 칸을 접는 손잡이.\n *\n * ## 무엇을 보고 있나\n *\n * 편집과 미리보기는 같은 자리를 놓고 다투지 않는다. 둘 다 넓이가 필요하고, 동시에\n * 봐야 할 일이 없다. 부품을 고치는 동안에는 편집만 보면 되고, 다 고친 뒤에 도면에서\n * 어떻게 되는지 보면 된다.\n *\n * 후보가 와 있을 때는 그 두 단추를 내지 않는다. 그때는 가운데가 이미 둘로 나뉘어\n * 있다.\n *\n * ## 띠는 늘 있다\n *\n * 손잡이가 여기 있기 때문이다. 후보가 왔다고 띠를 통째로 감추면, 칸을 접어 둔 채로\n * 후보를 받으면 되돌릴 방법이 없어진다.\n */\n private renderBar() {\n const one = (mode: 'edit' | 'preview') => html`\n <button\n mode\n ?on=${this.mode === mode}\n title=${i18next.t('figure.text.mode-' + mode + '-explained')}\n @click=${() => (this.mode = mode)}\n >\n ${i18next.t('figure.label.mode-' + mode)}\n </button>\n `\n\n return html`<div modes>\n ${this.renderCollapse('left')}${this.proposalSession ? '' : html`${one('edit')}${one('preview')}`}\n ${this.mode === 'edit' && !this.proposalSession\n ? html`\n <div separator></div>\n <button\n history\n ?disabled=${this.undoStack.length === 0}\n title=\"${i18next.t('figure.button.undo')} (Ctrl+Z)\"\n @click=${() => this.undo()}\n >\n <md-icon>undo</md-icon>\n </button>\n <button\n history\n ?disabled=${this.redoStack.length === 0}\n title=\"${i18next.t('figure.button.redo')} (Ctrl+Y)\"\n @click=${() => this.redo()}\n >\n <md-icon>redo</md-icon>\n </button>\n `\n : ''}\n <div spacer></div>\n <!--\n AI 를 여는 단추는 여기 두지 않는다. 앱 머리줄에 이미 있고 같은 dock 을 연다 — 한 화면에\n 같은 곳으로 가는 문이 둘이면 사람이 둘의 차이를 찾는다. 열린 도형을 dock 에 넘기는 일은\n 단추가 하던 것이 아니다: dock 이 열릴 때 figure-ai-context-request 로 묻고 이 화면이\n 저장 전 정본 그대로 답한다.\n -->\n ${this.saveFailure ? html`<span save-failed title=${this.saveFailure}>${this.saveFailure}</span>` : ''}\n ${this.dirty ? html`<span dirty>${i18next.t('figure.text.unsaved-changes')}</span>` : ''}\n <button save ?disabled=${!this.canSave} @click=${() => this.save()}>\n ${this.saving ? i18next.t('figure.text.saving-figure') : i18next.t('figure.button.save')}\n </button>\n ${this.renderCollapse('right')}\n </div>`\n }\n\n /**\n * 한쪽 칸을 접고 펴는 손잡이.\n *\n * 화살표는 **누르면 무엇이 일어나는지**를 가리킨다 — 펴져 있으면 칸이 사라질 쪽,\n * 접혀 있으면 칸이 돌아올 쪽. 지금 상태를 가리키게 하면 반대로 읽힌다.\n */\n private renderCollapse(side: keyof Panes) {\n const gone = this.collapsed[side]\n const shut = side === 'left' ? 'chevron_left' : 'chevron_right'\n const back = side === 'left' ? 'chevron_right' : 'chevron_left'\n const pane = side === 'left' ? 'parts' : 'properties'\n\n return html`\n <button\n collapse\n title=${i18next.t(`figure.text.${gone ? 'expand' : 'collapse'}-${pane}-pane`)}\n @click=${() => this.toggleCollapse(side)}\n >\n <md-icon>${gone ? back : shut}</md-icon>\n </button>\n `\n }\n\n private toggleCollapse(side: keyof Panes): void {\n this.collapsed = { ...this.collapsed, [side]: !this.collapsed[side] }\n rememberPanes(this.collapsed)\n }\n\n /**\n * AI 후보를 실제 배치 크기에서도 본다.\n *\n * 편집 캔버스 한 장으로는 `repeat`·`fixed`·`anchor`가 맞는지 알 수 없다. 후보는 아직\n * 정본이 아니므로 저장하지 않고, `figure-preview`가 같은 FigureSource를 기준 크기와\n * 일곱 가지 확장 축에서 실제 FigureInstance로 세운다. 따라서 저작자는 \"좋아 보이는\n * 한 장\"이 아니라 배치될 때도 붙어 있는지를 보고 수락한다.\n */\n private renderProposed() {\n if (!this.proposalSession) return ''\n\n return html`<figure-preview .source=${this.proposalSession.source} .view=${this.view}></figure-preview>`\n }\n\n /**\n * 무엇을 받을지 고른다.\n *\n * 통째로만 받게 두면 「몸통은 좋은데 모터 자리가 틀렸다」에 답할 수 없다 — 그게\n * 가장 흔한 반응이다. 변경 하나가 칩 하나이고, 처음에는 전부 골라 둔다. 통째로\n * 받는 것이 보통의 길이므로 그것이 한 번 누르기여야 한다.\n *\n * 고른 조합을 **받기 전에 재 본다.** 형식을 어기면 못 누르게 하고, 정책을 넘으면\n * 알린다. 골라 받으면 후보를 낼 때와 다른 조합이 되므로 후보의 점수를 그대로\n * 믿을 수 없다 — 예를 들어 등급은 안 받고 부품만 받으면 부품 수 한도를 넘는다.\n *\n * 형식 오류가 실제로 나는 조합은 아직 보지 못했다. 그래도 두는 것은 값이 거의\n * 안 들고, 우리가 생각 못 한 조합을 막아 주기 때문이다.\n */\n private renderDecision() {\n if (!this.proposalSession) return ''\n\n const stale = this.proposalBaseSource !== undefined && this.proposalBaseSource !== JSON.stringify(this.folded)\n\n const changes = proposals.diffProposal(this.folded, this.proposalSession.source)\n const picked = proposals.applyProposal(this.folded, this.proposalSession.source, this.proposalSession.picked)\n const { errors, violations } = validate(picked)\n\n return html`\n <div decide>\n <span>${this.proposalTitle(this.proposalSession)}</span>\n <div spacer></div>\n <button drop @click=${() => this.discard()}>${i18next.t('figure.button.discard')}</button>\n ${stale ? html`<span role=\"alert\">${i18next.t('figure.text.source-changed-ask-again')}</span>` : ''}\n <button take ?disabled=${stale || errors.length > 0 || this.proposalSession.picked.size === 0} @click=${() => this.take()}>\n ${i18next.t('figure.button.take-n-changes', { n: this.proposalSession.picked.size })}\n </button>\n </div>\n\n ${this.renderQualityReview()}\n <div proposal-note>\n <label>\n ${i18next.t('figure.text.proposal-feedback-note')}\n <input\n maxlength=\"500\"\n .value=${this.proposalSession.note}\n placeholder=${i18next.t('figure.text.proposal-feedback-placeholder')}\n @input=${(event: Event) => (this.proposalSession = { ...this.proposalSession!, note: (event.target as HTMLInputElement).value })}\n />\n </label>\n </div>\n\n ${changes.length > 0\n ? html`\n <div changes>\n ${changes.map(change => this.renderChange(change))}\n </div>\n `\n : ''}\n ${errors.length > 0\n ? html`<p blocked>${i18next.t('figure.text.picked-changes-do-not-validate')} — ${errors[0].message}</p>`\n : violations.length > 0\n ? html`<p blocked warn>${violations[0].message}</p>`\n : ''}\n `\n }\n\n /** 후보 품질 판단의 근거를 수락 전에 전부 보여 준다. */\n private renderQualityReview() {\n const quality = this.proposalSession?.metrics?.quality\n if (!quality) return ''\n\n return html`\n <div quality-review ?review=${quality.status === 'review'}>\n <strong>${i18next.t('figure.label.candidate-review')}</strong>\n <span>${quality.summary}</span>\n <span> · top-view ${Math.round(quality.visualReadability * 100)}% · ${quality.visualRegions} regions</span>\n ${quality.findings.length > 0\n ? html`<ul>\n ${quality.findings.map(finding => html`<li><code>${finding.dimension}</code> — ${finding.message}</li>`)}\n </ul>`\n : ''}\n </div>\n `\n }\n\n /** 변경 하나. 무엇을 하겠다는 것인지 부호로 먼저 말한다. */\n private renderChange(change: proposals.ProposalChange) {\n /*\n Every ChangeKind needs a row here. A kind the map does not answer draws an empty icon and a\n key with no name beside it, which reads as a broken row rather than a missing case — the\n `satisfies` makes the compiler say so instead.\n */\n const icon = ({\n added: 'add',\n changed: 'edit',\n removed: 'remove',\n base: 'aspect_ratio',\n placement: 'vertical_align_bottom',\n detail: 'tune',\n styleKit: 'palette',\n animations: 'animation',\n parameters: 'tune',\n capabilities: 'hub',\n joints: 'settings_input_component'\n } satisfies Record<proposals.ChangeKind, string>)[change.kind]\n\n const on = this.proposalSession?.picked.has(change.key)\n\n return html`\n <button\n change\n ?on=${on}\n title=${i18next.t('figure.text.change-' + change.kind)}\n @click=${() => this.toggle(change.key)}\n >\n <md-icon>${icon}</md-icon>\n <span key>${change.key}</span>\n </button>\n `\n }\n\n private toggle(key: string) {\n if (this.proposalSession) this.proposalSession = proposalSessions.toggleProposalChange(this.proposalSession, key)\n }\n\n /**\n * 후보가 왔다.\n *\n * 원본 데이터에 넣지 않는다. 나란히 두고 저작자가 고른다. AI 가 원본 데이터에 직접 쓰면\n * 무엇이 자기 것이고 무엇이 기계 것인지 모르게 된다.\n */\n private receive(proposal: FigureProposal) {\n this.proposalBaseSource = JSON.stringify(this.folded)\n this.proposalSession = proposalSessions.openAiProposal(this.folded, proposal)\n }\n\n /**\n * 발행 게이트의 구조화된 수정안 하나를 현재 작업 사본에만 겹쳐 본다. 서버가 준 값은\n * `part.anchor.axis = to`라는 데이터이지 사람이 읽을 문장을 다시 해석한 결과가 아니다.\n * 원본 `draft`/`parts`는 여기서 전혀 바꾸지 않고, 아래의 `take()`만 바꾼 사본을 적용한다.\n */\n private previewSizingFix(fix: FigureGateFix) {\n if (!this.folded) return\n this.proposalBaseSource = JSON.stringify(this.folded)\n this.proposalSession = proposalSessions.openSizingFix(this.folded, fix)\n }\n\n /**\n * 원클릭 적용은 서버가 모든 크기 변경 case를 재검사해 안전하다고 표시한 한-앵커 후보에만 연다.\n * 그래도 저장은 저작자의 별도 행위다. 잘못 눌러도 DB 정본이나 발행판은 바뀌지 않는다.\n */\n private applySizingFix(fix: FigureGateFix) {\n if (!fix.safe || !this.folded) return\n this.previewSizingFix(fix)\n if (!this.proposalSession) return\n this.take()\n }\n\n /**\n * The release gate may need several anchors to keep a connected assembly together. It has already\n * measured the complete set. Apply it directly to this unsaved work copy instead of making the\n * author accept a sequence of opaque mini-proposals.\n */\n private applySizingFixPlan(plan: FigureGateFixPlan) {\n if (!plan.safe || !this.folded || plan.fixes.length === 0) return\n const fixes = new Map(plan.fixes.map(fix => [`${fix.part}:${fix.axis}`, fix]))\n const next: FigureSource = {\n ...this.folded,\n parts: this.folded.parts.map(part => {\n const anchor = { ...(part.anchor ?? {}) }\n let changed = false\n for (const axis of ['x', 'y', 'z'] as const) {\n const fix = fixes.get(`${part.name}:${axis}`)\n if (!fix) continue\n anchor[axis] = fix.to\n changed = true\n }\n return changed ? { ...part, anchor } : part\n })\n }\n this.recordHistory()\n const { draft, parts } = fromFigureSource(next)\n this.draft = draft\n this.parts = parts\n this.selected = parts.length ? 0 : -1\n this.dirty = true\n }\n\n /**\n * 고른 변경을 받아들인다.\n *\n * 후보 비교는 저장 형식으로 한다 — 부품 이름으로 짝짓고 기준 치수·등급까지 함께 봐야\n * 하므로 그쪽이 자연스럽다. 받아들인 결과는 다시 씬 모델로 되돌려 편집을 이어간다.\n */\n private take() {\n if (!this.proposalSession) return\n if (this.proposalBaseSource !== undefined && this.proposalBaseSource !== JSON.stringify(this.folded)) return\n\n this.recordHistory()\n const taken = proposals.applyProposal(this.folded, this.proposalSession.source, this.proposalSession.picked)\n const { draft, parts } = fromFigureSource(taken)\n\n this.draft = draft\n this.parts = parts\n this.selected = parts.length ? 0 : -1\n this.dirty = true\n this.reportProposalFeedback('accepted')\n this.discard(false)\n }\n\n /**\n * 후보에 대한 사람의 결정을 도크에만 알린다.\n *\n * 이 기록은 분석 이벤트나 Figure 속성이 아니다. 같은 모델러 세션에서 다음 요청을 더 잘\n * 이해시키는 짧은 신호일 뿐이다. 원본 source나 사람의 작업 내용을 다시 보내지 않고, 사람이\n * 후보 변경 중 몇 개를 받아들였는지와 이미 화면에 보였던 품질 판정만 보낸다.\n */\n private reportProposalFeedback(outcome: 'accepted' | 'discarded') {\n if (!this.proposalSession) return\n\n const feedback = proposalSessions.feedbackFromSession(this.folded, this.proposalSession, outcome)\n if (!feedback) return\n this.proposalFeedback = [...this.proposalFeedback, feedback].slice(-5)\n window.dispatchEvent(\n new CustomEvent('figure-ai-feedback', {\n detail: {\n figureId: this.figure?.id,\n ...feedback\n }\n })\n )\n }\n\n private discard(reportFeedback = true) {\n if (reportFeedback) this.reportProposalFeedback('discarded')\n this.proposalSession = undefined\n this.proposalBaseSource = undefined\n }\n\n /**\n * 저장할 수 있나.\n *\n * 형식을 어긴 원본 데이터로는 저장을 누를 수 없게 한다. 눌렀다가 서버가 받아주지 않는 것보다,\n * 누를 수 없는 이유가 판단 패널에 떠 있는 것이 낫다.\n *\n * ## 바뀐 것이 없으면 누를 수 없다\n *\n * **`dirty` 를 안 보고 있었다.** 저장한 직후에도 단추가 살아 있어 같은 정본을 몇 번이고 다시 쓸\n * 수 있었고, 저장마다 썸네일을 새로 찍어 20KB 를 다시 넣고 `thumbnailUpdatedAt` 을 올렸다 —\n * 그림 주소의 판이 바뀌므로 브라우저 캐시도 함께 버려진다. 바뀐 것이 없는데 일어날 일이 아니다.\n *\n * 새로 만드는 중인 것은 `startNew` 가 `dirty` 로 시작하므로 첫 저장이 막히지 않는다.\n * 저장이 거절되면 `dirty` 를 그대로 두므로 다시 누를 수 있다.\n */\n private get canSave(): boolean {\n if (this.saving || !this.dirty || !this.folded) {\n return false\n }\n // 기존 저장된 모델은 이름이 있어야 바로 저장 가능. 신규 모델은 저장 팝업에서 이름을 입력할 수 있음\n if (this.figure?.id && !this.figure?.name) {\n return false\n }\n return validate(this.folded).errors.length === 0\n }\n\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n ++this.loadGeneration\n return\n }\n\n const id = lifecycle?.resourceId\n\n if (id) {\n if (changes.active || id !== this.figure?.id) {\n await this.load(id)\n }\n const pending = consumePendingFigureProposal()\n if (pending?.source) {\n this.openProposalForReview(pending.source, pending.proposal)\n }\n return\n }\n\n /*\n ── id 없이 들어온 것은 「새로 만든다」는 뜻이다 ─────────────────────────────\n 전에는 `!this.figure` 일 때만 새로 시작했다. 셸은 페이지를 살려 두므로(MDI 탭) 카탈로그에서\n 하나 열어 본 뒤에는 이 페이지가 그 도형을 계속 들고 있고, 그래서 「새 figure」가 **아무 일도\n 하지 않았다.**\n\n 그것보다 나쁜 것이 있었다 — 화면에는 옛 도형이 그대로 있으니 사람은 새 것인 줄 알고 고치고\n 저장한다. 그러면 **열려 있던 도형에 덮어쓴다.** 「안 열린다」로 보이던 것이 실은 그 문이었다.\n\n 들고 있는 것이 **저장된 도형이면**(id 가 있으면) 새로 시작한다. 아직 저장 안 한 새 도형을\n 들고 있으면 그대로 둔다 — 페이지를 다시 들렀다고 저작 중인 것을 버리지 않는다.\n */\n if (!this.figure) {\n this.startNew()\n const pending = consumePendingFigureProposal()\n if (pending?.source) {\n this.openProposalForReview(pending.source, pending.proposal)\n }\n return\n }\n\n if (this.figure.id) {\n this.startNew()\n const pending = consumePendingFigureProposal()\n if (pending?.source) {\n this.openProposalForReview(pending.source, pending.proposal)\n }\n return\n }\n\n if (shouldStartNewFigure(changes, !!this.figure, this.figure?.id)) {\n this.startNew()\n const pending = consumePendingFigureProposal()\n if (pending?.source) {\n this.openProposalForReview(pending.source, pending.proposal)\n }\n return\n }\n\n const pending = consumePendingFigureProposal()\n if (pending?.source) {\n this.openProposalForReview(pending.source, pending.proposal)\n }\n }\n\n private loadGeneration = 0\n private async load(id: string) {\n const generation = ++this.loadGeneration\n const figure = await fetchFigure(id)\n if (generation !== this.loadGeneration || !this.active) return\n /*\n A converted figure reached by an old address — a bookmark, the AI dock, a link someone sent — opens in the\n modeller that can edit it instead of failing here on a source this page cannot read.\n */\n if (isV3Source(figure.source)) {\n navigate(modellerPathOf({ id, format: 'v3' }), true)\n return\n }\n\n // Decode before assigning the target ID or generating a thumbnail. On failure the previous\n // editor snapshot remains internal, but is hidden and cannot be saved under the requested ID.\n let model: ReturnType<typeof fromFigureSource> | undefined\n try {\n model = figure.source ? fromFigureSource(JSON.parse(figure.source) as FigureSource) : undefined\n } catch (error) {\n this.sourceLoadFailure = error instanceof Error ? error.message : String(error)\n return\n }\n this.sourceLoadFailure = ''\n\n this.proposalSession = undefined\n this.proposalBaseSource = undefined\n this.figure = figure\n\n /*\n 그림이 없으면 이 자리에서 채운다(아래 `fillThumbnail`). 기다리지 않는다 — 화면이 먼저다.\n\n **`thumbnail` 이 아니라 `thumbnailUpdatedAt` 으로 판정한다.** 그림 자체는 목록·낱개 질의가\n 가져오지 않는다(base64 라서 무겁고 캐시가 안 된다 — 주소로 부른다). 없는 칸으로 판정하면\n 늘 「그림이 없다」가 되어 저작면을 열 때마다 다시 찍어 저장하게 된다.\n */\n if (!figure.thumbnailUpdatedAt && figure.id) {\n void this.fillThumbnail(figure.id)\n }\n\n // 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.\n // 그러면 사용자가 저장을 선택해 원본을 덮어쓴다.\n this.draft = model?.draft\n this.parts = model?.parts ?? []\n\n /*\n 발행된 적이 있으면 그 판을 가져다 둔다 — 고치는 동안 견줄 상대다. 기다리지 않는다:\n 도형은 이미 화면에 있고, 판정은 도착하는 대로 붙는다.\n */\n this.releasedSource = undefined\n if (figure.id && (figure.version ?? 0) > 0) {\n void this.loadReleased(figure.id, generation)\n }\n\n /*\n **아무것도 고르지 않은 채로 연다.**\n\n 전에는 첫 부품을 잡아 두었다. 그러면 사용자가 고른 적 없는 부품의 속성이 떠 있고,\n 캔버스에는 그 부품의 윤곽선이 그려진다. 모델 전체를 보려면 먼저 선택을 풀어야 했다.\n\n 속성 패널의 빈 상태도 그래서 **열었을 때 한 번도 안 보였다.** 처음 여는 사람에게\n 「왼쪽에서 부품을 고르는 도구」라고 알려 줄 자리가 그 화면이다.\n\n 새로 만들 때(`startNew`)는 다르다. 방금 부품을 만들었으니 바로 편집하려는 것이 맞다.\n */\n this.selected = -1\n this.dirty = false\n this.violations = []\n this.undoStack = []\n this.redoStack = []\n this.lastCoalesceKey = undefined\n }\n\n /** 직전 발행판을 가져다 둔다. 못 읽으면 견주지 않는다 — 없는 판정을 지어내지 않는다. */\n private async loadReleased(id: string, generation: number) {\n try {\n const source = await fetchReleasedSource(id)\n if (generation !== this.loadGeneration || !this.active) return\n this.releasedSource = source ? (JSON.parse(source) as FigureSource) : undefined\n } catch (e) {\n console.warn(`[figure] 직전 발행판을 읽지 못했습니다 — ${(e as Error).message}`)\n }\n }\n\n private startNew() {\n this.sourceLoadFailure = ''\n ++this.loadGeneration\n this.proposalSession = undefined\n this.proposalBaseSource = undefined\n this.proposalFeedback = []\n this.mode = 'edit'\n // 타입 이름을 사람이 정하기 전까지는 임시 이름을 쓴다. 저장할 때 확정한다.\n const draftType = placeholderType()\n const { draft, parts } = startingModel(draftType)\n\n this.figure = { id: '', type: draftType, name: '' } as Figure\n this.draft = draft\n this.parts = parts\n /* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */\n this.selected = -1\n this.dirty = true\n this.violations = []\n this.undoStack = []\n this.redoStack = []\n this.lastCoalesceKey = undefined\n }\n\n private rename(name: string) {\n if (!this.figure) return\n\n this.figure = { ...this.figure, name }\n this.dirty = true\n }\n\n /**\n * 타입을 고친다 — **발행 전까지만**(ADR-0063 결정 4).\n *\n * 전에는 저장된 도형이면 무조건 돌아섰다. 그래서 기계가 지은 이름이 그대로 굳었다. 발행된\n * 뒤에는 문이 거절하므로 여기서도 막는다 — 눌러서 되는 것처럼 보이고 저장에서 실패하면\n * 저작자가 무엇을 잘못했는지 못 읽는다.\n */\n private typeChanged(type: string) {\n if (!type || (this.figure?.version ?? 0) > 0) return\n const sanitized = type.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n if (this.draft) this.draft.figureType = sanitized\n this.figure = { ...this.figure, type: sanitized } as Figure\n this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined\n this.dirty = true\n }\n\n /**\n * 분류와 태그를 고친다.\n *\n * 정본(`source`)이 아니라 **카탈로그의 값**이다 — 도형이 무엇으로 생겼는지가 아니라 어디에\n * 걸리는지다. 그래서 점수도 발행도 흔들리지 않는다.\n */\n private catalog(patch: { category?: string; tags?: string[] }) {\n if (!this.figure) return\n\n this.figure = { ...this.figure, ...patch }\n this.dirty = true\n }\n\n private snapshotState(): { draft?: FigureDraft; parts: PartModel[]; selected: number } {\n return {\n draft: this.draft ? structuredClone(this.draft) : undefined,\n parts: structuredClone(this.parts),\n selected: this.selected\n }\n }\n\n private recordHistory(coalesceKey?: string) {\n const now = Date.now()\n if (coalesceKey && this.lastCoalesceKey === coalesceKey && now - this.lastHistoryTime < 600) {\n this.lastHistoryTime = now\n return\n }\n\n this.lastCoalesceKey = coalesceKey\n this.lastHistoryTime = now\n\n this.undoStack = [...this.undoStack.slice(-49), this.snapshotState()]\n this.redoStack = []\n }\n\n private undo() {\n if (this.undoStack.length === 0) return\n const prev = this.undoStack[this.undoStack.length - 1]\n this.undoStack = this.undoStack.slice(0, -1)\n this.redoStack = [this.snapshotState(), ...this.redoStack.slice(0, 49)]\n\n this.draft = prev.draft ? structuredClone(prev.draft) : undefined\n this.parts = structuredClone(prev.parts)\n this.selected = this.parts.length ? Math.max(-1, Math.min(prev.selected, this.parts.length - 1)) : -1\n this.dirty = true\n this.lastCoalesceKey = undefined\n }\n\n private redo() {\n if (this.redoStack.length === 0) return\n const next = this.redoStack[0]\n this.redoStack = this.redoStack.slice(1)\n this.undoStack = [...this.undoStack.slice(-49), this.snapshotState()]\n\n this.draft = next.draft ? structuredClone(next.draft) : undefined\n this.parts = structuredClone(next.parts)\n this.selected = this.parts.length ? Math.max(-1, Math.min(next.selected, this.parts.length - 1)) : -1\n this.dirty = true\n this.lastCoalesceKey = undefined\n }\n\n /**\n * 부품 목록을 갈아 끼운다.\n *\n * 편집 규칙은 `part-edits` 가 갖는다 — 화면과 테스트가 같은 것을 쓰게 하려고 떼어\n * 놓았다. 여기서는 결과를 담고 선택 인덱스를 정리한다.\n */\n private put(parts: PartModel[], selected = this.selected, coalesceKey?: string) {\n this.recordHistory(coalesceKey)\n this.parts = parts\n this.selected = Math.min(selected, parts.length - 1)\n this.dirty = true\n }\n\n private addPart(primitive: PrimitiveKind) {\n if (!this.draft) return\n /*\n 고른 부품 위에 얹는다 — 그 자리에서 보이고, 관절을 걸면 맞닿는 면이 회전 중심이 된다.\n 고른 것이 없으면 `newPart` 가 바닥 한가운데로 놓는다.\n */\n const onTopOf = this.parts[this.selected]\n // 추가한 것을 바로 수정할 수 있게 선택 상태로 둔다.\n this.put(edits.addPart(this.draft, this.parts, primitive, onTopOf), this.parts.length)\n }\n\n private removePart(index: number) {\n this.put(edits.removePart(this.parts, index), Math.max(0, index - 1))\n }\n\n private duplicatePart(index: number) {\n this.put(edits.duplicatePart(this.parts, index), index + 1)\n }\n\n private movePart(index: number, to: number) {\n this.put(edits.movePart(this.parts, index, to), to)\n }\n\n private updatePart(index: number, part: PartModel) {\n this.put(edits.updatePart(this.parts, index, part), this.selected, `part:${part.name || index}`)\n }\n\n /**\n * 판 자체의 값을 갈아 끼운다 — 능력처럼 부품보다 위에 있는 것들.\n *\n * 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를\n * 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.\n */\n /**\n * 미리보기에서 세운 자세를 파라미터의 기본값으로 앉힌다.\n *\n * 관절이 있는 도형은 값이 0 인 자세(휴지 자세)가 곧게 선 기둥이라, 기본값이 없으면 목록과\n * 도면에서 그 모양으로 선다. 저작자가 미리보기에서 맞춘 자세가 곧 그 답이다.\n */\n private poseAsDefault(values: Record<string, number>) {\n if (!this.draft) return\n\n const next = poseAsDefaults(this.draft, values)\n /* 앉힐 것이 없으면 초안이 그대로 온다. 그때 되돌리기 칸과 「저장 안 됨」을 만들지 않는다. */\n if (next !== this.draft) this.updateDraft(next)\n }\n\n /**\n * 기준 상자를 고친다 — 초안과 부품이 **한 걸음으로** 바뀐다.\n *\n * 상자가 커지면 한가운데가 옮겨 가므로 부품을 그만큼 되밀어야 저장되는 자리가 유지된다\n * (`base-box.ts`). 그 둘을 따로 기록하면 한 번 되돌렸을 때 부품이 반쯤 밀린 화면이 남는다.\n */\n /**\n * 기준 상자를 부품에 맞춘다. 속성 패널의 단추와 **같은 함수**를 쓴다 — 두 자리가 다른 크기를\n * 내면 어느 쪽을 눌렀는지에 따라 발행이 갈린다.\n */\n private fitBaseToParts() {\n if (!this.draft) return\n const fitted = baseFittingParts(this.draft, this.parts)\n if (!fitted) return\n const next = resizedToFit(this.draft, this.parts, { ...fitted })\n if (next.draft === this.draft) return\n this.resizeBase(next.draft, [...next.parts])\n }\n\n /** 떠 있는 부품 전부를 바닥에 앉힌다. 상자는 건드리지 않는다 — 조치 하나가 한 가지만 한다. */\n private settlePartsOnFloor() {\n const settled = partsSettledOnFloor(this.parts)\n if (settled === this.parts) return\n\n this.recordHistory('base')\n this.parts = [...settled]\n this.dirty = true\n }\n\n /** 이전 발행 X·Z만 복원한다. Y와 이번 리비전의 부품 편집은 건드리지 않는다. */\n private restoreReleasedFootprint(base: { x: number; z: number }) {\n if (!this.draft || !this.releasedSource) return\n const released = this.releasedSource.base\n if (base.x !== released.x || base.z !== released.z) return\n\n const next = resizedToFit(this.draft, this.parts, {\n width: released.x,\n depth: this.draft.depth,\n height: released.z\n })\n if (next.draft === this.draft) return\n this.resizeBase(next.draft, [...next.parts])\n }\n\n private resizeBase(draft: FigureDraft, parts: PartModel[]) {\n this.recordHistory('base')\n this.draft = draft\n this.parts = parts\n this.dirty = true\n }\n\n private updateDraft(draft: FigureDraft) {\n this.recordHistory('draft')\n this.draft = draft\n this.dirty = true\n }\n\n /**\n * 디테일 등급을 바꾼다.\n *\n * 이 값은 부품 수 한도를 정하고 성숙도의 한 축이 된다. 부품보다 위에 있는 값이라\n * 초안에 담는다.\n *\n * 한도를 내려서 이미 넣은 부품 수보다 작아질 수 있다. 그때 부품을 지우지 않는다.\n * 검증이 위반으로 알리고, 성숙도가 등급에 안 맞는다고 말한다. 저작자가 무엇을 뺄지\n * 정할 일이지 도구가 정할 일이 아니다.\n */\n private changeDetailLevel(level: DetailLevel) {\n if (!this.draft) return\n\n this.recordHistory('detailLevel')\n this.draft = { ...this.draft, detailLevel: level }\n this.dirty = true\n }\n\n /**\n * 부품을 잠깐 가린다.\n *\n * 씬이 이미 갖고 있는 상태를 켜고 끄는 것뿐이다 — 편집 모드에서는 faint 로 보이고\n * 뷰 모드에서는 안 그려진다. 저장 형식에는 실리지 않으므로 다시 열면 전부 보인다.\n *\n * `dirty` 로 치지 않는다. 저장될 것이 하나도 바뀌지 않았는데 \"저장하지 않은 변경\"이\n * 뜨면, 저장 버튼이 무엇을 저장하겠다는 것인지 알 수 없다.\n */\n private toggleHidden(index: number) {\n const part = this.parts[index]\n if (!part) return\n\n this.parts = edits.updatePart(this.parts, index, { ...part, hidden: !part.hidden })\n }\n\n /**\n * 한꺼번에 감추거나 되돌린다.\n *\n * 부품 스물짜리에서 안쪽 하나를 보려고 열아홉을 누르던 것을 없앤다. 한 번에 바꾸므로\n * 목록을 한 번만 갈아 끼운다 — 하나씩 스무 번 갈면 미리보기가 스무 번 다시 선다.\n */\n private setAllHidden(hidden: boolean) {\n this.parts = this.parts.map(part => (part.hidden === hidden ? part : { ...part, hidden }))\n }\n\n /**\n * 미리보기에서 기즈모로 옮기거나 2D 에서 끌었다.\n *\n * 알려 오는 것은 부품 **이름**이다. 목록의 몇 번째인지는 여기서 찾는다 — 이벤트가\n * 오는 사이에 목록이 바뀌었으면 찾지 못하고, 그때는 아무것도 하지 않는다.\n */\n private partChanged(name: string, part: PartModel) {\n const at = this.parts.findIndex(item => item.name === name)\n if (at >= 0) this.put(edits.updatePart(this.parts, at, part), this.selected, `drag:${name}`)\n }\n\n /**\n * 정밀 배치 도구.\n *\n * 규칙은 `part-edits` 가 갖는다. 여기서는 어느 것을 부를지만 정한다.\n */\n private runTool(index: number, tool: string, axis: PlaneAxis) {\n const draft = this.draft\n if (!draft) return\n\n if (tool === 'center') this.put(edits.centerPart(draft, this.parts, index, axis))\n else if (tool === 'floor') this.put(edits.alignPartToStart(this.parts, index, axis))\n else if (tool === 'snap') this.put(edits.snapPart(draft, this.parts, index))\n // 대칭 복제는 부품이 하나 늘어난다 — 새로 생긴 것을 선택 상태로 둔다\n else if (tool === 'mirror') this.put(edits.mirrorPart(draft, this.parts, index, axis), index + 1)\n }\n\n /**\n * 캔버스에서 부품을 선택했다.\n *\n * 캔버스가 알리는 것은 **이름**이다. 목록의 몇 번째인지는 여기서 찾는다 — 알림이\n * 오는 사이에 목록이 바뀌었으면 못 찾고, 그때는 고른 것을 바꾸지 않는다.\n * 없는 자리를 고르면 속성 칸이 빈 채로 열려 「사라졌나」로 읽힌다.\n */\n /** 지금 고른 부품 이름. 캔버스가 인덱스가 아니라 이름을 받는다. */\n private get pickedName(): string | undefined {\n return this.parts[this.selected]?.name\n }\n\n /**\n * 고른 부품에 관절이 있으면 **그 관절이 도는 자리**를 저작면에 함께 그린다(`joint-gizmo.ts`).\n *\n * 축을 고르는 칸은 이 화면에 있는데 도는 모습은 미리보기에만 있었다. 고르는 자리와 보이는\n * 자리가 갈리면 사람은 왕복하며 맞춘다 — 여섯 축짜리 로봇이면 여섯 번이다.\n */\n private get jointGizmo(): PartModel | undefined {\n const name = this.pickedName\n if (!name || !this.draft) return undefined\n\n const joint = jointOf(this.draft, name)\n if (!joint) return undefined\n\n return gizmoPart(joint, this.parts.find(part => part.name === name), this.draft)\n }\n\n private pickPart(name: string) {\n const at = this.parts.findIndex(part => part.name === name)\n if (at >= 0) this.selected = at\n }\n\n /**\n * 그림이 없는 도형을 **열었을 때 한 번** 찍어 채운다.\n *\n * ## 왜 여기인가\n *\n * 도면은 저장할 때 서버가 찍는다. 그런데 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는\n * 것이 자주 실패한다. 도형을 그리는 GPU 는 이미 보는 사람 손에 있고, 씬은 지금 여기 서 있다.\n *\n * 저작면을 거치지 않고 생긴 것들이 그림이 없다 — 씨앗으로 넣은 표본, AI 가 낸 후보, 반입한 자산.\n * 그것들을 한 번 열어 보면 그때 채워진다.\n *\n * ## 저장이 아니다\n *\n * 그림만 보낸다. 정본을 건드리지 않으므로 미저장 표시도 켜지 않는다 — 열어 봤다는 것이 고쳤다는\n * 뜻이 되면 저작자가 저장할지 말지를 매번 고민한다.\n */\n /**\n * 발행되었다 — **정본을 든 자리가 그 사실을 받는다.**\n *\n * 이력 패널은 발행을 시키고 결과를 알릴 뿐, 이 페이지의 상태를 고쳐 쓰지 않는다. 판 번호와 상태가\n * 여기서 갱신되어야 머리줄·이력 탭이 같은 것을 본다.\n *\n * 정본(source)은 발행이 건드리지 않으므로 다시 불러오지 않는다 — 저작 중인 화면을 되감지 않는다.\n */\n private released(figure: Figure): void {\n if (!this.figure || figure?.id !== this.figure.id) {\n return\n }\n\n this.figure = { ...this.figure, version: figure.version, state: figure.state, updatedAt: figure.updatedAt }\n }\n\n private async fillThumbnail(id: string): Promise<void> {\n await this.updateComplete\n\n const thumbnail = await captureThumbnail(this.draft, this.parts)\n if (!thumbnail || this.figure?.id !== id) {\n return\n }\n\n try {\n const { figure } = await updateFigure(id, { thumbnail })\n /* 서버가 찍은 시각을 그대로 받는다 — 이 값이 그림을 부르는 주소의 판 번호가 된다. */\n this.figure = { ...this.figure, thumbnailUpdatedAt: figure?.thumbnailUpdatedAt }\n } catch (e) {\n /* 볼 권한만 있는 사람일 수 있다. 화면은 그대로 두고 조용히 넘어간다. */\n console.warn(`[thumbnail] ${id} 를 채우지 못했습니다 — ${(e as Error).message}`)\n }\n }\n\n private save() {\n if (this.sourceLoadFailure || !this.canSave || !this.folded || !this.figure) {\n return\n }\n\n if (!this.figure.id) {\n // 신규 저장: 타입 코드와 표시 이름을 명확히 팝업으로 확인/입력받음\n let defaultType = this.draft?.figureType || this.figure.type || ''\n /*\n A placeholder is not prefilled. This used to prefill 'FIGURE', inviting the author to confirm a\n meaningless name that cannot change later and that the release gate refuses.\n */\n if (!defaultType || isPlaceholderType(defaultType)) {\n defaultType = ''\n }\n this.saveModalType = defaultType\n this.saveModalName = this.figure.name || ''\n this.saveModalError = ''\n this.showSaveModal = true\n return\n }\n\n void this.doSave()\n }\n\n private async doSave() {\n if (!this.folded || !this.figure) return\n\n this.saving = true\n this.saveFailure = ''\n this.saveModalError = ''\n\n try {\n if (!this.figure.id) {\n const typeToSave = (this.saveModalType || this.draft?.figureType || this.figure.type || 'FIGURE').trim().toUpperCase()\n const nameToSave = (this.saveModalName || this.figure.name || typeToSave).trim()\n\n if (!typeToSave) {\n throw new Error(i18next.t('figure.text.enter-a-type-name'))\n }\n\n const taken = await fetchFigureTypeNames()\n if (taken.includes(typeToSave)) {\n throw new Error(i18next.t('figure.text.type-name-already-taken', { type: typeToSave }))\n }\n\n if (this.draft) this.draft.figureType = typeToSave\n this.figure = { ...this.figure, type: typeToSave, name: nameToSave }\n this.folded = { ...this.folded, type: typeToSave }\n const sourceJson = JSON.stringify(this.folded)\n const thumbnail = await captureThumbnail(this.draft, this.parts)\n\n const result = await createFigure({\n type: typeToSave,\n name: nameToSave,\n category: this.figure.category,\n tags: this.figure.tags,\n source: sourceJson,\n thumbnail\n })\n\n this.figure = result.figure\n this.violations = result.violations\n this.showSaveModal = false\n this.dirty = false\n navigate(`figure-modeller/${result.figure.id}`)\n return\n }\n\n // 기존 모델 업데이트: folded.type이 figure.type과 일치하도록 보장\n if (this.figure.type && this.folded.type !== this.figure.type) {\n this.folded = { ...this.folded, type: this.figure.type }\n if (this.draft) this.draft.figureType = this.figure.type\n }\n const sourceJson = JSON.stringify(this.folded)\n const thumbnail = await captureThumbnail(this.draft, this.parts)\n\n const result = await updateFigure(this.figure.id, {\n /*\n 타입을 함께 보낸다. 정본 안의 `type` 만 고치고 행을 안 고치면 저장된 JSON 과 행이\n 다른 이름을 말하고, 발행할 때 어느 쪽이 도면에 적히는지가 갈린다. 발행된 뒤라면\n 문이 거절하고 그 사유가 화면에 그대로 뜬다.\n */\n type: this.figure.type,\n name: this.figure.name,\n category: this.figure.category,\n tags: this.figure.tags,\n source: sourceJson,\n thumbnail\n })\n this.figure = result.figure\n this.violations = result.violations\n this.dirty = false\n } catch (e) {\n const msg = (e as Error).message\n this.saveFailure = msg\n this.saveModalError = msg\n } finally {\n this.saving = false\n }\n }\n\n /** Title of the candidate bar. A candidate opened from a release-check fix is described by translating that fix here. */\n private proposalTitle(session: proposalSessions.ProposalSession): string {\n const fix = session.fix\n if (fix) {\n return i18next.t('figure.text.release-check-fix', {\n part: fix.part,\n axis: fix.axis.toUpperCase(),\n from: fix.from ?? 'auto',\n to: fix.to\n })\n }\n return session.title || i18next.t('figure.text.assistant-proposed-a-figure')\n }\n\n private renderSaveModal() {\n if (!this.showSaveModal) return nothing\n\n return html`\n <div class=\"save-modal-backdrop\" @click=${(e: Event) => { if (e.target === e.currentTarget) this.showSaveModal = false }}>\n <div class=\"save-modal-card\" role=\"dialog\" aria-modal=\"true\">\n <h3 class=\"save-modal-title\">${i18next.t('figure.title.save-new-figure')}</h3>\n <p class=\"save-modal-desc\">${i18next.t('figure.text.save-new-figure-desc')}</p>\n\n <div class=\"save-modal-field\">\n <label>${i18next.t('figure.label.figure-type')}</label>\n <input\n .value=${this.saveModalType}\n placeholder=${i18next.t('figure.text.figure-type-example')}\n @input=${(e: Event) => (this.saveModalType = (e.target as HTMLInputElement).value.trim().toUpperCase())}\n />\n ${this.saveModalType && !releasableType(this.saveModalType)\n ? html`<div class=\"save-modal-error\">${i18next.t('figure.text.type-name-will-not-release')}</div>`\n : nothing}\n </div>\n\n <div class=\"save-modal-field\">\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${this.saveModalName}\n placeholder=${i18next.t('figure.text.figure-name-example')}\n @input=${(e: Event) => (this.saveModalName = (e.target as HTMLInputElement).value)}\n />\n </div>\n\n ${this.saveModalError ? html`<div class=\"save-modal-error\">${this.saveModalError}</div>` : nothing}\n\n <div class=\"save-modal-actions\">\n <button class=\"btn-cancel\" @click=${() => (this.showSaveModal = false)}>\n ${i18next.t('figure.button.cancel')}\n </button>\n <button class=\"btn-confirm\" ?disabled=${this.saving || !this.saveModalType} @click=${() => this.doSave()}>\n ${this.saving ? i18next.t('figure.text.saving-figure') : i18next.t('figure.button.save')}\n </button>\n </div>\n </div>\n </div>\n `\n }\n}\n"]}
@@ -1,78 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
-
4
- import { readFileSync } from 'node:fs'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- import { dialShape } from '../client/modeller/angle-dial.ts'
8
-
9
- /*
10
- * 막대 옆의 각도 눈.
11
- *
12
- * 관절이 돌았다는 것을 3D 안에서만 보이려 했다가 잰 값에 막혔다 — 미리보기의 부품에 단 바늘이
13
- * 4 × 2 픽셀이었다. 여덟 대를 한 화면에 세우는 자리라 한 대가 20 픽셀쯤이고, 그 안의 표시는
14
- * 무엇이든 몇 픽셀을 못 넘는다. 그래서 각은 3D 의 배율에서 떼어 이 눈이 읽는다.
15
- */
16
-
17
- const near = (got: number, want: number, why: string) => assert.ok(Math.abs(got - want) < 0.05, `${why}: ${got}`)
18
-
19
- describe('각도 눈', () => {
20
- it('0 도는 위를 가리킨다', () => {
21
- const dial = dialShape(-90, 90, 0)
22
-
23
- near(dial.needle.x, dial.centre.x, '가운데에서 안 벗어난다')
24
- near(dial.needle.y, dial.centre.y - dial.radius, '위로 반지름만큼')
25
- })
26
-
27
- it('양수는 시계 방향이다 — 화면의 각과 같은 쪽', () => {
28
- const dial = dialShape(-180, 180, 90)
29
-
30
- near(dial.needle.x, dial.centre.x + dial.radius, '오른쪽')
31
- near(dial.needle.y, dial.centre.y, '높이는 가운데')
32
- })
33
-
34
- it('음수는 반대쪽이다', () => {
35
- const dial = dialShape(-180, 180, -90)
36
- near(dial.needle.x, dial.centre.x - dial.radius, '왼쪽')
37
- })
38
-
39
- it('범위 밖의 값은 범위 끝에 선다 — 밖을 가리키는 바늘은 없는 값을 말한다', () => {
40
- const over = dialShape(-90, 90, 200)
41
- const under = dialShape(-90, 90, -200)
42
-
43
- assert.deepEqual(over.needle, dialShape(-90, 90, 90).needle)
44
- assert.deepEqual(under.needle, dialShape(-90, 90, -90).needle)
45
- })
46
-
47
- it('180 도를 넘는 범위는 큰 호로 그린다 — 안 그러면 부채꼴이 반대쪽으로 패인다', () => {
48
- const wide = dialShape(-150, 150, 0)
49
- const narrow = dialShape(-45, 45, 0)
50
-
51
- assert.ok(/A 9 9 0 1 1/.test(wide.sector), `큰 호: ${wide.sector}`)
52
- assert.ok(/A 9 9 0 0 1/.test(narrow.sector), `작은 호: ${narrow.sector}`)
53
- })
54
-
55
- it('한 바퀴는 원이다 — 부채꼴의 두 변이 겹쳐 그어지지 않는다', () => {
56
- const full = dialShape(-180, 180, 45)
57
-
58
- assert.ok(!full.sector.includes('L'), `변을 긋지 않는다: ${full.sector}`)
59
- assert.equal((full.sector.match(/A /g) ?? []).length, 2, '반원 둘로 원을 만든다')
60
- })
61
- })
62
-
63
- describe('눈이 화면에 붙어 있다', () => {
64
- const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
65
-
66
- it('미리보기의 파라미터 줄이 눈을 그린다', () => {
67
- const preview = read('../client/modeller/figure-preview.ts')
68
-
69
- assert.ok(/this\.renderDial\(range, value\)/.test(preview), '막대 옆에 있다')
70
- assert.ok(/dialShape\(range\.min, range\.max, value\)/.test(preview), '값으로 그린다')
71
- })
72
-
73
- it('각이 아닌 파라미터에는 안 단다', () => {
74
- const preview = read('../client/modeller/figure-preview.ts')
75
-
76
- assert.ok(/range\.unit !== 'deg'/.test(preview), 'deg 만')
77
- })
78
- })
@@ -1,235 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
-
4
- import { readFileSync } from 'node:fs'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- import { baseFittingParts, baseOf, partsSettledOnFloor, resizeBase } from '../client/modeller/base-box.ts'
8
- import { toFigureSource } from '../client/modeller/figure-source.ts'
9
- import type { FigureDraft, PartModel } from '../client/modeller/figure-source.ts'
10
-
11
- /*
12
- * 기준 상자를 고치는 일.
13
- *
14
- * 저작면의 자리는 판의 왼쪽 위에서 재고 저장 형식의 자리는 상자 한가운데에서 잰다. 그래서 상자만
15
- * 바꾸면 **저장되는 부품 자리가 전부 달라진다** — 이 시험이 지키는 것이 그 한 가지다.
16
- */
17
-
18
- const DRAFT: FigureDraft = { version: 2, figureType: 'BOX', width: 1000, height: 1000, depth: 500, detailLevel: 'M' }
19
-
20
- const part = (over: Partial<PartModel> = {}): PartModel => ({
21
- type: 'figure-part',
22
- name: 'body',
23
- primitive: 'cube',
24
- left: 400,
25
- top: 400,
26
- width: 200,
27
- height: 200,
28
- zPos: 0,
29
- depth: 300,
30
- token: 'palette.primary',
31
- ...over
32
- })
33
-
34
- /** 저장 형식에서 본 부품 자리. 상자를 고쳐도 이 값이 그대로여야 한다. */
35
- const placed = (draft: FigureDraft, parts: readonly PartModel[]) =>
36
- toFigureSource(draft, [...parts]).parts.map(one => one.transform.position)
37
-
38
- describe('기준 상자', () => {
39
- it('지금 크기를 읽는다', () => {
40
- assert.deepEqual(baseOf(DRAFT), { width: 1000, height: 1000, depth: 500 })
41
- })
42
-
43
- it('상자를 키워도 부품의 저장 자리는 한 값도 안 바뀐다', () => {
44
- const parts = [part(), part({ name: 'lamp', left: 450, top: 450, width: 100, height: 100, zPos: 300, depth: 60 })]
45
- const before = placed(DRAFT, parts)
46
-
47
- const next = resizeBase(DRAFT, parts, { width: 1600, height: 1200, depth: 500 })
48
-
49
- assert.deepEqual(placed(next.draft, next.parts), before)
50
- assert.equal(next.draft.width, 1600)
51
- /* 늘어난 절반만큼 부품이 함께 옮겨 간다 — 저작면의 값은 달라진다. */
52
- assert.equal(next.parts[0]!.left, 400 + 300)
53
- assert.equal(next.parts[0]!.top, 400 + 100)
54
- })
55
-
56
- it('높이만 바꾸면 부품은 안 움직인다 — 밑면이 바닥이다', () => {
57
- const parts = [part()]
58
- const next = resizeBase(DRAFT, parts, { width: 1000, height: 1000, depth: 900 })
59
-
60
- assert.equal(next.parts, parts, '같은 배열 그대로다')
61
- assert.equal(next.draft.depth, 900)
62
- })
63
-
64
- it('달라질 것이 없으면 받은 것을 그대로 돌려준다', () => {
65
- const parts = [part()]
66
- const same = resizeBase(DRAFT, parts, { width: 1000, height: 1000, depth: 500 })
67
-
68
- assert.equal(same.draft, DRAFT)
69
- assert.equal(same.parts, parts)
70
- })
71
-
72
- it('0 으로 만들 수 없다 — 배율이 0 으로 나뉜다', () => {
73
- const next = resizeBase(DRAFT, [part()], { width: 0, height: -20, depth: 0 })
74
- assert.deepEqual(baseOf(next.draft), { width: 1, height: 1, depth: 1 })
75
- })
76
-
77
- describe('부품에 맞추기', () => {
78
- it('부품을 감싸는 가장 작은 상자를 낸다', () => {
79
- /* 가운데(500,500)에서 가장 먼 모서리가 600 이므로 반지름 100 → 200. 높이는 제일 높은 끝. */
80
- const fitted = baseFittingParts(DRAFT, [part()])
81
- assert.deepEqual(fitted, { width: 200, height: 200, depth: 300 })
82
- })
83
-
84
- it('치우친 부품은 한가운데를 지키느라 반대쪽에 여유가 남는다', () => {
85
- /*
86
- 부품을 가운데로 끌어오지 않는다. 끌어오면 저작자가 놓은 자리가 소리 없이 달라지고,
87
- 발행된 도형에서는 문의 자리가 함께 움직인다.
88
- */
89
- const off = part({ left: 700, top: 400, width: 200, height: 200 })
90
- const fitted = baseFittingParts(DRAFT, [off])!
91
-
92
- assert.equal(fitted.width, 800, '가운데에서 900 까지가 400 이므로 양쪽으로 800')
93
- const next = resizeBase(DRAFT, [off], fitted)
94
- assert.deepEqual(placed(next.draft, next.parts), placed(DRAFT, [off]))
95
- })
96
-
97
- /*
98
- 45 도 돌린 판은 제 `size` 보다 넓게 자리를 차지한다. 발행 관문은 `rotatedExtentOf` 로 그것을
99
- 재는데 이 함수는 `width`·`height` 를 그대로 쓰고 있었다. 그래서 회전한 부품이 있는 도형은
100
- 「부품에 맞추기」를 눌러도 발행이 막힌 채로 남았다 — 표본의 CONVEYOR(부품 17 중 4 가 회전)와
101
- ROLL_CRADLE(20 중 13)이 그랬다.
102
-
103
- 기대값을 산술로 베껴 적지 않는다 — 돌린 축이 어느 두 변을 섞는지를 여기 옮겨 적으면 픽스처가
104
- 바뀔 때 조용히 틀린 것을 잰다. 보는 것은 **돌린 것이 안 돌린 것보다 넓게 담기는가** 하나다.
105
- */
106
- it('돌아간 부품은 돌아간 부피로 담는다 — 관문과 같은 함수로 잰다', () => {
107
- const flat = part({ left: 400, top: 400, width: 200, height: 200 })
108
- const turned = part({ left: 400, top: 400, width: 200, height: 200, rotationY: Math.PI / 4 })
109
-
110
- const straight = baseFittingParts(DRAFT, [flat])!
111
- const rotated = baseFittingParts(DRAFT, [turned])!
112
-
113
- assert.ok(
114
- rotated.width > straight.width,
115
- `돌리면 더 넓게 담아야 한다 — 안 돌린 것 ${straight.width} · 돌린 것 ${rotated.width}`
116
- )
117
- })
118
-
119
- it('부품이 없으면 잴 것이 없다', () => {
120
- assert.equal(baseFittingParts(DRAFT, []), undefined)
121
- })
122
- })
123
-
124
- describe('바닥에 앉히기', () => {
125
- /*
126
- 관문이 막는 것은 「가장 낮은 부품이 밑면에 안 닿는다」다. 그 부품 하나만 내리는 길은 전부터
127
- 있었으나(고르고 「바닥에」), 그러면 프레임에 붙어 있던 발이 떨어진다 — 부품 사이의 자리는
128
- 저작자가 정한 것이다. 그래서 전부를 같은 값만큼 옮긴다.
129
- */
130
- it('가장 낮은 것이 바닥에 닿도록 전부를 같은 값만큼 내린다', () => {
131
- const low = part({ zPos: 40, depth: 100 })
132
- const high = part({ zPos: 300, depth: 100 })
133
- const settled = partsSettledOnFloor([low, high])
134
-
135
- assert.equal(settled[0]!.zPos, 0, '가장 낮은 것이 바닥에 닿는다')
136
- assert.equal(settled[1]!.zPos, 260, '사이 거리가 그대로다 — 300 − 40')
137
- })
138
-
139
- it('바닥 아래로 내려간 것은 올린다', () => {
140
- const dipped = part({ zPos: -10, depth: 100 })
141
- const settled = partsSettledOnFloor([dipped])
142
-
143
- assert.equal(settled[0]!.zPos, 0)
144
- })
145
-
146
- it('이미 닿아 있으면 받은 것을 그대로 돌려준다 — 「저장 안 됨」이 붙지 않게', () => {
147
- const parts = [part({ zPos: 0, depth: 100 })]
148
-
149
- assert.equal(partsSettledOnFloor(parts), parts)
150
- })
151
-
152
- it('부품이 없으면 옮길 것이 없다', () => {
153
- const none: never[] = []
154
-
155
- assert.equal(partsSettledOnFloor(none), none)
156
- })
157
-
158
- it('맞춘 상자 안에서는 부품이 상자를 넘지 않는다', () => {
159
- const parts = [part(), part({ name: 'arm', left: 300, top: 250, width: 120, height: 500, zPos: 200, depth: 420 })]
160
- const fitted = baseFittingParts(DRAFT, parts)!
161
- const next = resizeBase(DRAFT, parts, fitted)
162
- const source = toFigureSource(next.draft, [...next.parts])
163
-
164
- for (const one of source.parts) {
165
- const { position, size } = one.transform
166
- assert.ok(Math.abs(position.x) + size.x / 2 <= source.base.x / 2 + 0.001, `${one.name} x`)
167
- assert.ok(Math.abs(position.z) + size.z / 2 <= source.base.z / 2 + 0.001, `${one.name} z`)
168
- assert.ok(position.y + size.y / 2 <= source.base.y + 0.001, `${one.name} y`)
169
- }
170
- })
171
- })
172
- })
173
-
174
- /*
175
- * 셈이 맞아도 **화면에 안 붙어 있으면** 저작자는 아무것도 못 본다. 이 묶음은 붙어 있는지를 본다 —
176
- * 이 파일의 다른 시험들이 전부 초록인 채로 칸이 사라질 수 있는 자리다.
177
- */
178
- describe('화면에 붙어 있다', () => {
179
- const source = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
180
-
181
- it('검사기에 기준 상자 칸과 「부품에 맞추기」가 있다', () => {
182
- const inspector = source('../client/modeller/figure-inspector.ts')
183
-
184
- assert.ok(/figure\.label\.base-box/.test(inspector), '칸 이름')
185
- assert.ok(/this\.resizeBase\(\{ width: value \}\)/.test(inspector), 'X 가 가로를 고친다')
186
- assert.ok(/this\.resizeBase\(\{ depth: value \}\)/.test(inspector), 'Y 가 높이를 고친다')
187
- assert.ok(/this\.fitBaseToParts\(\)/.test(inspector), '부품에 맞추기')
188
- assert.ok(/new CustomEvent\('resize-base'/.test(inspector), '초안과 부품을 함께 올린다')
189
- })
190
-
191
- it('초안과 부품이 한 걸음으로 기록된다 — 한 번 되돌리면 둘 다 돌아온다', () => {
192
- const page = source('../client/pages/figure-modeller-page.ts')
193
-
194
- assert.ok(/@resize-base=\$\{\(e: CustomEvent\) => this\.resizeBase\(e\.detail\.draft, e\.detail\.parts\)\}/.test(page))
195
- assert.ok(/private resizeBase\(draft: FigureDraft, parts: PartModel\[\]\) \{\s*\n\s*this\.recordHistory\('base'\)/.test(page))
196
- })
197
-
198
- it('발행 전에는 타입 칸이 열려 있다 — 기계가 지은 이름을 고칠 자리가 있어야 한다', () => {
199
- const inspector = source('../client/modeller/figure-inspector.ts')
200
- const side = source('../client/modeller/figure-side.ts')
201
- const page = source('../client/pages/figure-modeller-page.ts')
202
-
203
- /* 판 번호가 0 이면 고칠 수 있다. 「저장됐나」로 가르면 저장하는 순간 굳는다. */
204
- assert.ok(/figureVersion \?\? 0\) > 0/.test(inspector), '판 번호로 가른다')
205
- assert.ok(/<figure-inspector[\s\S]{0,200}\.figureVersion=/.test(side), '판 번호가 검사기까지 온다')
206
- assert.ok(/type: this\.figure\.type/.test(page), '저장할 때 행의 타입도 함께 보낸다')
207
- })
208
-
209
- it('발행된 도형을 고치는 동안 그 자리에서 말한다', () => {
210
- const page = source('../client/pages/figure-modeller-page.ts')
211
- const inspector = source('../client/modeller/figure-inspector.ts')
212
-
213
- /* 견줄 상대(직전 발행판)를 가져오고, 고칠 때마다 다시 잰다. */
214
- assert.ok(/fetchReleasedSource\(id\)/.test(page), '직전 발행판을 읽는다')
215
- assert.ok(/contractChanges\(this\.releasedSource, this\.folded\)/.test(page), '고치는 동안 견준다')
216
- assert.ok(/\.contract=\$\{this\.contract\}/.test(page), '화면으로 내려보낸다')
217
-
218
- /* 기준 상자에 대한 것은 그 칸 아래에서 말한다. */
219
- assert.ok(/renderBaseContract\(\)/.test(inspector))
220
- assert.ok(/base-ratio-changed|base-size-changed/.test(inspector))
221
- })
222
-
223
- it('외곽 폭 변경은 비율 수식 대신 선택 가능한 결과로 안내한다', () => {
224
- const history = source('../client/modeller/figure-history-panel.ts')
225
- const side = source('../client/modeller/figure-side.ts')
226
- const page = source('../client/pages/figure-modeller-page.ts')
227
-
228
- assert.ok(/figure\.button\.restore-released-base/.test(history), '이전 폭으로 되돌릴 수 있다')
229
- assert.ok(/figure\.button\.keep-changed-appearance/.test(history), '조정된 폭을 유지할 수 있다')
230
- assert.ok(/restore-released-footprint/.test(history), '복원 의도를 부모에게 보낸다')
231
- assert.ok(/\.releasedBase=\$\{this\.releasedBase\}/.test(side), '직전 발행 기준을 이력 탭까지 보낸다')
232
- assert.ok(/restoreReleasedFootprint\(base: \{ x: number; z: number \}\)/.test(page), 'X·Z만 복원한다')
233
- assert.ok(/depth: this\.draft\.depth/.test(page), 'Y는 이번 리비전 값을 유지한다')
234
- })
235
- })