@things-factory/figure-ui 10.1.224 → 10.1.225

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 (41) hide show
  1. package/client/modeller/editor-styles.ts +3 -2
  2. package/client/modeller/editor.ts +3 -1
  3. package/client/modeller/figure-capability-panel.ts +6 -0
  4. package/client/modeller/figure-clips.ts +1 -1
  5. package/client/modeller/figure-data-panel.ts +17 -14
  6. package/client/modeller/figure-preview.ts +46 -6
  7. package/client/modeller/product-session.ts +3 -2
  8. package/client/pages/figure-modeller-page.ts +2 -2
  9. package/dist-client/modeller/editor-styles.js +3 -2
  10. package/dist-client/modeller/editor-styles.js.map +1 -1
  11. package/dist-client/modeller/editor.js +5 -3
  12. package/dist-client/modeller/editor.js.map +1 -1
  13. package/dist-client/modeller/figure-capability-panel.js +6 -0
  14. package/dist-client/modeller/figure-capability-panel.js.map +1 -1
  15. package/dist-client/modeller/figure-clips.js +1 -1
  16. package/dist-client/modeller/figure-clips.js.map +1 -1
  17. package/dist-client/modeller/figure-data-panel.js +18 -14
  18. package/dist-client/modeller/figure-data-panel.js.map +1 -1
  19. package/dist-client/modeller/figure-preview.js +46 -6
  20. package/dist-client/modeller/figure-preview.js.map +1 -1
  21. package/dist-client/modeller/product-session.d.ts +4 -2
  22. package/dist-client/modeller/product-session.js +3 -2
  23. package/dist-client/modeller/product-session.js.map +1 -1
  24. package/dist-client/pages/figure-modeller-page.js +2 -2
  25. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  26. package/dist-client/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +3 -3
  28. package/test/ai-proposal.test.ts +2 -2
  29. package/test/browser/product-page.check.mjs +25 -0
  30. package/test/browser/side-panel-sweep.check.mjs +182 -0
  31. package/test/figure-assistant-surface.test.ts +1 -1
  32. package/test/first-save.test.ts +2 -2
  33. package/test/product-session.test.ts +5 -5
  34. package/test/proposal-record.test.ts +3 -3
  35. package/test/revert-keeping.test.ts +1 -1
  36. package/translations/en.json +1 -0
  37. package/translations/es.json +1 -0
  38. package/translations/ja.json +1 -0
  39. package/translations/ko.json +1 -0
  40. package/translations/ms.json +1 -0
  41. package/translations/zh.json +1 -0
@@ -339,11 +339,12 @@ export const EditorStyles = css`
339
339
  min-height: 0;
340
340
  overflow: auto;
341
341
  }
342
- /* Which part the panel speaks of. */
342
+ /* Which part the panel speaks of. Above z-index 1: a chip's label (md-filter-chip) sits at 1 and, coming later, was
343
+ drawn over it when the panel scrolled (sweep 2026-10-27 U2). */
343
344
  div[subject] {
344
345
  position: sticky;
345
346
  top: 0;
346
- z-index: 1;
347
+ z-index: 2;
347
348
  display: grid;
348
349
  gap: 4px;
349
350
  padding: 10px 12px;
@@ -1943,7 +1943,9 @@ export class FigureEditor extends LitElement {
1943
1943
  @part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
1944
1944
  ></figure-material-panel>
1945
1945
  ${this.renderMore(part, view.palette ?? {}, unavailable)}`
1946
- : view.geometry.length === 0
1946
+ : view.loading || !view.context
1947
+ ? nothing
1948
+ : view.geometry.length === 0
1947
1949
  ? html`${this.renderSubject()}<section empty-figure>
1948
1950
  <p>${i18next.t('figure.text.empty-figure-next')}</p>
1949
1951
  <details>
@@ -50,6 +50,12 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
50
50
  border-radius: 8px;
51
51
  cursor: pointer;
52
52
  }
53
+ /* The box keeps its size beside a long note: squeezed by the flex row it read as a dot (sweep 2026-10-27 U1). */
54
+ label[rung] > input,
55
+ label[item] > input {
56
+ flex: none;
57
+ margin: 2px 0 0;
58
+ }
53
59
  label[rung][on],
54
60
  label[item][on] {
55
61
  border-color: var(--md-sys-color-primary);
@@ -118,7 +118,7 @@ export class FigureClips extends localize(i18next)(LitElement) {
118
118
  <h3>${i18next.t('figure.label.clips')}${this.help.button('figure.text.clips-explained')}</h3>
119
119
  ${this.help.hint('figure.text.clips-explained')}
120
120
  ${clips.map(clip => this.renderClip(clip))}
121
- ${empty ? html`<p hint>${i18next.t('figure.text.no-controls')}</p>` : nothing}
121
+ ${empty ? html`<p hint>${i18next.t('figure.text.no-clips')}</p>` : nothing}
122
122
  </section>
123
123
  ${empty ? nothing : this.renderAdd(asset, clips)} ${this.renderGroups(asset)}
124
124
  `
