@things-factory/figure-ui 10.1.16 → 10.1.17

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 (77) hide show
  1. package/client/modeller/figure-animations.ts +12 -12
  2. package/client/modeller/figure-canvas.ts +32 -48
  3. package/client/modeller/figure-capabilities.ts +10 -10
  4. package/client/modeller/figure-inspector.ts +39 -28
  5. package/client/modeller/figure-parts.ts +1 -1
  6. package/client/modeller/figure-preview.ts +122 -25
  7. package/client/modeller/figure-report.ts +24 -6
  8. package/client/modeller/figure-settings.ts +45 -2
  9. package/client/modeller/figure-side.ts +6 -6
  10. package/client/modeller/figure-source.ts +52 -32
  11. package/client/modeller/figure-thumbnail.ts +5 -5
  12. package/client/modeller/figure-view.ts +53 -0
  13. package/client/modeller/part-edits.ts +36 -36
  14. package/client/modeller/proposal.ts +25 -4
  15. package/client/pages/figure-modeller-page.ts +64 -58
  16. package/dist-client/modeller/figure-animations.d.ts +4 -4
  17. package/dist-client/modeller/figure-animations.js +10 -10
  18. package/dist-client/modeller/figure-animations.js.map +1 -1
  19. package/dist-client/modeller/figure-canvas.d.ts +7 -7
  20. package/dist-client/modeller/figure-canvas.js +27 -40
  21. package/dist-client/modeller/figure-canvas.js.map +1 -1
  22. package/dist-client/modeller/figure-capabilities.d.ts +2 -2
  23. package/dist-client/modeller/figure-capabilities.js +9 -9
  24. package/dist-client/modeller/figure-capabilities.js.map +1 -1
  25. package/dist-client/modeller/figure-inspector.d.ts +16 -5
  26. package/dist-client/modeller/figure-inspector.js +38 -27
  27. package/dist-client/modeller/figure-inspector.js.map +1 -1
  28. package/dist-client/modeller/figure-parts.d.ts +1 -1
  29. package/dist-client/modeller/figure-parts.js +1 -1
  30. package/dist-client/modeller/figure-parts.js.map +1 -1
  31. package/dist-client/modeller/figure-preview.d.ts +39 -0
  32. package/dist-client/modeller/figure-preview.js +112 -23
  33. package/dist-client/modeller/figure-preview.js.map +1 -1
  34. package/dist-client/modeller/figure-report.d.ts +9 -0
  35. package/dist-client/modeller/figure-report.js +23 -5
  36. package/dist-client/modeller/figure-report.js.map +1 -1
  37. package/dist-client/modeller/figure-settings.js +42 -2
  38. package/dist-client/modeller/figure-settings.js.map +1 -1
  39. package/dist-client/modeller/figure-side.d.ts +2 -2
  40. package/dist-client/modeller/figure-side.js +5 -5
  41. package/dist-client/modeller/figure-side.js.map +1 -1
  42. package/dist-client/modeller/figure-source.d.ts +17 -15
  43. package/dist-client/modeller/figure-source.js +40 -21
  44. package/dist-client/modeller/figure-source.js.map +1 -1
  45. package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
  46. package/dist-client/modeller/figure-thumbnail.js +3 -3
  47. package/dist-client/modeller/figure-thumbnail.js.map +1 -1
  48. package/dist-client/modeller/figure-view.d.ts +45 -0
  49. package/dist-client/modeller/figure-view.js +27 -0
  50. package/dist-client/modeller/figure-view.js.map +1 -1
  51. package/dist-client/modeller/part-edits.d.ts +16 -16
  52. package/dist-client/modeller/part-edits.js +35 -35
  53. package/dist-client/modeller/part-edits.js.map +1 -1
  54. package/dist-client/modeller/proposal.d.ts +1 -1
  55. package/dist-client/modeller/proposal.js +15 -3
  56. package/dist-client/modeller/proposal.js.map +1 -1
  57. package/dist-client/pages/figure-modeller-page.d.ts +9 -10
  58. package/dist-client/pages/figure-modeller-page.js +60 -54
  59. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  60. package/dist-client/tsconfig.tsbuildinfo +1 -1
  61. package/dist-server/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +4 -4
  63. package/test/ai-proposal-contract.test.ts +393 -12
  64. package/test/expect.ts +55 -0
  65. package/test/figure-source.test.ts +562 -0
  66. package/test/i18n-prefix-guard.test.ts +1 -1
  67. package/{client/modeller → test}/part-edits.test.ts +50 -45
  68. package/translations/en.json +31 -28
  69. package/translations/ja.json +16 -21
  70. package/translations/ko.json +41 -38
  71. package/translations/ms.json +16 -21
  72. package/translations/zh.json +16 -21
  73. package/client/modeller/figure-ask.ts +0 -265
  74. package/client/modeller/figure-source.test.ts +0 -352
  75. package/dist-client/modeller/figure-ask.d.ts +0 -40
  76. package/dist-client/modeller/figure-ask.js +0 -282
  77. package/dist-client/modeller/figure-ask.js.map +0 -1
