@things-factory/figure-ui 10.1.37 → 10.1.39

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 (62) hide show
  1. package/client/graphql/index.ts +53 -0
  2. package/client/graphql/v3-store.ts +8 -2
  3. package/client/modeller/figure-history-panel.ts +33 -87
  4. package/client/modeller/figure-v3-parts.ts +365 -0
  5. package/client/modeller/figure-v3-verdict.ts +247 -0
  6. package/client/modeller/gate-findings.ts +105 -0
  7. package/client/modeller/v3-editor.ts +11 -2
  8. package/client/modeller/v3-occupancy.ts +22 -0
  9. package/client/modeller/v3-product-session.ts +23 -4
  10. package/client/modeller/v3-renderer.ts +27 -18
  11. package/client/modeller/v3-thumbnail.ts +97 -0
  12. package/client/pages/figure-v3-modeller-page.ts +272 -12
  13. package/client/route.ts +8 -0
  14. package/dist-client/graphql/index.d.ts +8 -0
  15. package/dist-client/graphql/index.js +51 -0
  16. package/dist-client/graphql/index.js.map +1 -1
  17. package/dist-client/graphql/v3-store.js +8 -2
  18. package/dist-client/graphql/v3-store.js.map +1 -1
  19. package/dist-client/modeller/figure-history-panel.d.ts +21 -0
  20. package/dist-client/modeller/figure-history-panel.js +34 -87
  21. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  22. package/dist-client/modeller/figure-v3-parts.d.ts +41 -0
  23. package/dist-client/modeller/figure-v3-parts.js +352 -0
  24. package/dist-client/modeller/figure-v3-parts.js.map +1 -0
  25. package/dist-client/modeller/figure-v3-verdict.d.ts +50 -0
  26. package/dist-client/modeller/figure-v3-verdict.js +268 -0
  27. package/dist-client/modeller/figure-v3-verdict.js.map +1 -0
  28. package/dist-client/modeller/gate-findings.d.ts +12 -0
  29. package/dist-client/modeller/gate-findings.js +102 -0
  30. package/dist-client/modeller/gate-findings.js.map +1 -0
  31. package/dist-client/modeller/v3-editor.js +11 -2
  32. package/dist-client/modeller/v3-editor.js.map +1 -1
  33. package/dist-client/modeller/v3-occupancy.d.ts +14 -0
  34. package/dist-client/modeller/v3-occupancy.js +22 -0
  35. package/dist-client/modeller/v3-occupancy.js.map +1 -0
  36. package/dist-client/modeller/v3-product-session.d.ts +44 -2
  37. package/dist-client/modeller/v3-product-session.js +13 -2
  38. package/dist-client/modeller/v3-product-session.js.map +1 -1
  39. package/dist-client/modeller/v3-renderer.d.ts +9 -0
  40. package/dist-client/modeller/v3-renderer.js +22 -30
  41. package/dist-client/modeller/v3-renderer.js.map +1 -1
  42. package/dist-client/modeller/v3-thumbnail.d.ts +3 -0
  43. package/dist-client/modeller/v3-thumbnail.js +74 -0
  44. package/dist-client/modeller/v3-thumbnail.js.map +1 -0
  45. package/dist-client/pages/figure-v3-modeller-page.d.ts +52 -1
  46. package/dist-client/pages/figure-v3-modeller-page.js +294 -12
  47. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  48. package/dist-client/route.d.ts +1 -1
  49. package/dist-client/route.js +7 -0
  50. package/dist-client/route.js.map +1 -1
  51. package/dist-client/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +5 -5
  53. package/test/resolve-ts-siblings.mjs +18 -0
  54. package/test/v3-product-write.test.ts +14 -2
  55. package/test/v3-release-panel.test.ts +73 -0
  56. package/test/v3-verdict.test.ts +82 -0
  57. package/things-factory.config.js +6 -1
  58. package/translations/en.json +79 -1
  59. package/translations/ja.json +79 -1
  60. package/translations/ko.json +79 -1
  61. package/translations/ms.json +79 -1
  62. package/translations/zh.json +79 -1
@@ -4,8 +4,22 @@ import { customElement, state } from 'lit/decorators.js';
4
4
  import { PageView } from '@operato/shell';
5
5
  import { createV3ProductSession } from '../modeller/v3-product-session.js';