@@ -346,7 +346,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
346
346
  this.edit({ kind: 'set-data-schema', schema: schemaWithoutField(schema, row.path) })
347
347
  }}
348
348
  >
349
- <md-icon>close</md-icon>
349
+ <md-icon>delete</md-icon>
350
350
  </button>
351
351
  </div>
352
352
  <div form>
@@ -412,22 +412,25 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
412
412
  private renderAddField(schema: readonly FigureDataField[], under: string) {
413
413
  const a = this.adding
414
414
  const named = a.name.trim()
415
+ const add = () => {
416
+ if (this.locked || !named) return
417
+ this.edit({ kind: 'set-data-schema', schema: schemaWithField(schema, under, { name: named, ...nodeOfKind(a.kind) } as FigureDataField) })
418
+ this.adding = { ...a, name: '' }
419
+ }
415
420
  return html`<div form>
416
421
  <span>${i18next.t('figure.label.data-name')}</span>
417
- <input .value=${a.name} @input=${(e: Event) => (this.adding = { ...a, name: (e.target as HTMLInputElement).value })} />
422
+ <input
423
+ aria-label=${i18next.t('figure.label.data-name')}
424
+ .value=${a.name}
425
+ @input=${(e: Event) => (this.adding = { ...a, name: (e.target as HTMLInputElement).value })}
426
+ @keydown=${(e: KeyboardEvent) => e.key === 'Enter' && !e.isComposing && add()}
427
+ />
418
428
  <span>${i18next.t('figure.label.data-kind')}</span>
419
429
  <select @change=${(e: Event) => (this.adding = { ...a, kind: (e.target as HTMLSelectElement).value as FigureDataNode['kind'] })}>
420
430
  ${KINDS.map(k => html`<option value=${k} ?selected=${k === a.kind}>${i18next.t('figure.label.data-kind-' + k)}</option>`)}
421
431
  </select>
422
432
  </div>
423
- <button
424
- act
425
- ?disabled=${this.locked || !named}
426
- @click=${() => {
427
- this.edit({ kind: 'set-data-schema', schema: schemaWithField(schema, under, { name: named, ...nodeOfKind(a.kind) } as FigureDataField) })
428
- this.adding = { ...a, name: '' }
429
- }}
430
- >
433
+ <button act ?disabled=${this.locked || !named} @click=${add}>
431
434
  <md-icon>add</md-icon>${i18next.t(under ? 'figure.button.add-data-child' : 'figure.button.add-data-field', { parent: under })}
432
435
  </button>`
433
436
  }
@@ -505,7 +508,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
505
508
  <strong>${b.label ?? b.accessor}</strong> → ${what} <small>· ${i18next.t('figure.label.data-method-' + methodOf(b))}</small>
506
509
  </button>
507
510
  <button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-binding')} @click=${() => this.edit({ kind: 'remove-data-binding', id: b.id })}>
508
- <md-icon>close</md-icon>
511
+ <md-icon>delete</md-icon>
509
512
  </button>
510
513
  </div>
511
514
  ${resting.filter(r => r.id === b.id).map(r => this.renderResting(r))}
@@ -700,7 +703,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
700
703
  return html`<div looks style="grid-template-columns: ${gridOf(keyCells, columns.length)}">
701
704
  <span style="grid-column: span ${keyCells}">${i18next.t('figure.label.data-otherwise')}</span>
702
705
  ${this.lookCells(asset, columns, look, l => set(tableWithLook(b, 'default', l)))}
703
- ${needed ? html`<span></span>` : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => set({ ...b, table: rest })}><md-icon>close</md-icon></button>`}
706
+ ${needed ? html`<span></span>` : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => set({ ...b, table: rest })}><md-icon>delete</md-icon></button>`}
704
707
  </div>`
705
708
  }
706
709
 