@@ -1,6 +1,5 @@
1
1
  import '@material/web/icon/icon.js'
2
2
  import '../modeller/figure-side.js'
3
- import '../modeller/figure-ask.js'
4
3
  import '../modeller/figure-canvas.js'
5
4
  import { captureThumbnail } from '../modeller/figure-thumbnail.js'
6
5
  import '../modeller/figure-preview.js'
@@ -23,7 +22,7 @@ import * as proposals from '../modeller/proposal.js'
23
22
  import * as proposalSessions from '../modeller/proposal-session.js'
24
23
  import type { ProposalSession } from '../modeller/proposal-session.js'
25
24
  import { toFigureSource, fromFigureSource } from '../modeller/figure-source.js'
26
- import type { BoardModel, PartModel, PlaneAxis } from '../modeller/figure-source.js'
25
+ import type { FigureDraft, PartModel, PlaneAxis } from '../modeller/figure-source.js'
27
26
  import { DEFAULT_VIEW } from '../modeller/figure-view.js'
28
27
  import type { ViewSettings } from '../modeller/figure-view.js'
29
28
  import { createFigure, fetchFigure, fetchFigureTypeNames, updateFigure } from '../graphql/index.js'
@@ -86,9 +85,9 @@ function rememberPanes(panes: Panes): void {
86
85
  * 빈 화면이 가르치는 자리다: 부품 목록이 「위에서 도형을 골라 시작하세요」를 이미 말하고 팔레트가
87
86
  * 바로 그 위에 있다(`figure-parts`).
88
87
  */
89
- function startingModel(type: string): { board: BoardModel; parts: PartModel[] } {
88
+ function startingModel(type: string): { draft: FigureDraft; parts: PartModel[] } {
90
89
  return {
91
- board: { width: 300, height: 300, depth: 300, figureType: type, detailLevel: 'M' },
90
+ draft: { width: 300, height: 300, depth: 300, figureType: type, detailLevel: 'M' },
92
91
  parts: []
93
92
  }
94
93
  }
@@ -107,7 +106,7 @@ function startingModel(type: string): { board: BoardModel; parts: PartModel[] }
107
106
  *
108
107
  * ## 편집 데이터는 씬 모델 하나뿐이다
109
108
  *
110
- * 이 페이지가 갖는 것은 보드 하나와 부품 목록 — 미리보기에 그대로 세워지는 씬 모델이다.
109
+ * 이 페이지가 갖는 것은 초안 하나와 부품 목록 — 미리보기에 그대로 세워지는 씬 모델이다.
111
110
  * 저장 형식(`FigureSource`)으로 바꾸는 것은 **경계에서만** 한다: 저장할 때, 판단할 때,
112
111
  * AI 후보와 비교할 때. 그 변환은 `figure-source.ts` 한 곳에서만 일어난다.
113
112
  *
@@ -623,7 +622,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
623
622
  /** 편집 중인 Figure. 새로 만드는 중이면 `id` 가 없다. */
624
623
  @state() private figure?: Figure
625
624
  /** 편집 데이터 — 미리보기에 그대로 세워지는 씬 모델이다. */
626
- @state() private board?: BoardModel
625
+ @state() private draft?: FigureDraft
627
626
  @state() private parts: PartModel[] = []
628
627
  @state() private selected = -1
629
628
  /*
@@ -668,8 +667,8 @@ export class FigureModellerPage extends FigureModellerPageBase {
668
667
  /** 인라인 요청창과 AI 도크가 함께 쓰는 현재 Figure의 제한된 세션 피드백. */
669
668
  @state() private proposalFeedback: ProposalFeedback[] = []
670
669
 
671
- @state() private undoStack: { board?: BoardModel; parts: PartModel[]; selected: number }[] = []
672
- @state() private redoStack: { board?: BoardModel; parts: PartModel[]; selected: number }[] = []
670
+ @state() private undoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []
671
+ @state() private redoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []
673
672
  @state() private sceneRevision = 0
674
673
  private lastCoalesceKey?: string
675
674
  private lastHistoryTime = 0
@@ -683,15 +682,15 @@ export class FigureModellerPage extends FigureModellerPageBase {
683
682
  private folded?: FigureSource
684
683
 
685
684
  willUpdate(changed: Map<string, unknown>) {
686
- if (changed.has('board') || changed.has('parts')) {
687
- this.folded = this.board ? toFigureSource(this.board, this.parts) : undefined
685
+ if (changed.has('draft') || changed.has('parts')) {
686
+ this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined
688
687
  }
689
688
  }
690
689
 
691
690
  /**
692
691
  * 페이지 밖 AI 도크에도 **지금 손에 든 정본**을 알려 준다.
693
692
  *
694
- * 모델러 안의 `figure-ask` 는 이미 `folded`를 직접 받는다. 반면 asidebar 도크는 주소의 id만
693
+ * asidebar 도크는 주소의 id만
695
694
  * 알았으므로 DB에 저장된 옛 판을 읽었다. 저장 전 변경을 두고 "왜 발행이 안 되나" 또는
696
695
  * "이 부품을 줄여 달라"고 물으면 대화가 엉뚱한 판을 보게 되는 결함이었다.
697
696
  *
@@ -703,7 +702,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
703
702
  if (changed.has('figure') && (changed.get('figure') as Figure | undefined)?.id !== this.figure?.id) {
704
703
  this.proposalFeedback = []
705
704
  }
706
- if (changed.has('board') || changed.has('parts') || changed.has('figure')) {
705
+ if (changed.has('draft') || changed.has('parts') || changed.has('figure')) {
707
706
  window.dispatchEvent(
708
707
  new CustomEvent('figure-ai-context', {
709
708
  detail: { figureId: this.figure?.id, source: this.folded }
@@ -753,15 +752,15 @@ export class FigureModellerPage extends FigureModellerPageBase {
753
752
  /**
754
753
  * A candidate from the dock opens the review lane. It does not become the draft.
755
754
  *
756
- * This used to write `board` and `parts` straight onto the working draft and then set
755
+ * This used to write `draft` and `parts` straight onto the working draft and then set
757
756
  * `proposalSession = undefined` — the dock's candidate overwrote what the author had open and
758
757
  * erased the lane built to review it. There was no moment where the person said yes, and a
759
758
  * draft has no version history to go back to, so the previous work was simply gone.
760
759
  *
761
760
  * `ai-client-base`'s analysis contract states this as a literal type: `approvalRequired: true`,
762
761
  * and of `proposal`, "It is never an instruction to mutate canonical state." The in-page
763
- * `figure-ask` entrance already honoured it through `openAiProposal`; the dock entrance is now
764
- * the same door. Applying is what the take button does, from the lane, per change.
762
+ * The in-page entrance honoured this through `openAiProposal` before it was removed, and the dock
763
+ * entrance is now the same door. Applying is what the take button does, from the lane, per change.
765
764
  *
766
765
  * ## Why the overwrite was there — so it does not come back
767
766
  *
@@ -899,13 +898,13 @@ export class FigureModellerPage extends FigureModellerPageBase {
899
898
  @toggle-part-hidden=${(e: CustomEvent) => this.toggleHidden(e.detail.index)}
900
899
  @set-all-parts-hidden=${(e: CustomEvent) => this.setAllHidden(e.detail.hidden)}
901
900
  @change-detail-level=${(e: CustomEvent) => this.changeDetailLevel(e.detail.level)}
902
- @update-board=${(e: CustomEvent) => this.updateBoard(e.detail.board)}
901
+ @update-draft=${(e: CustomEvent) => this.updateDraft(e.detail.draft)}
903
902
  @part-tool=${(e: CustomEvent) => this.runTool(e.detail.index, e.detail.tool, e.detail.axis)}
904
903
  @pick-part=${(e: CustomEvent) => this.pickPart(e.detail.name)}
905
904
  >
906
905
  <figure-parts
907
906
  .parts=${this.parts}
908
- .level=${(this.board?.detailLevel ?? 'M') as DetailLevel}
907
+ .level=${(this.draft?.detailLevel ?? 'M') as DetailLevel}
909
908
  .selected=${this.selected}
910
909
  ></figure-parts>
911
910
 
@@ -919,7 +918,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
919
918
  <h4>${i18next.t('figure.label.now')}</h4>
920
919
  <figure-canvas
921
920
  .figureId=${this.figure?.id || this.figure?.type}
922
- .board=${this.board}
921
+ .draft=${this.draft}
923
922
  .parts=${this.parts}
924
923
  .picked=${this.pickedName}
925
924
  .view=${this.view}
@@ -942,10 +941,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
942
941
  </div>
943
942
  `
944
943
  : this.mode === 'preview'
945
- ? html`<figure-preview .source=${source}></figure-preview>`
944
+ ? html`<figure-preview .source=${source} .view=${this.view}></figure-preview>`
946
945
  : html`<figure-canvas
947
946
  .figureId=${this.figure?.id || this.figure?.type}
948
- .board=${this.board}
947
+ .draft=${this.draft}
949
948
  .parts=${this.parts}
950
949
  .picked=${this.pickedName}
951
950
  .view=${this.view}
@@ -961,7 +960,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
961
960
  .figureState=${this.figure?.state}
962
961
  .figureVersion=${this.figure?.version ?? 0}
963
962
  .dirty=${this.dirty}
964
- .board=${this.board}
963
+ .draft=${this.draft}
965
964
  .parts=${this.parts}
966
965
  .source=${source}
967
966
  .selected=${this.selected}
@@ -1085,7 +1084,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1085
1084
  private renderProposed() {
1086
1085
  if (!this.proposalSession) return ''
1087
1086
 
1088
- return html`<figure-preview .source=${this.proposalSession.source}></figure-preview>`
1087
+ return html`<figure-preview .source=${this.proposalSession.source} .view=${this.view}></figure-preview>`
1089
1088
  }
1090
1089
 
1091
1090
  /**
@@ -1171,15 +1170,22 @@ export class FigureModellerPage extends FigureModellerPageBase {
1171
1170
 
1172
1171
  /** 변경 하나. 무엇을 하겠다는 것인지 부호로 먼저 말한다. */
1173
1172
  private renderChange(change: proposals.ProposalChange) {
1174
- const icon = {
1173
+ /*
1174
+ Every ChangeKind needs a row here. A kind the map does not answer draws an empty icon and a
1175
+ key with no name beside it, which reads as a broken row rather than a missing case — the
1176
+ `satisfies` makes the compiler say so instead.
1177
+ */
1178
+ const icon = ({
1175
1179
  added: 'add',
1176
1180
  changed: 'edit',
1177
1181
  removed: 'remove',
1178
1182
  base: 'aspect_ratio',
1183
+ placement: 'vertical_align_bottom',
1179
1184
  detail: 'tune',
1180
- anchor: 'my_location',
1181
- styleKit: 'palette'
1182
- }[change.kind]
1185
+ styleKit: 'palette',
1186
+ animations: 'animation',
1187
+ capabilities: 'hub'
1188
+ } satisfies Record<proposals.ChangeKind, string>)[change.kind]
1183
1189
 
1184
1190
  const on = this.proposalSession?.picked.has(change.key)
1185
1191
 
@@ -1214,7 +1220,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1214
1220
  /**
1215
1221
  * 발행 게이트의 구조화된 수정안 하나를 현재 작업 사본에만 겹쳐 본다. 서버가 준 값은
1216
1222
  * `part.anchor.axis = to`라는 데이터이지 사람이 읽을 문장을 다시 해석한 결과가 아니다.
1217
- * 원본 `board`/`parts`는 여기서 전혀 바꾸지 않고, 아래의 `take()`만 바꾼 사본을 적용한다.
1223
+ * 원본 `draft`/`parts`는 여기서 전혀 바꾸지 않고, 아래의 `take()`만 바꾼 사본을 적용한다.
1218
1224
  */
1219
1225
  private previewSizingFix(fix: FigureGateFix) {
1220
1226
  if (!this.folded) return
@@ -1245,9 +1251,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
1245
1251
 
1246
1252
  this.recordHistory()
1247
1253
  const taken = proposals.applyProposal(this.folded, this.proposalSession.source, this.proposalSession.picked)
1248
- const { board, parts } = fromFigureSource(taken)
1254
+ const { draft, parts } = fromFigureSource(taken)
1249
1255
 
1250
- this.board = board
1256
+ this.draft = draft
1251
1257
  this.parts = parts
1252
1258
  this.selected = parts.length ? 0 : -1
1253
1259
  this.dirty = true
@@ -1398,7 +1404,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1398
1404
  // 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
1399
1405
  // 그러면 사용자가 저장을 선택해 원본을 덮어쓴다.
1400
1406
  const model = figure.source ? fromFigureSource(JSON.parse(figure.source) as FigureSource) : undefined
1401
- this.board = model?.board
1407
+ this.draft = model?.draft
1402
1408
  this.parts = model?.parts ?? []
1403
1409
 
1404
1410
  /*
@@ -1428,10 +1434,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
1428
1434
  this.mode = 'edit'
1429
1435
  // 타입 이름을 사람이 정하기 전까지는 임시 이름을 쓴다. 저장할 때 확정한다.
1430
1436
  const draftType = `FIGURE_${Date.now().toString(36).toUpperCase()}`
1431
- const { board, parts } = startingModel(draftType)
1437
+ const { draft, parts } = startingModel(draftType)
1432
1438
 
1433
1439
  this.figure = { id: '', type: draftType, name: '' } as Figure
1434
- this.board = board
1440
+ this.draft = draft
1435
1441
  this.parts = parts
1436
1442
  /* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */
1437
1443
  this.selected = -1
@@ -1452,9 +1458,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
1452
1458
  private typeChanged(type: string) {
1453
1459
  if (!type || this.figure?.id) return
1454
1460
  const sanitized = type.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()
1455
- if (this.board) this.board.figureType = sanitized
1461
+ if (this.draft) this.draft.figureType = sanitized
1456
1462
  this.figure = { ...this.figure, type: sanitized } as Figure
1457
- this.folded = this.board ? toFigureSource(this.board, this.parts) : undefined
1463
+ this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined
1458
1464
  this.dirty = true
1459
1465
  }
1460
1466
 
@@ -1471,9 +1477,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
1471
1477
  this.dirty = true
1472
1478
  }
1473
1479
 
1474
- private snapshotState(): { board?: BoardModel; parts: PartModel[]; selected: number } {
1480
+ private snapshotState(): { draft?: FigureDraft; parts: PartModel[]; selected: number } {
1475
1481
  return {
1476
- board: this.board ? structuredClone(this.board) : undefined,
1482
+ draft: this.draft ? structuredClone(this.draft) : undefined,
1477
1483
  parts: structuredClone(this.parts),
1478
1484
  selected: this.selected
1479
1485
  }
@@ -1499,7 +1505,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1499
1505
  this.undoStack = this.undoStack.slice(0, -1)
1500
1506
  this.redoStack = [this.snapshotState(), ...this.redoStack.slice(0, 49)]
1501
1507
 
1502
- this.board = prev.board ? structuredClone(prev.board) : undefined
1508
+ this.draft = prev.draft ? structuredClone(prev.draft) : undefined
1503
1509
  this.parts = structuredClone(prev.parts)
1504
1510
  this.selected = this.parts.length ? Math.max(-1, Math.min(prev.selected, this.parts.length - 1)) : -1
1505
1511
  this.dirty = true
@@ -1512,7 +1518,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1512
1518
  this.redoStack = this.redoStack.slice(1)
1513
1519
  this.undoStack = [...this.undoStack.slice(-49), this.snapshotState()]
1514
1520
 
1515
- this.board = next.board ? structuredClone(next.board) : undefined
1521
+ this.draft = next.draft ? structuredClone(next.draft) : undefined
1516
1522
  this.parts = structuredClone(next.parts)
1517
1523
  this.selected = this.parts.length ? Math.max(-1, Math.min(next.selected, this.parts.length - 1)) : -1
1518
1524
  this.dirty = true
@@ -1533,9 +1539,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
1533
1539
  }
1534
1540
 
1535
1541
  private addPart(primitive: PrimitiveKind) {
1536
- if (!this.board) return
1542
+ if (!this.draft) return
1537
1543
  // 추가한 것을 바로 수정할 수 있게 선택 상태로 둔다.
1538
- this.put(edits.addPart(this.board, this.parts, primitive), this.parts.length)
1544
+ this.put(edits.addPart(this.draft, this.parts, primitive), this.parts.length)
1539
1545
  }
1540
1546
 
1541
1547
  private removePart(index: number) {
@@ -1560,9 +1566,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
1560
1566
  * 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
1561
1567
  * 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
1562
1568
  */
1563
- private updateBoard(board: BoardModel) {
1564
- this.recordHistory('board')
1565
- this.board = board
1569
+ private updateDraft(draft: FigureDraft) {
1570
+ this.recordHistory('draft')
1571
+ this.draft = draft
1566
1572
  this.dirty = true
1567
1573
  }
1568
1574
 
@@ -1570,17 +1576,17 @@ export class FigureModellerPage extends FigureModellerPageBase {
1570
1576
  * 디테일 등급을 바꾼다.
1571
1577
  *
1572
1578
  * 이 값은 부품 수 한도를 정하고 성숙도의 한 축이 된다. 부품보다 위에 있는 값이라
1573
- * 보드에 담는다.
1579
+ * 초안에 담는다.
1574
1580
  *
1575
1581
  * 한도를 내려서 이미 넣은 부품 수보다 작아질 수 있다. 그때 부품을 지우지 않는다.
1576
1582
  * 검증이 위반으로 알리고, 성숙도가 등급에 안 맞는다고 말한다. 저작자가 무엇을 뺄지
1577
1583
  * 정할 일이지 도구가 정할 일이 아니다.
1578
1584
  */
1579
1585
  private changeDetailLevel(level: DetailLevel) {
1580
- if (!this.board) return
1586
+ if (!this.draft) return
1581
1587
 
1582
1588
  this.recordHistory('detailLevel')
1583
- this.board = { ...this.board, detailLevel: level }
1589
+ this.draft = { ...this.draft, detailLevel: level }
1584
1590
  this.dirty = true
1585
1591
  }
1586
1592
 
@@ -1627,14 +1633,14 @@ export class FigureModellerPage extends FigureModellerPageBase {
1627
1633
  * 규칙은 `part-edits` 가 갖는다. 여기서는 어느 것을 부를지만 정한다.
1628
1634
  */
1629
1635
  private runTool(index: number, tool: string, axis: PlaneAxis) {
1630
- const board = this.board
1631
- if (!board) return
1636
+ const draft = this.draft
1637
+ if (!draft) return
1632
1638
 
1633
- if (tool === 'center') this.put(edits.centerPart(board, this.parts, index, axis))
1639
+ if (tool === 'center') this.put(edits.centerPart(draft, this.parts, index, axis))
1634
1640
  else if (tool === 'floor') this.put(edits.alignPartToStart(this.parts, index, axis))
1635
- else if (tool === 'snap') this.put(edits.snapPart(board, this.parts, index))
1641
+ else if (tool === 'snap') this.put(edits.snapPart(draft, this.parts, index))
1636
1642
  // 대칭 복제는 부품이 하나 늘어난다 — 새로 생긴 것을 선택 상태로 둔다
1637
- else if (tool === 'mirror') this.put(edits.mirrorPart(board, this.parts, index, axis), index + 1)
1643
+ else if (tool === 'mirror') this.put(edits.mirrorPart(draft, this.parts, index, axis), index + 1)
1638
1644
  }
1639
1645
 
1640
1646
  /**
@@ -1689,7 +1695,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1689
1695
  private async fillThumbnail(id: string): Promise<void> {
1690
1696
  await this.updateComplete
1691
1697
 
1692
- const thumbnail = await captureThumbnail(this.board, this.parts)
1698
+ const thumbnail = await captureThumbnail(this.draft, this.parts)
1693
1699
  if (!thumbnail || this.figure?.id !== id) {
1694
1700
  return
1695
1701
  }
@@ -1711,7 +1717,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1711
1717
 
1712
1718
  if (!this.figure.id) {
1713
1719
  // 신규 저장: 타입 코드와 표시 이름을 명확히 팝업으로 확인/입력받음
1714
- let defaultType = this.board?.figureType || this.figure.type || ''
1720
+ let defaultType = this.draft?.figureType || this.figure.type || ''
1715
1721
  if (!defaultType || defaultType.startsWith('FIGURE_')) {
1716
1722
  defaultType = 'FIGURE'
1717
1723
  }
@@ -1734,7 +1740,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
1734
1740
 
1735
1741
  try {
1736
1742
  if (!this.figure.id) {
1737
- const typeToSave = (this.saveModalType || this.board?.figureType || this.figure.type || 'FIGURE').trim().toUpperCase()
1743
+ const typeToSave = (this.saveModalType || this.draft?.figureType || this.figure.type || 'FIGURE').trim().toUpperCase()
1738
1744
  const nameToSave = (this.saveModalName || this.figure.name || typeToSave).trim()
1739
1745
 
1740
1746
  if (!typeToSave) {
@@ -1746,11 +1752,11 @@ export class FigureModellerPage extends FigureModellerPageBase {
1746
1752
  throw new Error(i18next.t('figure.text.type-name-already-taken', { type: typeToSave }))
1747
1753
  }
1748
1754
 
1749
- if (this.board) this.board.figureType = typeToSave
1755
+ if (this.draft) this.draft.figureType = typeToSave
1750
1756
  this.figure = { ...this.figure, type: typeToSave, name: nameToSave }
1751
1757
  this.folded = { ...this.folded, type: typeToSave }
1752
1758
  const sourceJson = JSON.stringify(this.folded)
1753
- const thumbnail = await captureThumbnail(this.board, this.parts)
1759
+ const thumbnail = await captureThumbnail(this.draft, this.parts)
1754
1760
 
1755
1761
  const result = await createFigure({
1756
1762
  type: typeToSave,
@@ -1772,10 +1778,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
1772
1778
  // 기존 모델 업데이트: folded.type이 figure.type과 일치하도록 보장
1773
1779
  if (this.figure.type && this.folded.type !== this.figure.type) {
1774
1780
  this.folded = { ...this.folded, type: this.figure.type }
1775
- if (this.board) this.board.figureType = this.figure.type
1781
+ if (this.draft) this.draft.figureType = this.figure.type
1776
1782
  }
1777
1783
  const sourceJson = JSON.stringify(this.folded)
1778
- const thumbnail = await captureThumbnail(this.board, this.parts)
1784
+ const thumbnail = await captureThumbnail(this.draft, this.parts)
1779
1785
 
1780
1786
  const result = await updateFigure(this.figure.id, {
1781
1787
  name: this.figure.name,
@@ -1,9 +1,9 @@
1
1
  import { LitElement, type TemplateResult } from 'lit';
2
- import type { BoardModel, PartModel } from './figure-source.js';
2
+ import type { FigureDraft, PartModel } from './figure-source.js';
3
3
  declare const FigureAnimations_base: (new (...args: any[]) => LitElement) & typeof LitElement;
4
4
  export declare class FigureAnimations extends FigureAnimations_base {
5
5
  static styles: import("lit").CSSResult[];
6
- board?: BoardModel;
6
+ draft?: FigureDraft;
7
7
  parts: PartModel[];
8
8
  /** 펼쳐 둔 clip. 이름이 아니라 자리로 잡는다 — 이름은 편집 중에 바뀐다. */
9
9
  private open;
@@ -62,9 +62,9 @@ export declare class FigureAnimations extends FigureAnimations_base {
62
62
  */
63
63
  private renderBudget;
64
64
  /**
65
- * 보드에 실어 올려보낸다.
65
+ * 초안에 실어 올려보낸다.
66
66
  *
67
- * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-board` 로
67
+ * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
68
68
  * 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
69
69
  */
70
70
  private tell;
@@ -246,7 +246,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
246
246
  `
247
247
  ]; }
248
248
  get clips() {
249
- return this.board?.animations ?? [];
249
+ return this.draft?.animations ?? [];
250
250
  }
251
251
  /** 부품 이름 — 채널이 이것을 가리킨다. */
252
252
  get partNames() {
@@ -266,7 +266,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
266
266
  return moving.size;
267
267
  }
268
268
  render() {
269
- if (!this.board)
269
+ if (!this.draft)
270
270
  return html ``;
271
271
  return html `
272
272
  <section>
@@ -550,20 +550,20 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
550
550
  }
551
551
  // ─── 고치기 ───────────────────────────────────────────────────────────────
552
552
  /**
553
- * 보드에 실어 올려보낸다.
553
+ * 초안에 실어 올려보낸다.
554
554
  *
555
- * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-board` 로
555
+ * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
556
556
  * 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
557
557
  */
558
558
  tell(animations) {
559
- if (!this.board)
559
+ if (!this.draft)
560
560
  return;
561
- const board = { ...this.board };
561
+ const draft = { ...this.draft };
562
562
  if (animations.length === 0)
563
- delete board.animations;
563
+ delete draft.animations;
564
564
  else
565
- board.animations = animations;
566
- this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }));
565
+ draft.animations = animations;
566
+ this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
567
567
  }
568
568
  patchClip(at, patch) {
569
569
  this.tell(this.clips.map((clip, i) => (i === at ? { ...clip, ...patch } : clip)));
@@ -688,7 +688,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
688
688
  __decorate([
689
689
  property({ type: Object }),
690
690
  __metadata("design:type", Object)
691
- ], FigureAnimations.prototype, "board", void 0);
691
+ ], FigureAnimations.prototype, "draft", void 0);
692
692
  __decorate([
693
693
  property({ type: Array }),
694
694
  __metadata("design:type", Array)