6
6
  import { v3ProductStore } from '../graphql/v3-store.js';
7
+ import { i18next } from '@operato/i18n';
8
+ import { fetchFigure, updateFigure } from '../graphql/index.js';
9
+ import { occupancyFromParts } from '../modeller/v3-occupancy.js';
7
10
  import '../modeller/v3-editor.js';
8
- /** The V3 product page has exactly one authoritative workspace for UI and AI input transactions. */
11
+ import '@material/web/icon/icon.js';
12
+ import '../modeller/figure-history-panel.js';
13
+ import '../modeller/figure-v3-verdict.js';
14
+ /**
15
+ * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
16
+ *
17
+ * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches
18
+ * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a
19
+ * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route
20
+ * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.
21
+ * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.
22
+ */
9
23
  let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
10
24
  constructor() {
11
25
  super(...arguments);
@@ -13,6 +27,24 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
13
27
  this.discardApproved = false;
14
28
  this.revision = 0;
15
29
  this.error = '';
30
+ /**
31
+ * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.
32
+ *
33
+ * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서
34
+ * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부
35
+ * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로
36
+ * V2 화면의 패널을 그대로 건다.
37
+ */
38
+ this.row = { version: 0 };
39
+ this.typeError = '';
40
+ /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
41
+ this.fitError = '';
42
+ /**
43
+ * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
44
+ * its tab stays in sight while the history is open — as on the V2 page.
45
+ */
46
+ this.tab = 'verdict';
47
+ this.wasDirty = false;
16
48
  this.announce = () => {
17
49
  const view = this.session.read();
18
50
  if (!this.active || view.loading || view.saving || view.error || !view.context)
@@ -29,8 +61,73 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
29
61
  };
30
62
  this.changed = () => {
31
63
  this.revision++;
32
- if (!this.session.read().saving)
64
+ /* Any edit after a failed fit makes the message stale — the author has done something about it. */
65
+ this.fitError = '';
66
+ const view = this.session.read();
67
+ if (!view.saving)
33
68
  this.announce();
69
+ /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */
70
+ if (this.wasDirty && !view.dirty && !view.saving && view.id)
71
+ this.readRow(view.id);
72
+ this.wasDirty = !!view.dirty;
73
+ };
74
+ /**
75
+ * 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
76
+ *
77
+ * 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
78
+ * 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
79
+ * 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
80
+ */
81
+ this.renameType = async (event) => {
82
+ const input = event.target;
83
+ const id = this.session.read().id;
84
+ const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase();
85
+ input.value = wanted;
86
+ if (!id || !wanted || wanted === this.row.type)
87
+ return;
88
+ try {
89
+ const result = await updateFigure(id, { type: wanted });
90
+ this.typeError = '';
91
+ this.row = { ...this.row, type: result.figure.type };
92
+ }
93
+ catch (error) {
94
+ this.typeError = error instanceof Error ? error.message : String(error);
95
+ input.value = this.row.type ?? '';
96
+ }
97
+ };
98
+ /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
99
+ this.fitToParts = () => {
100
+ try {
101
+ this.session.command(occupancyFromParts(this.session.source()));
102
+ this.fitError = '';
103
+ this.changed();
104
+ }
105
+ catch (error) {
106
+ /*
107
+ This used to set the page error, which replaces the whole editor with one raw line — for a figure with no
108
+ parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).
109
+ */
110
+ this.fitError =
111
+ error?.code === 'OCCUPANCY_NO_PARTS'
112
+ ? i18next.t('figure.text.fit-needs-parts')
113
+ : error instanceof Error
114
+ ? error.message
115
+ : String(error);
116
+ }
117
+ };
118
+ this.released = (event) => {
119
+ const figure = event.detail?.figure;
120
+ if (figure?.id && figure.id === this.session.read().id)
121
+ this.row = { state: figure.state, version: figure.version ?? 0 };
122
+ };
123
+ /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */
124
+ this.reverted = async () => {
125
+ const id = this.session.read().id;
126
+ if (!id)
127
+ return;
128
+ await this.session.open(id, { discard: true });
129
+ this.changed();
130
+ this.readRow(id);
34
131
  };
35
132
  /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */
36
133
  this.applyAI = (event) => {
@@ -51,19 +148,131 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
51
148
  static { this.styles = css `
52
149
  :host {
53
150
  display: flex;
54
- flex-direction: column;
151
+ flex-direction: row;
55
152
  height: 100%;
56
153
  min-height: 0;
154
+ /*
155
+ The page must fit the width it is given. Without this the shell sizes it to its content — the editor's
156
+ 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The
157
+ V2 page does the same.
158
+ */
159
+ overflow: hidden;
57
160
  }
58
161
  figure-v3-editor {
59
162
  flex: 1;
163
+ min-width: 0;
164
+ min-height: 0;
165
+ }
166
+ aside {
167
+ width: 320px;
168
+ flex: none;
60
169
  min-height: 0;
170
+ overflow: auto;
171
+ display: flex;
172
+ flex-direction: column;
173
+ border-left: 1px solid var(--md-sys-color-outline-variant);
174
+ }
175
+ label[type-field] {
176
+ display: grid;
177
+ gap: 4px;
178
+ padding: 12px 12px 8px;
179
+ font-size: 12px;
180
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
181
+ }
182
+ label[type-field] input,
183
+ label[type-field] code {
184
+ font: inherit;
185
+ font-family: monospace;
186
+ padding: 6px 8px;
187
+ border: 1px solid var(--md-sys-color-outline-variant);
188
+ border-radius: 4px;
189
+ background: var(--md-sys-color-surface);
190
+ color: var(--md-sys-color-on-surface);
191
+ }
192
+ label[type-field] small {
193
+ color: var(--md-sys-color-on-surface-variant);
194
+ }
195
+ p[fit-error] {
196
+ margin: 0;
197
+ padding: 8px 12px;
198
+ font-size: 12px;
199
+ color: var(--md-sys-color-error);
200
+ }
201
+ label[type-field] small[role='alert'] {
202
+ color: var(--md-sys-color-error);
203
+ }
204
+ /* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */
205
+ div[tabs] {
206
+ display: flex;
207
+ flex: none;
208
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
209
+ }
210
+ button[tab] {
211
+ flex: 1;
212
+ position: relative;
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ padding: 7px 6px;
217
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
218
+ background: none;
219
+ border: none;
220
+ border-bottom: 2px solid transparent;
221
+ cursor: pointer;
222
+ }
223
+ button[tab]:hover:not([on]) {
224
+ background-color: var(--md-sys-color-surface-container);
225
+ }
226
+ button[tab][on] {
227
+ color: var(--md-sys-color-primary);
228
+ border-bottom-color: var(--md-sys-color-primary);
229
+ }
230
+ button[tab] md-icon {
231
+ font-size: 20px;
232
+ width: 20px;
233
+ height: 20px;
234
+ }
235
+ span[badge] {
236
+ position: absolute;
237
+ top: 5px;
238
+ right: 2px;
239
+ padding: 0 5px;
240
+ font-size: 0.62rem;
241
+ font-weight: 700;
242
+ line-height: 1.5;
243
+ color: var(--md-sys-color-on-primary-container);
244
+ background-color: var(--md-sys-color-primary-container);
245
+ border-radius: 999em;
246
+ pointer-events: none;
247
+ }
248
+ /* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */
249
+ [off] {
250
+ display: none;
61
251
  }
62
252
  .error {
63
253
  padding: 16px;
64
254
  color: #922716;
65
255
  }
66
256
  `; }
257
+ draftSource() {
258
+ try {
259
+ this.draft = this.session.source();
260
+ }
261
+ catch {
262
+ /* loading or saving — keep the last source */
263
+ }
264
+ return this.draft;
265
+ }
266
+ async readRow(id) {
267
+ try {
268
+ const figure = await fetchFigure(id);
269
+ if (this.session.read().id === id)
270
+ this.row = { state: figure.state, version: figure.version ?? 0, type: figure.type };
271
+ }
272
+ catch {
273
+ /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */
274
+ }
275
+ }
67
276
  connectedCallback() {
68
277
  super.connectedCallback();
69
278
  window.addEventListener('figure-ai-context-request', this.announce);
@@ -98,12 +307,18 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
98
307
  const discard = this.discardApproved;
99
308
  this.discardApproved = false;
100
309
  if (id) {
310
+ this.draft = undefined;
101
311
  const opening = this.session.open(id, { discard });
102
312
  this.changed();
103
313
  await opening;
314
+ this.readRow(id);
104
315
  }
105
- else
316
+ else {
106
317
  this.session.startNew({ discard });
318
+ this.draft = undefined;
319
+ this.row = { version: 0 };
320
+ this.typeError = '';
321
+ }
107
322
  this.routeId = id;
108
323
  this.error = '';
109
324
  this.changed();
@@ -116,13 +331,60 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
116
331
  return { title: this.session.read().name ?? 'Figure', actions: [] };
117
332
  }
118
333
  render() {
119
- return this.error
120
- ? html `<p role="alert" class="error">${this.error}</p>`
121
- : html `<figure-v3-editor
122
- .session=${this.session}
123
- .revision=${this.revision}
124
- @v3-changed=${this.changed}
125
- ></figure-v3-editor>`;
334
+ if (this.error)
335
+ return html `<p role="alert" class="error">${this.error}</p>`;
336
+ const view = this.session.read();
337
+ return html `<figure-v3-editor
338
+ .session=${this.session}
339
+ .revision=${this.revision}
340
+ @v3-changed=${this.changed}
341
+ ></figure-v3-editor>
342
+ <aside>
343
+ <label type-field>
344
+ <span>${i18next.t('figure.label.figure-type')}</span>
345
+ ${this.row.version > 0
346
+ ? html `<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`
347
+ : html `<input
348
+ .value=${this.row.type ?? ''}
349
+ placeholder=${i18next.t('figure.text.figure-type-placeholder')}
350
+ ?disabled=${!view.id}
351
+ @change=${this.renameType}
352
+ />`}
353
+ ${this.typeError ? html `<small role="alert">${this.typeError}</small>` : ''}
354
+ </label>
355
+ ${this.fitError ? html `<p fit-error role="alert">${this.fitError}</p>` : ''}
356
+ <div tabs>
357
+ ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
358
+ ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
359
+ </div>
360
+ <figure-v3-verdict
361
+ ?off=${this.tab !== 'verdict'}
362
+ .source=${this.draftSource()}
363
+ .figureId=${view.id}
364
+ .figureType=${this.row.type}
365
+ @capacity-changed=${(event) => (this.instances = event.detail.instances)}
366
+ @fit-base-to-parts=${this.fitToParts}
367
+ ></figure-v3-verdict>
368
+ <figure-history-panel
369
+ ?off=${this.tab !== 'history'}
370
+ format="v3"
371
+ .figureId=${view.id}
372
+ .figureState=${this.row.state}
373
+ .figureVersion=${this.row.version}
374
+ .figureType=${this.row.type}
375
+ .dirty=${!!view.dirty}
376
+ @fit-base-to-parts=${this.fitToParts}
377
+ @figure-released=${this.released}
378
+ @figure-reverted=${this.reverted}
379
+ ></figure-history-panel>
380
+ </aside>`;
381
+ }
382
+ renderTab(tab, icon, label, badge) {
383
+ return html `
384
+ <button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
385
+ <md-icon>${icon}</md-icon>${badge == null ? '' : html `<span badge>${badge.toLocaleString()}</span>`}
386
+ </button>
387
+ `;
126
388
  }
127
389
  };
128
390
  __decorate([
@@ -133,8 +395,28 @@ __decorate([
133
395
  state(),
134
396
  __metadata("design:type", Object)
135
397
  ], FigureV3ModellerPage.prototype, "error", void 0);
398
+ __decorate([
399
+ state(),
400
+ __metadata("design:type", Object)
401
+ ], FigureV3ModellerPage.prototype, "row", void 0);
402
+ __decorate([
403
+ state(),
404
+ __metadata("design:type", Object)
405
+ ], FigureV3ModellerPage.prototype, "typeError", void 0);
406
+ __decorate([
407
+ state(),
408
+ __metadata("design:type", Object)
409
+ ], FigureV3ModellerPage.prototype, "fitError", void 0);
410
+ __decorate([
411
+ state(),
412
+ __metadata("design:type", String)
413
+ ], FigureV3ModellerPage.prototype, "tab", void 0);
414
+ __decorate([
415
+ state(),
416
+ __metadata("design:type", Number)
417
+ ], FigureV3ModellerPage.prototype, "instances", void 0);
136
418
  FigureV3ModellerPage = __decorate([
137
- customElement('figure-modeller-page')
419
+ customElement('figure-v3-modeller-page')
138
420
  ], FigureV3ModellerPage);
139
421
  export { FigureV3ModellerPage };
140
422
  //# sourceMappingURL=figure-v3-modeller-page.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport '../modeller/v3-editor.js'\n\n/** The V3 product page has exactly one authoritative workspace for UI and AI input transactions. */\n@customElement('figure-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n }\n figure-v3-editor {\n flex: 1;\n min-height: 0;\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n if (!this.session.read().saving) this.announce()\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n } else this.session.startNew({ discard })\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n return this.error\n ? html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n : html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>`\n }\n}\n"]}
1
+ {"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport { i18next } from '@operato/i18n'\nimport { fetchFigure, updateFigure } from '../graphql/index.js'\nimport { occupancyFromParts } from '../modeller/v3-occupancy.js'\nimport '../modeller/v3-editor.js'\nimport '@material/web/icon/icon.js'\nimport '../modeller/figure-history-panel.js'\nimport '../modeller/figure-v3-verdict.js'\n\n/**\n * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.\n *\n * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches\n * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a\n * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route\n * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.\n * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.\n */\n@customElement('figure-v3-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n /*\n The page must fit the width it is given. Without this the shell sizes it to its content — the editor's\n 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The\n V2 page does the same.\n */\n overflow: hidden;\n }\n figure-v3-editor {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n aside {\n width: 320px;\n flex: none;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] {\n display: grid;\n gap: 4px;\n padding: 12px 12px 8px;\n font-size: 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] input,\n label[type-field] code {\n font: inherit;\n font-family: monospace;\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n label[type-field] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n p[fit-error] {\n margin: 0;\n padding: 8px 12px;\n font-size: 12px;\n color: var(--md-sys-color-error);\n }\n label[type-field] small[role='alert'] {\n color: var(--md-sys-color-error);\n }\n /* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n /* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */\n [off] {\n display: none;\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n /**\n * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.\n *\n * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서\n * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부\n * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로\n * V2 화면의 패널을 그대로 건다.\n */\n @state() private row: { state?: 'draft' | 'released'; version: number; type?: string } = { version: 0 }\n @state() private typeError = ''\n /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */\n @state() private fitError = ''\n /**\n * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on\n * its tab stays in sight while the history is open — as on the V2 page.\n */\n @state() private tab: 'verdict' | 'history' = 'verdict'\n @state() private instances?: number\n /**\n * The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is\n * kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.\n */\n private draft?: string\n private draftSource(): string | undefined {\n try {\n this.draft = this.session.source()\n } catch {\n /* loading or saving — keep the last source */\n }\n return this.draft\n }\n private wasDirty = false\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n /* Any edit after a failed fit makes the message stale — the author has done something about it. */\n this.fitError = ''\n const view = this.session.read()\n if (!view.saving) this.announce()\n /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */\n if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)\n this.wasDirty = !!view.dirty\n }\n private async readRow(id: string) {\n try {\n const figure = await fetchFigure(id)\n if (this.session.read().id === id)\n this.row = { state: figure.state as 'draft' | 'released', version: figure.version ?? 0, type: figure.type }\n } catch {\n /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */\n }\n }\n /**\n * 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).\n *\n * 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.\n * 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.\n * 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.\n */\n private renameType = async (event: Event) => {\n const input = event.target as HTMLInputElement\n const id = this.session.read().id\n const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n input.value = wanted\n if (!id || !wanted || wanted === this.row.type) return\n try {\n const result = await updateFigure(id, { type: wanted })\n this.typeError = ''\n this.row = { ...this.row, type: result.figure.type }\n } catch (error) {\n this.typeError = error instanceof Error ? error.message : String(error)\n input.value = this.row.type ?? ''\n }\n }\n /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */\n private fitToParts = () => {\n try {\n this.session.command(occupancyFromParts(this.session.source()))\n this.fitError = ''\n this.changed()\n } catch (error) {\n /*\n This used to set the page error, which replaces the whole editor with one raw line — for a figure with no\n parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).\n */\n this.fitError =\n (error as { code?: string })?.code === 'OCCUPANCY_NO_PARTS'\n ? i18next.t('figure.text.fit-needs-parts')\n : error instanceof Error\n ? error.message\n : String(error)\n }\n }\n private released = (event: Event) => {\n const figure = (event as CustomEvent).detail?.figure\n if (figure?.id && figure.id === this.session.read().id) this.row = { state: figure.state, version: figure.version ?? 0 }\n }\n /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */\n private reverted = async () => {\n const id = this.session.read().id\n if (!id) return\n await this.session.open(id, { discard: true })\n this.changed()\n this.readRow(id)\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n this.draft = undefined\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n this.readRow(id)\n } else {\n this.session.startNew({ discard })\n this.draft = undefined\n this.row = { version: 0 }\n this.typeError = ''\n }\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n if (this.error) return html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n const view = this.session.read()\n return html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>\n <aside>\n <label type-field>\n <span>${i18next.t('figure.label.figure-type')}</span>\n ${this.row.version > 0\n ? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`\n : html`<input\n .value=${this.row.type ?? ''}\n placeholder=${i18next.t('figure.text.figure-type-placeholder')}\n ?disabled=${!view.id}\n @change=${this.renameType}\n />`}\n ${this.typeError ? html`<small role=\"alert\">${this.typeError}</small>` : ''}\n </label>\n ${this.fitError ? html`<p fit-error role=\"alert\">${this.fitError}</p>` : ''}\n <div tabs>\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n <figure-v3-verdict\n ?off=${this.tab !== 'verdict'}\n .source=${this.draftSource()}\n .figureId=${view.id}\n .figureType=${this.row.type}\n @capacity-changed=${(event: CustomEvent) => (this.instances = event.detail.instances)}\n @fit-base-to-parts=${this.fitToParts}\n ></figure-v3-verdict>\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n format=\"v3\"\n .figureId=${view.id}\n .figureState=${this.row.state}\n .figureVersion=${this.row.version}\n .figureType=${this.row.type}\n .dirty=${!!view.dirty}\n @fit-base-to-parts=${this.fitToParts}\n @figure-released=${this.released}\n @figure-reverted=${this.reverted}\n ></figure-history-panel>\n </aside>`\n }\n private renderTab(tab: 'verdict' | 'history', icon: string, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
@@ -1 +1 @@
1
- export default function route(page: string): "figure-modeller" | "figure-list" | undefined;
1
+ export default function route(page: string): "figure-list" | "figure-modeller" | "figure-v3-modeller" | undefined;
@@ -6,6 +6,13 @@ export default function route(page) {
6
6
  case 'figure-modeller':
7
7
  import('./pages/figure-modeller-page');
8
8
  return page;
9
+ /*
10
+ The V3 modeller, under its own name until the catalogue's assets are V3 (ADR-0087, step 5). Both pages
11
+ define a custom element, and an element name may be registered once, so they cannot share one.
12
+ */
13
+ case 'figure-v3-modeller':
14
+ import('./pages/figure-v3-modeller-page');
15
+ return page;
9
16
  }
10
17
  }
11
18
  //# sourceMappingURL=route.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,MAAM,CAAC,0BAA0B,CAAC,CAAA;YAClC,OAAO,IAAI,CAAA;QAEb,KAAK,iBAAiB;YACpB,MAAM,CAAC,8BAA8B,CAAC,CAAA;YACtC,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'figure-list':\n import('./pages/figure-list-page')\n return page\n\n case 'figure-modeller':\n import('./pages/figure-modeller-page')\n return page\n }\n}\n"]}
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,MAAM,CAAC,0BAA0B,CAAC,CAAA;YAClC,OAAO,IAAI,CAAA;QAEb,KAAK,iBAAiB;YACpB,MAAM,CAAC,8BAA8B,CAAC,CAAA;YACtC,OAAO,IAAI,CAAA;QAEb;;;UAGE;QACF,KAAK,oBAAoB;YACvB,MAAM,CAAC,iCAAiC,CAAC,CAAA;YACzC,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'figure-list':\n import('./pages/figure-list-page')\n return page\n\n case 'figure-modeller':\n import('./pages/figure-modeller-page')\n return page\n\n /*\n The V3 modeller, under its own name until the catalogue's assets are V3 (ADR-0087, step 5). Both pages\n define a custom element, and an element name may be registered once, so they cannot share one.\n */\n case 'figure-v3-modeller':\n import('./pages/figure-v3-modeller-page')\n return page\n }\n}\n"]}