@@ -736,7 +739,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
736
739
  ${this.lookCells(asset, columns, look, l => set(tableWithLook(b, k, l)))}
737
740
  ${boolean || rows.length < 2 && domain.length > 0
738
741
  ? html`<span></span>`
739
- : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => rekeyed(rows.filter((_, j) => j !== i))}><md-icon>close</md-icon></button>`}`
742
+ : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => rekeyed(rows.filter((_, j) => j !== i))}><md-icon>delete</md-icon></button>`}`
740
743
  )}
741
744
  </div>
742
745
  ${this.renderOtherwise(asset, b, columns, domain.length > 0 && !covered, set, 1)}
@@ -779,7 +782,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
779
782
  return html`<input type="number" step="any" .value=${e.from} ?disabled=${this.locked} @change=${(ev: Event) => rekey(i, 'from', (ev.target as HTMLInputElement).value)} />
780
783
  <input type="number" step="any" .value=${e.to} ?disabled=${this.locked} @change=${(ev: Event) => rekey(i, 'to', (ev.target as HTMLInputElement).value)} />
781
784
  ${this.lookCells(asset, columns, look, l => set(tableWithLook(b, k, l)))}
782
- <button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-range')} @click=${() => set(tableWithKeys(b, rows.filter((_, j) => j !== i)))}><md-icon>close</md-icon></button>`
785
+ <button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-range')} @click=${() => set(tableWithKeys(b, rows.filter((_, j) => j !== i)))}><md-icon>delete</md-icon></button>`
783
786
  })}
784
787
  </div>
785
788
  ${this.renderOtherwise(asset, b, columns, false, set, 2)}
@@ -83,7 +83,26 @@ export class FigurePreview extends localize(i18next)(LitElement) {
83
83
  div[foot] > * { pointer-events: auto; }
84
84
  div[setup] { max-width: min(320px, 100%); padding: 12px; border-radius: 8px; background: rgba(28,28,30,.9); color: #fff; display: grid; gap: 8px; }
85
85
  div[setup] p { margin: 0; line-height: 1.5; }
86
- div[setup] select, div[setup] button { font: inherit; padding: 8px; min-width: 0; }
86
+ /* The help's choice and button in the look of the controls beside it, not the browser's grey (sweep 2026-10-27 U7). */
87
+ div[setup] select,
88
+ div[setup] button {
89
+ min-width: 0;
90
+ padding: 4px 8px;
91
+ font: inherit;
92
+ color: rgba(255, 255, 255, 0.92);
93
+ background-color: rgba(255, 255, 255, 0.14);
94
+ border: 1px solid rgba(255, 255, 255, 0.22);
95
+ border-radius: 5px;
96
+ }
97
+ div[setup] button {
98
+ cursor: pointer;
99
+ }
100
+ div[setup] button:hover {
101
+ background-color: rgba(255, 255, 255, 0.24);
102
+ }
103
+ div[setup] option {
104
+ color: initial;
105
+ }
87
106
  /* Which is which: side by side means nothing without a legend. */
88
107
  div[legend] {
89
108
  position: absolute;
@@ -153,22 +172,29 @@ export class FigurePreview extends localize(i18next)(LitElement) {
153
172
  margin-left: 4px;
154
173
  color: #ffcc80;
155
174
  }
175
+ /*
176
+ One grid for every row, each row its subgrid: the rows' names in one column, so a value's field starts where the one
177
+ above it does whatever the names' lengths (a row of its own grid started 28px off, sweep 2026-10-27 U7).
178
+ */
156
179
  div[clips] {
157
180
  margin-left: auto;
158
- display: flex;
159
- flex-direction: column;
160
- gap: 6px;
181
+ display: grid;
182
+ grid-template-columns: auto 20px 96px 34px;
183
+ gap: 6px 8px;
184
+ align-items: center;
161
185
  padding: 8px 10px;
162
186
  color: rgba(255, 255, 255, 0.92);
163
187
  background-color: rgba(28, 28, 30, 0.72);
164
188
  border-radius: 8px;
165
189
  font-size: var(--ui-t-micro, 11px);
166
190
  }
191
+ div[clips] > * {
192
+ grid-column: 1 / -1;
193
+ }
167
194
  div[clips] label {
168
195
  display: grid;
169
- grid-template-columns: auto 20px 96px 34px;
196
+ grid-template-columns: subgrid;
170
197
  align-items: center;
171
- gap: 8px;
172
198
  }
173
199
  /* A motion group's clip: as wide as the dial, slider and value together, so its name reads. */
174
200
  div[clips] select {
@@ -218,6 +244,20 @@ export class FigurePreview extends localize(i18next)(LitElement) {
218
244
  width: 96px;
219
245
  accent-color: var(--md-sys-color-primary);
220
246
  }
247
+ /* A value typed, not slid: in the look of the group's clip choice, starting where the sliders start. */
248
+ div[clips] input:not([type='range']) {
249
+ box-sizing: border-box;
250
+ min-width: 0;
251
+ font: inherit;
252
+ color: inherit;
253
+ background-color: rgba(255, 255, 255, 0.14);
254
+ border: 1px solid rgba(255, 255, 255, 0.22);
255
+ border-radius: 5px;
256
+ }
257
+ div[clips] input:not([type='range']):not([type='number']) {
258
+ grid-column: 3 / -1;
259
+ width: auto;
260
+ }
221
261
  div[clips] output {
222
262
  font-variant-numeric: tabular-nums;
223
263
  text-align: right;
@@ -162,12 +162,13 @@ export function createFigureProductSession(store: FigureProductStore) {
162
162
  * -- the server writes over it only while it still holds this. Nothing for a figure not saved yet.
163
163
  */
164
164
  confirmedSource: () => (id ? baseline : undefined),
165
- startNew({ discard = false } = {}) {
165
+ /** A new figure, named in the screen's language by the page: the session has no words of its own. */
166
+ startNew({ discard = false, name }: { discard?: boolean; name: string }) {
166
167
  canLeave(discard)
167
168
  ++epoch
168
169
  clear()
169
170
  loading = false
170
- workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')))
171
+ workspace.open(serializeFigureAsset(createFigureAsset(name)))
171
172
  creationId = crypto.randomUUID()
172
173
  return read()
173
174
  },
@@ -450,7 +450,7 @@ export class FigureModellerPage extends PageView {
450
450
  await opening
451
451
  this.readRow(id)
452
452
  } else {
453
- this.session.startNew({ discard })
453
+ this.session.startNew({ discard, name: i18next.t('figure.text.new-figure-name') })
454
454
  this.row = { version: 0 }
455
455
  this.typeError = ''
456
456
  this.takeNewFigureProposal()
@@ -496,7 +496,7 @@ export class FigureModellerPage extends PageView {
496
496
  get context() {
497
497
  const view = this.session.read()
498
498
  return {
499
- title: view.name ?? 'Figure',
499
+ title: view.name || i18next.t('figure.label.figure'),
500
500
  /*
501
501
  A figure that has an id was opened from the catalog (or saved there), so the rail marks the catalog. Without this
502
502
  the rail matches the page name and marks "new figure" while an existing one is being edited.
@@ -338,11 +338,12 @@ export const EditorStyles = css `
338
338
  min-height: 0;
339
339
  overflow: auto;
340
340
  }
341
- /* Which part the panel speaks of. */
341
+ /* Which part the panel speaks of. Above z-index 1: a chip's label (md-filter-chip) sits at 1 and, coming later, was
342
+ drawn over it when the panel scrolled (sweep 2026-10-27 U2). */
342
343
  div[subject] {
343
344
  position: sticky;
344
345
  top: 0;
345
- z-index: 1;
346
+ z-index: 2;
346
347
  display: grid;
347
348
  gap: 4px;
348
349
  padding: 10px 12px;
@@ -1 +1 @@
1
- {"version":3,"file":"editor-styles.js","sourceRoot":"","sources":["../../client/modeller/editor-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,gHAAgH;AAChH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4a9B,CAAA","sourcesContent":["import { css } from 'lit'\n\n/** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */\nexport const EditorStyles = css`\n :host {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n /* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */\n container-type: inline-size;\n /* The app's one face (its typography theme's --theme-font) at the body size: a bare sans-serif here put a second\n face under everything the editor draws, beside the parts list and the shell in the app's own (2026-10-16). */\n font-family: var(--theme-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-t-body, 13px);\n color: var(--md-sys-color-on-surface);\n }\n /* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and\n fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */\n ox-canvas-bar button[mode] {\n height: 28px;\n border: 1px solid transparent;\n padding: 0 10px;\n border-radius: 999em;\n border-color: transparent;\n background: none;\n }\n ox-canvas-bar button[mode][aria-pressed='true'] {\n border-color: var(--md-sys-color-primary);\n }\n /* The gizmo's tools, drawn as the bar's own icon buttons (undo, redo, fit). */\n ox-canvas-bar .gizmo-tools {\n display: inline-flex;\n gap: 2px;\n padding-left: 4px;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n ox-canvas-bar button[tool] {\n display: inline-grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--md-sys-shape-corner-small, 8px);\n background: transparent;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n ox-canvas-bar button[tool]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n ox-canvas-bar button[tool][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n ox-canvas-bar button[tool]:disabled {\n opacity: 0.38;\n cursor: default;\n }\n /*\n Slotted into the bar from here, the tools do not get the bar's own icon size (20px, set inside its shadow): they\n showed at md-icon's 24px, heavier than undo, redo and fit beside them. The same size, and the bar's focus ring.\n */\n ox-canvas-bar button[tool] md-icon {\n --md-icon-size: 20px;\n }\n ox-canvas-bar button[tool]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n figure-preview {\n min-width: 0;\n min-height: 0;\n }\n /* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */\n div[middle][hidden] {\n display: none;\n }\n /* The preview, once made, stays standing out of sight too: its 3D scene is not made again each time it is asked for. */\n figure-preview[hidden] {\n display: none;\n }\n ox-canvas-bar .status {\n padding: 0 8px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n The bar's two commitments, one shape (user 2026-10-18: 「발행버튼도 저장버튼도 라운드 버튼으로 … 버튼 안에 적절한 아이콘」):\n round, an icon inside, so neither is taken for the other. Save, done many times a day, is the filled one; release,\n a promise to whoever draws the figure, is outlined.\n */\n ox-canvas-bar button[save],\n ox-canvas-bar button[publish] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 14px 0 10px;\n border-radius: 999em;\n font-weight: 600;\n }\n ox-canvas-bar button[save] md-icon,\n ox-canvas-bar button[publish] md-icon {\n --md-icon-size: 18px;\n }\n ox-canvas-bar button[save] {\n border: 1px solid transparent;\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n }\n /* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under\n 860px and one column with the middle first under 560px are the frame's, as are the dividers. */\n ox-three-pane {\n flex: 1;\n min-height: 0;\n }\n figure-parts {\n min-height: 0;\n }\n figure-canvas {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n div[middle] {\n position: relative;\n display: flex;\n min-width: 0;\n min-height: 0;\n }\n /* Which size handles change one diameter (ADR-0140 4.1): a quiet line over the canvas, out of the gizmo's way. */\n p[same-measure] {\n position: absolute;\n bottom: 12px;\n left: 50%;\n z-index: 5;\n transform: translateX(-50%);\n margin: 0;\n padding: 4px 10px;\n border-radius: 999em;\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface-variant);\n font-size: var(--ui-t-small, 11.5px);\n pointer-events: none;\n white-space: nowrap;\n }\n /* A new figure's first question, over the empty canvas. */\n div[start] {\n position: absolute;\n inset: 50% auto auto 50%;\n z-index: 10;\n color: var(--md-sys-color-on-surface);\n transform: translate(-50%, -50%);\n width: min(440px, calc(100% - 32px));\n box-sizing: border-box;\n padding: 20px;\n border-radius: 12px;\n background: var(--md-sys-color-surface, #fff);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);\n }\n div[start] button[start-button] {\n width: auto;\n margin: 0;\n font-size: var(--ui-t-body);\n height: 36px;\n padding: 0 16px;\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 999em;\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface, #fff);\n cursor: pointer;\n }\n div[start] button[start-button]:hover:not(:disabled) {\n background: var(--md-sys-color-primary-container);\n }\n div[start] button[start-button][pressed] {\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n border-color: var(--md-sys-color-primary);\n }\n div[start] button[start-button]:disabled {\n opacity: 0.5;\n cursor: default;\n }\n div[start] h2 {\n margin: 0 0 12px;\n font-size: var(--ui-t-title);\n }\n div[start-paths] {\n display: grid;\n gap: 14px;\n }\n div[start-paths] h3 {\n margin: 0 0 6px;\n font-size: var(--ui-t-body);\n }\n div[start-shapes] {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n div[start] small {\n display: block;\n margin-top: 4px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The side: tabs over one panel at a time, each using the whole height. */\n /*\n The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body\n for the bar, and the fields the editor drew itself -- the part's name -- stood a step larger than every other field.\n */\n aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: var(--ui-t-small, 11.5px);\n background: var(--md-sys-color-surface-container-lowest);\n }\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 flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1px;\n min-width: 0;\n height: 48px;\n padding: 0;\n border: none;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[tab]:hover:not([on]) {\n background: 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 button[tab] span[tab-label] {\n max-width: 100%;\n overflow: hidden;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* A range's two ends read as one: smallest – largest. */\n div[pair][range] {\n align-items: center;\n }\n div[pair][range] > span[dash] {\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */\n div[panel] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 0;\n }\n div[panel][flush] {\n padding: 0;\n }\n /* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */\n [off] {\n display: none !important;\n }\n /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */\n div[group-fields] {\n display: grid;\n gap: 6px;\n }\n /* The folded 「더 보기」: its title is the fold's handle, at the title's size. */\n section[more] > details > summary {\n padding: 0;\n }\n section[more] > details > summary > h3 {\n margin: 0;\n }\n div[more-body] {\n display: grid;\n gap: 16px;\n margin-top: 12px;\n }\n /*\n The view settings and the release history, opened from the bar over the right of the work: neither is about the\n part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16). The fastening window stands in\n the same place, over the side panel, so the canvas it previews on stays in sight (user 2026-10-19).\n */\n :host {\n position: relative;\n }\n div[side-overlay] {\n position: absolute;\n top: var(--figure-bar-height, 44px);\n right: 8px;\n z-index: 5;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100% - 16px));\n max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);\n }\n div[overlay-head] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 8px 8px 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n div[overlay-head] h3 {\n margin: 0;\n }\n div[overlay-head] button[quiet] {\n margin-left: 0;\n }\n [overlay-body] {\n min-height: 0;\n overflow: auto;\n }\n /* Which part the panel speaks of. */\n div[subject] {\n position: sticky;\n top: 0;\n z-index: 1;\n display: grid;\n gap: 4px;\n padding: 10px 12px;\n background: var(--md-sys-color-surface-container-lowest);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */\n div[subject-summary] {\n display: grid;\n gap: 2px;\n }\n div[subject-summary] b {\n font-size: var(--ui-t-body, 13px);\n font-weight: 600;\n }\n div[subject-summary] b span {\n font-weight: 400;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[subject-summary] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one\n height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도\n 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).\n */\n button {\n font: inherit;\n box-sizing: border-box;\n color: inherit;\n min-width: 0;\n cursor: pointer;\n }\n /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */\n ox-canvas-bar button[release-blocked] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 32px;\n padding: 0 14px 0 10px;\n border: none;\n /* Round as save and release beside it: one shape for the bar's end. */\n border-radius: 999em;\n color: var(--md-sys-color-on-error-container);\n background: var(--md-sys-color-error-container);\n }\n ox-canvas-bar button[release-blocked] md-icon {\n --md-icon-size: 18px;\n }\n div[release-blockers] {\n position: absolute;\n top: 46px;\n right: 8px;\n z-index: 6;\n width: min(340px, calc(100% - 16px));\n padding: 8px 10px 10px;\n box-sizing: border-box;\n background: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);\n }\n ox-canvas-bar button[publish] {\n border: 1px solid var(--md-sys-color-outline);\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface);\n }\n ox-canvas-bar button[publish][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n border-color: var(--md-sys-color-primary);\n }\n .hint {\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n`\n"]}
1
+ {"version":3,"file":"editor-styles.js","sourceRoot":"","sources":["../../client/modeller/editor-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,gHAAgH;AAChH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6a9B,CAAA","sourcesContent":["import { css } from 'lit'\n\n/** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */\nexport const EditorStyles = css`\n :host {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n /* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */\n container-type: inline-size;\n /* The app's one face (its typography theme's --theme-font) at the body size: a bare sans-serif here put a second\n face under everything the editor draws, beside the parts list and the shell in the app's own (2026-10-16). */\n font-family: var(--theme-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-t-body, 13px);\n color: var(--md-sys-color-on-surface);\n }\n /* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and\n fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */\n ox-canvas-bar button[mode] {\n height: 28px;\n border: 1px solid transparent;\n padding: 0 10px;\n border-radius: 999em;\n border-color: transparent;\n background: none;\n }\n ox-canvas-bar button[mode][aria-pressed='true'] {\n border-color: var(--md-sys-color-primary);\n }\n /* The gizmo's tools, drawn as the bar's own icon buttons (undo, redo, fit). */\n ox-canvas-bar .gizmo-tools {\n display: inline-flex;\n gap: 2px;\n padding-left: 4px;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n ox-canvas-bar button[tool] {\n display: inline-grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--md-sys-shape-corner-small, 8px);\n background: transparent;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n ox-canvas-bar button[tool]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n ox-canvas-bar button[tool][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n ox-canvas-bar button[tool]:disabled {\n opacity: 0.38;\n cursor: default;\n }\n /*\n Slotted into the bar from here, the tools do not get the bar's own icon size (20px, set inside its shadow): they\n showed at md-icon's 24px, heavier than undo, redo and fit beside them. The same size, and the bar's focus ring.\n */\n ox-canvas-bar button[tool] md-icon {\n --md-icon-size: 20px;\n }\n ox-canvas-bar button[tool]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n figure-preview {\n min-width: 0;\n min-height: 0;\n }\n /* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */\n div[middle][hidden] {\n display: none;\n }\n /* The preview, once made, stays standing out of sight too: its 3D scene is not made again each time it is asked for. */\n figure-preview[hidden] {\n display: none;\n }\n ox-canvas-bar .status {\n padding: 0 8px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n The bar's two commitments, one shape (user 2026-10-18: 「발행버튼도 저장버튼도 라운드 버튼으로 … 버튼 안에 적절한 아이콘」):\n round, an icon inside, so neither is taken for the other. Save, done many times a day, is the filled one; release,\n a promise to whoever draws the figure, is outlined.\n */\n ox-canvas-bar button[save],\n ox-canvas-bar button[publish] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 14px 0 10px;\n border-radius: 999em;\n font-weight: 600;\n }\n ox-canvas-bar button[save] md-icon,\n ox-canvas-bar button[publish] md-icon {\n --md-icon-size: 18px;\n }\n ox-canvas-bar button[save] {\n border: 1px solid transparent;\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n }\n /* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under\n 860px and one column with the middle first under 560px are the frame's, as are the dividers. */\n ox-three-pane {\n flex: 1;\n min-height: 0;\n }\n figure-parts {\n min-height: 0;\n }\n figure-canvas {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n div[middle] {\n position: relative;\n display: flex;\n min-width: 0;\n min-height: 0;\n }\n /* Which size handles change one diameter (ADR-0140 4.1): a quiet line over the canvas, out of the gizmo's way. */\n p[same-measure] {\n position: absolute;\n bottom: 12px;\n left: 50%;\n z-index: 5;\n transform: translateX(-50%);\n margin: 0;\n padding: 4px 10px;\n border-radius: 999em;\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface-variant);\n font-size: var(--ui-t-small, 11.5px);\n pointer-events: none;\n white-space: nowrap;\n }\n /* A new figure's first question, over the empty canvas. */\n div[start] {\n position: absolute;\n inset: 50% auto auto 50%;\n z-index: 10;\n color: var(--md-sys-color-on-surface);\n transform: translate(-50%, -50%);\n width: min(440px, calc(100% - 32px));\n box-sizing: border-box;\n padding: 20px;\n border-radius: 12px;\n background: var(--md-sys-color-surface, #fff);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);\n }\n div[start] button[start-button] {\n width: auto;\n margin: 0;\n font-size: var(--ui-t-body);\n height: 36px;\n padding: 0 16px;\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 999em;\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface, #fff);\n cursor: pointer;\n }\n div[start] button[start-button]:hover:not(:disabled) {\n background: var(--md-sys-color-primary-container);\n }\n div[start] button[start-button][pressed] {\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n border-color: var(--md-sys-color-primary);\n }\n div[start] button[start-button]:disabled {\n opacity: 0.5;\n cursor: default;\n }\n div[start] h2 {\n margin: 0 0 12px;\n font-size: var(--ui-t-title);\n }\n div[start-paths] {\n display: grid;\n gap: 14px;\n }\n div[start-paths] h3 {\n margin: 0 0 6px;\n font-size: var(--ui-t-body);\n }\n div[start-shapes] {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n div[start] small {\n display: block;\n margin-top: 4px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The side: tabs over one panel at a time, each using the whole height. */\n /*\n The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body\n for the bar, and the fields the editor drew itself -- the part's name -- stood a step larger than every other field.\n */\n aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: var(--ui-t-small, 11.5px);\n background: var(--md-sys-color-surface-container-lowest);\n }\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 flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1px;\n min-width: 0;\n height: 48px;\n padding: 0;\n border: none;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[tab]:hover:not([on]) {\n background: 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 button[tab] span[tab-label] {\n max-width: 100%;\n overflow: hidden;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* A range's two ends read as one: smallest – largest. */\n div[pair][range] {\n align-items: center;\n }\n div[pair][range] > span[dash] {\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */\n div[panel] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 0;\n }\n div[panel][flush] {\n padding: 0;\n }\n /* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */\n [off] {\n display: none !important;\n }\n /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */\n div[group-fields] {\n display: grid;\n gap: 6px;\n }\n /* The folded 「더 보기」: its title is the fold's handle, at the title's size. */\n section[more] > details > summary {\n padding: 0;\n }\n section[more] > details > summary > h3 {\n margin: 0;\n }\n div[more-body] {\n display: grid;\n gap: 16px;\n margin-top: 12px;\n }\n /*\n The view settings and the release history, opened from the bar over the right of the work: neither is about the\n part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16). The fastening window stands in\n the same place, over the side panel, so the canvas it previews on stays in sight (user 2026-10-19).\n */\n :host {\n position: relative;\n }\n div[side-overlay] {\n position: absolute;\n top: var(--figure-bar-height, 44px);\n right: 8px;\n z-index: 5;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100% - 16px));\n max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);\n }\n div[overlay-head] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 8px 8px 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n div[overlay-head] h3 {\n margin: 0;\n }\n div[overlay-head] button[quiet] {\n margin-left: 0;\n }\n [overlay-body] {\n min-height: 0;\n overflow: auto;\n }\n /* Which part the panel speaks of. Above z-index 1: a chip's label (md-filter-chip) sits at 1 and, coming later, was\n drawn over it when the panel scrolled (sweep 2026-10-27 U2). */\n div[subject] {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n gap: 4px;\n padding: 10px 12px;\n background: var(--md-sys-color-surface-container-lowest);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */\n div[subject-summary] {\n display: grid;\n gap: 2px;\n }\n div[subject-summary] b {\n font-size: var(--ui-t-body, 13px);\n font-weight: 600;\n }\n div[subject-summary] b span {\n font-weight: 400;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[subject-summary] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one\n height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도\n 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).\n */\n button {\n font: inherit;\n box-sizing: border-box;\n color: inherit;\n min-width: 0;\n cursor: pointer;\n }\n /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */\n ox-canvas-bar button[release-blocked] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 32px;\n padding: 0 14px 0 10px;\n border: none;\n /* Round as save and release beside it: one shape for the bar's end. */\n border-radius: 999em;\n color: var(--md-sys-color-on-error-container);\n background: var(--md-sys-color-error-container);\n }\n ox-canvas-bar button[release-blocked] md-icon {\n --md-icon-size: 18px;\n }\n div[release-blockers] {\n position: absolute;\n top: 46px;\n right: 8px;\n z-index: 6;\n width: min(340px, calc(100% - 16px));\n padding: 8px 10px 10px;\n box-sizing: border-box;\n background: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);\n }\n ox-canvas-bar button[publish] {\n border: 1px solid var(--md-sys-color-outline);\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface);\n }\n ox-canvas-bar button[publish][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n border-color: var(--md-sys-color-primary);\n }\n .hint {\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n`\n"]}
@@ -1970,15 +1970,17 @@ let FigureEditor = class FigureEditor extends LitElement {
1970
1970
  @part-live=${(e) => this.hold({}, e.detail.edit)}
1971
1971
  ></figure-material-panel>
1972
1972
  ${this.renderMore(part, view.palette ?? {}, unavailable)}`
1973
- : view.geometry.length === 0
1974
- ? html `${this.renderSubject()}<section empty-figure>
1973
+ : view.loading || !view.context
1974
+ ? nothing
1975
+ : view.geometry.length === 0
1976
+ ? html `${this.renderSubject()}<section empty-figure>
1975
1977
  <p>${i18next.t('figure.text.empty-figure-next')}</p>
1976
1978
  <details>
1977
1979
  <summary>${i18next.t('figure.label.figure-info-later')}</summary>
1978
1980
  <slot name="figure"></slot>
1979
1981
  </details>
1980
1982
  </section>`
1981
- : html `${this.renderSubject()}<slot name="figure"></slot>`}
1983
+ : html `${this.renderSubject()}<slot name="figure"></slot>`}
1982
1984
  </div>
1983
1985
  <div panel ?off=${tab !== 'response'}>
1984
1986
  ${this.renderSubject()}