@things-factory/figure-ui 10.1.168 → 10.1.170

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 (123) hide show
  1. package/client/figure-assistant.ts +10 -1
  2. package/client/modeller/affected-parts.ts +11 -0
  3. package/client/modeller/ai-proposal.ts +40 -9
  4. package/client/modeller/canvas.ts +15 -1
  5. package/client/modeller/editor-styles.ts +98 -6
  6. package/client/modeller/editor.ts +212 -53
  7. package/client/modeller/fasten-geometry.ts +19 -0
  8. package/client/modeller/figure-fasten-drawing.ts +4 -3
  9. package/client/modeller/figure-links.ts +80 -37
  10. package/client/modeller/figure-material-panel.ts +20 -1
  11. package/client/modeller/figure-preview.ts +117 -8
  12. package/client/modeller/figure-properties.ts +1 -2
  13. package/client/modeller/figure-section-panel.ts +5 -0
  14. package/client/modeller/figure-verdict.ts +9 -2
  15. package/client/modeller/gate-findings.ts +12 -4
  16. package/client/modeller/group-menu.ts +57 -0
  17. package/client/modeller/links.ts +22 -1
  18. package/client/modeller/modeller-keys.ts +6 -0
  19. package/client/modeller/preview-size.ts +32 -0
  20. package/client/modeller/product-session.ts +23 -9
  21. package/client/modeller/refusals.ts +14 -0
  22. package/client/modeller/stage-model.ts +11 -0
  23. package/client/pages/figure-modeller-page.ts +15 -6
  24. package/dist-client/figure-assistant.d.ts +6 -0
  25. package/dist-client/figure-assistant.js +9 -1
  26. package/dist-client/figure-assistant.js.map +1 -1
  27. package/dist-client/modeller/affected-parts.d.ts +6 -0
  28. package/dist-client/modeller/affected-parts.js +12 -0
  29. package/dist-client/modeller/affected-parts.js.map +1 -0
  30. package/dist-client/modeller/ai-proposal.d.ts +15 -5
  31. package/dist-client/modeller/ai-proposal.js +39 -10
  32. package/dist-client/modeller/ai-proposal.js.map +1 -1
  33. package/dist-client/modeller/canvas.d.ts +3 -0
  34. package/dist-client/modeller/canvas.js +12 -1
  35. package/dist-client/modeller/canvas.js.map +1 -1
  36. package/dist-client/modeller/editor-styles.js +98 -6
  37. package/dist-client/modeller/editor-styles.js.map +1 -1
  38. package/dist-client/modeller/editor.d.ts +42 -1
  39. package/dist-client/modeller/editor.js +220 -51
  40. package/dist-client/modeller/editor.js.map +1 -1
  41. package/dist-client/modeller/fasten-geometry.d.ts +27 -0
  42. package/dist-client/modeller/fasten-geometry.js +14 -0
  43. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  44. package/dist-client/modeller/figure-fasten-drawing.js +4 -3
  45. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  46. package/dist-client/modeller/figure-links.d.ts +3 -1
  47. package/dist-client/modeller/figure-links.js +84 -38
  48. package/dist-client/modeller/figure-links.js.map +1 -1
  49. package/dist-client/modeller/figure-material-panel.d.ts +8 -1
  50. package/dist-client/modeller/figure-material-panel.js +24 -1
  51. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  52. package/dist-client/modeller/figure-preview.d.ts +12 -0
  53. package/dist-client/modeller/figure-preview.js +116 -5
  54. package/dist-client/modeller/figure-preview.js.map +1 -1
  55. package/dist-client/modeller/figure-properties.js +1 -2
  56. package/dist-client/modeller/figure-properties.js.map +1 -1
  57. package/dist-client/modeller/figure-section-panel.d.ts +1 -1
  58. package/dist-client/modeller/figure-section-panel.js +5 -0
  59. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  60. package/dist-client/modeller/figure-verdict.d.ts +1 -0
  61. package/dist-client/modeller/figure-verdict.js +8 -2
  62. package/dist-client/modeller/figure-verdict.js.map +1 -1
  63. package/dist-client/modeller/gate-findings.d.ts +3 -1
  64. package/dist-client/modeller/gate-findings.js +8 -4
  65. package/dist-client/modeller/gate-findings.js.map +1 -1
  66. package/dist-client/modeller/group-menu.d.ts +18 -0
  67. package/dist-client/modeller/group-menu.js +52 -0
  68. package/dist-client/modeller/group-menu.js.map +1 -0
  69. package/dist-client/modeller/links.d.ts +5 -0
  70. package/dist-client/modeller/links.js +21 -1
  71. package/dist-client/modeller/links.js.map +1 -1
  72. package/dist-client/modeller/modeller-keys.js +7 -0
  73. package/dist-client/modeller/modeller-keys.js.map +1 -1
  74. package/dist-client/modeller/preview-size.d.ts +23 -0
  75. package/dist-client/modeller/preview-size.js +32 -0
  76. package/dist-client/modeller/preview-size.js.map +1 -0
  77. package/dist-client/modeller/product-session.d.ts +0 -1
  78. package/dist-client/modeller/product-session.js +22 -9
  79. package/dist-client/modeller/product-session.js.map +1 -1
  80. package/dist-client/modeller/refusals.d.ts +1 -5
  81. package/dist-client/modeller/refusals.js +14 -1
  82. package/dist-client/modeller/refusals.js.map +1 -1
  83. package/dist-client/modeller/stage-model.d.ts +11 -0
  84. package/dist-client/modeller/stage-model.js +11 -0
  85. package/dist-client/modeller/stage-model.js.map +1 -1
  86. package/dist-client/pages/figure-modeller-page.d.ts +3 -0
  87. package/dist-client/pages/figure-modeller-page.js +18 -5
  88. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  89. package/dist-client/route.d.ts +1 -1
  90. package/dist-client/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +8 -8
  92. package/test/ai-proposal.test.ts +43 -4
  93. package/test/browser/editing-page.js +3 -1
  94. package/test/browser/editing.check.mjs +2 -1
  95. package/test/browser/links-page.js +1 -0
  96. package/test/browser/links.check.mjs +20 -5
  97. package/test/browser/modeller.check.mjs +6 -0
  98. package/test/browser/more-open.js +11 -0
  99. package/test/browser/parts.check.mjs +11 -1
  100. package/test/browser/places-mesh.check.mjs +9 -3
  101. package/test/browser/product-page.check.mjs +9 -4
  102. package/test/browser/props.check.mjs +224 -0
  103. package/test/browser/screen-page.js +17 -0
  104. package/test/browser/screen.check.mjs +171 -29
  105. package/test/browser/section-page.js +1 -0
  106. package/test/browser/section.check.mjs +6 -0
  107. package/test/command-preview.test.ts +2 -2
  108. package/test/fasten-geometry.test.ts +15 -0
  109. package/test/fields.test.ts +4 -2
  110. package/test/figure-assistant-surface.test.ts +38 -0
  111. package/test/first-minute.test.ts +15 -2
  112. package/test/layout.test.ts +1 -1
  113. package/test/preview-size.test.ts +33 -0
  114. package/test/refusals.test.ts +1 -1
  115. package/test/resize-with-placement.test.ts +1 -1
  116. package/test/stage-model.test.ts +11 -1
  117. package/test/verdict.test.ts +7 -0
  118. package/translations/en.json +43 -27
  119. package/translations/es.json +24 -8
  120. package/translations/ja.json +25 -9
  121. package/translations/ko.json +56 -40
  122. package/translations/ms.json +25 -9
  123. package/translations/zh.json +25 -9
@@ -3,11 +3,13 @@ import { LitElement, html, css, unsafeCSS } from 'lit';
3
3
  import { customElement, property, query } from 'lit/decorators.js';
4
4
  import { create, Model, niceGridStep } from '@hatiolab/things-scene';
5
5
  import { DEFAULT_VIEW, sceneLook, WORKBENCH } from '@things-factory/figure-viewer/dist-client/figure-view.js';
6
- import { boardFor, changedKeys, componentOf, gizmoResultOf } from './stage-model.js';
6
+ import { boardFor, changedKeys, componentOf, gizmoResultOf, needsRefit } from './stage-model.js';
7
7
  import { JOINT_FAN } from './joint-fan.js';
8
8
  import { SLOT_MARK } from './slot-layout-host.js';
9
9
  import { PREVIEW, TARGET_FACE } from './command-preview.js';
10
10
  import { ownerOf } from './fields.js';
11
+ /* How long the figure stands still before the camera judges whether to follow it. */
12
+ const REFIT_SETTLE_MS = 400;
11
13
  /* The fan's colour when the figure's palette names no accent. */
12
14
  const FAN_COLOUR = '#ff8a00';
13
15
  /* The 2D side of the board, which the author never sees on this page but the scene paints. */
@@ -101,6 +103,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
101
103
  }
102
104
  disconnectedCallback() {
103
105
  super.disconnectedCallback();
106
+ clearTimeout(this.refitTimer);
104
107
  this.watcher?.disconnect();
105
108
  this.watcher = undefined;
106
109
  this.teardown();
@@ -130,6 +133,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
130
133
  }
131
134
  /** The whole figure in view: the board's fit, then the 3D camera's own. */
132
135
  fit() {
136
+ this.fittedFor = boardFor(this.parts);
133
137
  this.scene?.fit?.('ratio');
134
138
  this.scene?.root?.fitCameraToModel?.();
135
139
  }
@@ -154,6 +158,13 @@ let FigureCanvas = class FigureCanvas extends LitElement {
154
158
  if (outgrown)
155
159
  this.grow(needed);
156
160
  this.sync();
161
+ /*
162
+ Grown or shrunk far past the view (a size typed in): the camera follows, once the figure has settled. A value is
163
+ drawn as it is typed -- 「3」, 「30」, 「300」 on the way to 3000 -- so the reach is judged after the last change.
164
+ */
165
+ clearTimeout(this.refitTimer);
166
+ if (this.parts.length)
167
+ this.refitTimer = setTimeout(() => needsRefit(this.fittedFor, boardFor(this.parts)) && this.fit(), REFIT_SETTLE_MS);
157
168
  return;
158
169
  }
159
170
  this.teardown();
@@ -1 +1 @@
1
- {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../client/modeller/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './gizmo.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board, type ScaleAnchor } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { SLOT_MARK } from './slot-layout-host.js'\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * The modeller's canvas -- a things-scene board in editing mode.\n *\n * Each body figure-model evaluates stands on the board as a `figure-part` component, so everything the canvas\n * has comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n setGizmoMode?: (mode: 'translate' | 'rotate' | 'scale') => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-canvas')\nexport class FigureCanvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n `\n @property({ attribute: false }) parts: FigureRenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** The face a size drag keeps, per part (figure-model's `figureScaleAnchorOf`), so the drag shows what is written. */\n @property({ attribute: false }) anchors: Record<string, ScaleAnchor> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit.\n */\n @property({ type: Boolean }) viewOnly = false\n /** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: FigureRenderPart | null = null\n /**\n * A command seen before it is done (ADR-0097 §5.5): the bodies it would move, where they would stand, translucent,\n * and the face they would be fastened to, lit. Shown, never picked, never saved; gone when the command is.\n */\n @property({ attribute: false }) ghost: FigureRenderPart[] = []\n @property({ attribute: false }) highlight: FigureRenderPart | null = null\n /** The slots a slot layout unfolds to (ADR-0118), as boxes of what each allows: shown, never picked, never saved. */\n @property({ attribute: false }) marks: FigureRenderPart[] = []\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n /*\n * No line of its own over an empty stage: a new figure's start card says how to begin, and an empty stage otherwise\n * is one loading, or with every part hidden -- where 「도형을 추가해…」 flashed and said the wrong thing (2026-10-02).\n */\n return html`<div stage></div>`\n }\n\n firstUpdated() {\n this.standUp()\n /* Out of sight it has no size to draw at: it resizes when it is shown again, not to nothing. */\n this.watcher = new ResizeObserver(() => {\n if (this.checkVisibility?.() !== false) this.scene?.resize?.()\n })\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (['parts', 'palette', 'context', 'fan', 'anchors', 'ghost', 'highlight', 'marks'].some(key => changed.has(key))) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (user, 2026-09-24: no card had a picture after this canvas moved onto the scene).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n /* Kept standing while the preview is in front, it is not drawn: no picture, as when there was no canvas. */\n if (this.checkVisibility?.() === false) return undefined\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own. */\n fit() {\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /** The gizmo in a mode on the selected part, as W, E and R do with the canvas focused -- the scene's toggle. */\n gizmo(mode: 'translate' | 'rotate' | 'scale') {\n this.scene?.root?.setGizmoMode?.(mode)\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context) {\n /*\n A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,\n the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).\n */\n if (outgrown) this.grow(needed)\n this.sync()\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large. */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!, this.anchors[ownerOf(part.id)])).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n const accent = this.palette['palette.accent'] ?? FAN_COLOUR\n const slots = this.marks.map(part => componentOf(part, { ...this.palette, [SLOT_MARK]: this.palette['palette.secondary'] ?? accent }, this.board!))\n const shown = [...(fan ? [fan] : []), ...slots, ...this.ghost.map(part => componentOf(part, { ...this.palette, [PREVIEW]: accent, [TARGET_FACE]: accent }, this.board!)), ...(this.highlight ? [componentOf(this.highlight, { ...this.palette, [TARGET_FACE]: accent }, this.board!)] : [])]\n return [...parts, ...shown.filter((c): c is Record<string, unknown> => !!c).map(c => ({ ...c, capturable: false, locked: true }))]\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */\n private grow(needed: Board & { depth: number }) {\n const layer = this.scene?.root\n this.board = needed\n if (!layer) return\n this.writing = true\n try {\n layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) })\n } finally {\n this.writing = false\n }\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n /*\n A canvas taken off the screen disposes its scene, and the scene empties its selection as it goes. Told out,\n that emptied the editor's choice whenever the middle turned to the preview (U03): the listener lit set on this\n element hears it even after it left the page. A canvas not on the page has nothing to say about the choice.\n */\n if (!this.isConnected) return\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (ownerOf(id) !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing.\n */\n /*\n A drag is written once, when it ends. The scene's gizmo writes the component on every move -- a size anchored on\n the opposite face, from the scale it captured when the drag began -- and writing each move back through the\n editor (and the evaluation back onto the component) fought it: the scale gizmo did not work (user, 2026-09-24).\n While a pointer is down the changes are only kept; on release the last one is told, and the board is brought to\n the evaluation. A change with no pointer down (the arrow keys) is told after a short pause.\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n if (typeof name !== 'string' || !this.parts.some(p => p.id === name)) return\n this.pending.set(name, origin!)\n if (!this.pointerDown) this.commitSoon(80)\n })\n this.stage.addEventListener('pointerdown', this.onPointerDown, true)\n window.addEventListener('pointerup', this.onPointerUp, true)\n window.addEventListener('pointercancel', this.onPointerUp, true)\n }\n\n /** Components the gizmo changed since the last commit, by part name. */\n private pending = new Map<string, SceneComponent>()\n private pointerDown = false\n private commitTimer?: ReturnType<typeof setTimeout>\n private onPointerDown = () => {\n this.pointerDown = true\n }\n private onPointerUp = () => {\n if (!this.pointerDown) return\n this.pointerDown = false\n /* After the scene's own drag end (it settles the depth and rebuilds the geometry on release). */\n this.commitSoon(0)\n }\n private commitSoon(delay: number) {\n clearTimeout(this.commitTimer)\n this.commitTimer = setTimeout(() => this.commit(), delay)\n }\n /** Tell each changed part once, in the inputs' terms; then put the board where the inputs say (a refusal springs back). */\n private commit() {\n /*\n Not while a pointer is down: a pause set by a change just before the press (the arrow keys, the scene settling\n the pick) fired in the middle of the drag and wrote where the part was then -- a drag on a part placed by a rule\n became two steps, the first one a few millimetres in (browser check, 2026-09-25). What is pending waits for the release.\n */\n if (this.pointerDown || !this.board || !this.pending.size) return\n const changed = [...this.pending]\n this.pending.clear()\n for (const [name, component] of changed) {\n const part = this.parts.find(p => p.id === name)\n if (!part) continue\n const result = gizmoResultOf(part, component.state, this.board)\n if (!result.position && !result.rotation && !result.scale) continue\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n }\n setTimeout(() => this.sync())\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n /* A repeat is selected whole: every copy of it (the editor selects the part the author made). */\n const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : []\n const now = root.selected ?? []\n if (now.length === wanted.length && wanted.every((one, i) => now[i] === one)) return\n root.selected = wanted\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n clearTimeout(this.commitTimer)\n this.pending.clear()\n this.stage?.removeEventListener('pointerdown', this.onPointerDown, true)\n window.removeEventListener('pointerup', this.onPointerUp, true)\n window.removeEventListener('pointercancel', this.onPointerUp, true)\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
1
+ {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../client/modeller/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './gizmo.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, needsRefit, type Board, type ScaleAnchor } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { SLOT_MARK } from './slot-layout-host.js'\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * The modeller's canvas -- a things-scene board in editing mode.\n *\n * Each body figure-model evaluates stands on the board as a `figure-part` component, so everything the canvas\n * has comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n setGizmoMode?: (mode: 'translate' | 'rotate' | 'scale') => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* How long the figure stands still before the camera judges whether to follow it. */\nconst REFIT_SETTLE_MS = 400\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-canvas')\nexport class FigureCanvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n `\n @property({ attribute: false }) parts: FigureRenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** The face a size drag keeps, per part (figure-model's `figureScaleAnchorOf`), so the drag shows what is written. */\n @property({ attribute: false }) anchors: Record<string, ScaleAnchor> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit.\n */\n @property({ type: Boolean }) viewOnly = false\n /** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: FigureRenderPart | null = null\n /**\n * A command seen before it is done (ADR-0097 §5.5): the bodies it would move, where they would stand, translucent,\n * and the face they would be fastened to, lit. Shown, never picked, never saved; gone when the command is.\n */\n @property({ attribute: false }) ghost: FigureRenderPart[] = []\n @property({ attribute: false }) highlight: FigureRenderPart | null = null\n /** The slots a slot layout unfolds to (ADR-0118), as boxes of what each allows: shown, never picked, never saved. */\n @property({ attribute: false }) marks: FigureRenderPart[] = []\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n /** The figure's reach when the view was last fitted to it. */\n private fittedFor?: Board & { depth: number }\n private refitTimer?: ReturnType<typeof setTimeout>\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n /*\n * No line of its own over an empty stage: a new figure's start card says how to begin, and an empty stage otherwise\n * is one loading, or with every part hidden -- where 「도형을 추가해…」 flashed and said the wrong thing (2026-10-02).\n */\n return html`<div stage></div>`\n }\n\n firstUpdated() {\n this.standUp()\n /* Out of sight it has no size to draw at: it resizes when it is shown again, not to nothing. */\n this.watcher = new ResizeObserver(() => {\n if (this.checkVisibility?.() !== false) this.scene?.resize?.()\n })\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (['parts', 'palette', 'context', 'fan', 'anchors', 'ghost', 'highlight', 'marks'].some(key => changed.has(key))) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n clearTimeout(this.refitTimer)\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (user, 2026-09-24: no card had a picture after this canvas moved onto the scene).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n /* Kept standing while the preview is in front, it is not drawn: no picture, as when there was no canvas. */\n if (this.checkVisibility?.() === false) return undefined\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own. */\n fit() {\n this.fittedFor = boardFor(this.parts)\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /** The gizmo in a mode on the selected part, as W, E and R do with the canvas focused -- the scene's toggle. */\n gizmo(mode: 'translate' | 'rotate' | 'scale') {\n this.scene?.root?.setGizmoMode?.(mode)\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context) {\n /*\n A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,\n the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).\n */\n if (outgrown) this.grow(needed)\n this.sync()\n /*\n Grown or shrunk far past the view (a size typed in): the camera follows, once the figure has settled. A value is\n drawn as it is typed -- 「3」, 「30」, 「300」 on the way to 3000 -- so the reach is judged after the last change.\n */\n clearTimeout(this.refitTimer)\n if (this.parts.length) this.refitTimer = setTimeout(() => needsRefit(this.fittedFor, boardFor(this.parts)) && this.fit(), REFIT_SETTLE_MS)\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large. */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!, this.anchors[ownerOf(part.id)])).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n const accent = this.palette['palette.accent'] ?? FAN_COLOUR\n const slots = this.marks.map(part => componentOf(part, { ...this.palette, [SLOT_MARK]: this.palette['palette.secondary'] ?? accent }, this.board!))\n const shown = [...(fan ? [fan] : []), ...slots, ...this.ghost.map(part => componentOf(part, { ...this.palette, [PREVIEW]: accent, [TARGET_FACE]: accent }, this.board!)), ...(this.highlight ? [componentOf(this.highlight, { ...this.palette, [TARGET_FACE]: accent }, this.board!)] : [])]\n return [...parts, ...shown.filter((c): c is Record<string, unknown> => !!c).map(c => ({ ...c, capturable: false, locked: true }))]\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */\n private grow(needed: Board & { depth: number }) {\n const layer = this.scene?.root\n this.board = needed\n if (!layer) return\n this.writing = true\n try {\n layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) })\n } finally {\n this.writing = false\n }\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n /*\n A canvas taken off the screen disposes its scene, and the scene empties its selection as it goes. Told out,\n that emptied the editor's choice whenever the middle turned to the preview (U03): the listener lit set on this\n element hears it even after it left the page. A canvas not on the page has nothing to say about the choice.\n */\n if (!this.isConnected) return\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (ownerOf(id) !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing.\n */\n /*\n A drag is written once, when it ends. The scene's gizmo writes the component on every move -- a size anchored on\n the opposite face, from the scale it captured when the drag began -- and writing each move back through the\n editor (and the evaluation back onto the component) fought it: the scale gizmo did not work (user, 2026-09-24).\n While a pointer is down the changes are only kept; on release the last one is told, and the board is brought to\n the evaluation. A change with no pointer down (the arrow keys) is told after a short pause.\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n if (typeof name !== 'string' || !this.parts.some(p => p.id === name)) return\n this.pending.set(name, origin!)\n if (!this.pointerDown) this.commitSoon(80)\n })\n this.stage.addEventListener('pointerdown', this.onPointerDown, true)\n window.addEventListener('pointerup', this.onPointerUp, true)\n window.addEventListener('pointercancel', this.onPointerUp, true)\n }\n\n /** Components the gizmo changed since the last commit, by part name. */\n private pending = new Map<string, SceneComponent>()\n private pointerDown = false\n private commitTimer?: ReturnType<typeof setTimeout>\n private onPointerDown = () => {\n this.pointerDown = true\n }\n private onPointerUp = () => {\n if (!this.pointerDown) return\n this.pointerDown = false\n /* After the scene's own drag end (it settles the depth and rebuilds the geometry on release). */\n this.commitSoon(0)\n }\n private commitSoon(delay: number) {\n clearTimeout(this.commitTimer)\n this.commitTimer = setTimeout(() => this.commit(), delay)\n }\n /** Tell each changed part once, in the inputs' terms; then put the board where the inputs say (a refusal springs back). */\n private commit() {\n /*\n Not while a pointer is down: a pause set by a change just before the press (the arrow keys, the scene settling\n the pick) fired in the middle of the drag and wrote where the part was then -- a drag on a part placed by a rule\n became two steps, the first one a few millimetres in (browser check, 2026-09-25). What is pending waits for the release.\n */\n if (this.pointerDown || !this.board || !this.pending.size) return\n const changed = [...this.pending]\n this.pending.clear()\n for (const [name, component] of changed) {\n const part = this.parts.find(p => p.id === name)\n if (!part) continue\n const result = gizmoResultOf(part, component.state, this.board)\n if (!result.position && !result.rotation && !result.scale) continue\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n }\n setTimeout(() => this.sync())\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n /* A repeat is selected whole: every copy of it (the editor selects the part the author made). */\n const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : []\n const now = root.selected ?? []\n if (now.length === wanted.length && wanted.every((one, i) => now[i] === one)) return\n root.selected = wanted\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n clearTimeout(this.commitTimer)\n this.pending.clear()\n this.stage?.removeEventListener('pointerdown', this.onPointerDown, true)\n window.removeEventListener('pointerup', this.onPointerUp, true)\n window.removeEventListener('pointercancel', this.onPointerUp, true)\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
@@ -2,6 +2,7 @@ import { css } from 'lit';
2
2
  /** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */
3
3
  export const EditorStyles = css `
4
4
  :host {
5
+ position: relative;
5
6
  display: flex;
6
7
  flex-direction: column;
7
8
  min-height: 0;
@@ -15,6 +16,7 @@ export const EditorStyles = css `
15
16
  fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
16
17
  ox-canvas-bar button[mode] {
17
18
  height: 28px;
19
+ border: 1px solid transparent;
18
20
  padding: 0 10px;
19
21
  border-radius: 999em;
20
22
  border-color: transparent;
@@ -249,6 +251,63 @@ export const EditorStyles = css `
249
251
  [off] {
250
252
  display: none !important;
251
253
  }
254
+ /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */
255
+ div[group-fields] {
256
+ display: grid;
257
+ gap: 8px;
258
+ }
259
+ /* A folded group (capability, 「더 보기」): its title is the fold's handle, at the title's size. */
260
+ section[more] > details > summary,
261
+ section[capability] > details > summary {
262
+ padding: 0;
263
+ }
264
+ section[more] > details > summary > h3,
265
+ section[capability] > details > summary > h3 {
266
+ margin: 0;
267
+ }
268
+ div[more-body] {
269
+ display: grid;
270
+ gap: 16px;
271
+ margin-top: 12px;
272
+ }
273
+ /*
274
+ The view settings and the release history, opened from the bar over the right of the work: neither is about the
275
+ part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16).
276
+ */
277
+ :host {
278
+ position: relative;
279
+ }
280
+ div[bar-overlay-panel] {
281
+ position: absolute;
282
+ top: var(--figure-bar-height, 44px);
283
+ right: 8px;
284
+ z-index: 5;
285
+ display: flex;
286
+ flex-direction: column;
287
+ width: min(360px, calc(100% - 16px));
288
+ max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);
289
+ background: var(--md-sys-color-surface-container-lowest);
290
+ border: 1px solid var(--md-sys-color-outline-variant);
291
+ border-radius: 8px;
292
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
293
+ }
294
+ div[overlay-head] {
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: space-between;
298
+ padding: 8px 8px 8px 12px;
299
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
300
+ }
301
+ div[overlay-head] h3 {
302
+ margin: 0;
303
+ }
304
+ div[overlay-head] button[quiet] {
305
+ margin-left: 0;
306
+ }
307
+ div[overlay-body] {
308
+ min-height: 0;
309
+ overflow: auto;
310
+ }
252
311
  /* Which part the panel speaks of. */
253
312
  div[subject] {
254
313
  position: sticky;
@@ -276,19 +335,52 @@ export const EditorStyles = css `
276
335
  div[subject-summary] small {
277
336
  color: var(--md-sys-color-on-surface-variant);
278
337
  }
338
+ /*
339
+ No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one
340
+ height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도
341
+ 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).
342
+ */
279
343
  button {
280
344
  font: inherit;
281
345
  box-sizing: border-box;
346
+ color: inherit;
347
+ min-width: 0;
348
+ cursor: pointer;
349
+ }
350
+ /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */
351
+ ox-canvas-bar button[release-blocked] {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ gap: 4px;
282
355
  height: 32px;
283
- border: 1px solid var(--md-sys-color-outline-variant);
356
+ padding: 0 10px;
357
+ border: none;
284
358
  border-radius: 6px;
359
+ color: var(--md-sys-color-on-error-container);
360
+ background: var(--md-sys-color-error-container);
361
+ }
362
+ ox-canvas-bar button[release-blocked] md-icon {
363
+ --md-icon-size: 18px;
364
+ }
365
+ div[release-blockers] {
366
+ position: absolute;
367
+ top: 46px;
368
+ right: 8px;
369
+ z-index: 6;
370
+ width: min(340px, calc(100% - 16px));
371
+ padding: 8px 10px 10px;
372
+ box-sizing: border-box;
285
373
  background: var(--md-sys-color-surface);
286
- color: inherit;
287
- padding: 0 8px;
288
- min-width: 0;
374
+ border: 1px solid var(--md-sys-color-outline-variant);
375
+ border-radius: 8px;
376
+ box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);
289
377
  }
290
- button {
291
- cursor: pointer;
378
+ ox-canvas-bar button[publish] {
379
+ height: 32px;
380
+ padding: 0 8px;
381
+ border: 1px solid var(--md-sys-color-outline-variant);
382
+ border-radius: 6px;
383
+ background: var(--md-sys-color-surface);
292
384
  }
293
385
  button:disabled {
294
386
  opacity: 0.45;
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+S9B,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 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 font: 13px sans-serif;\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 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 ox-canvas-bar .status[dirty] {\n color: var(--md-sys-color-primary);\n font-weight: 600;\n }\n ox-canvas-bar button[save] {\n height: 32px;\n padding: 0 14px;\n border: none;\n border-radius: 999em;\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 /* 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 aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\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: 0.66rem;\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n span[badge] {\n position: absolute;\n top: 3px;\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: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\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 /* 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 button {\n font: inherit;\n box-sizing: border-box;\n height: 32px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface);\n color: inherit;\n padding: 0 8px;\n min-width: 0;\n }\n button {\n cursor: pointer;\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2Y9B,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 font: 13px sans-serif;\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 ox-canvas-bar .status[dirty] {\n color: var(--md-sys-color-primary);\n font-weight: 600;\n }\n ox-canvas-bar button[save] {\n height: 32px;\n padding: 0 14px;\n border: none;\n border-radius: 999em;\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 /* 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 aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\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: 0.66rem;\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n span[badge] {\n position: absolute;\n top: 3px;\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: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\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: 8px;\n }\n /* A folded group (capability, 「더 보기」): its title is the fold's handle, at the title's size. */\n section[more] > details > summary,\n section[capability] > details > summary {\n padding: 0;\n }\n section[more] > details > summary > h3,\n section[capability] > 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).\n */\n :host {\n position: relative;\n }\n div[bar-overlay-panel] {\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 div[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 10px;\n border: none;\n border-radius: 6px;\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 height: 32px;\n padding: 0 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface);\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"]}
@@ -16,6 +16,7 @@ import './figure-preview.js';
16
16
  import './figure-settings.js';
17
17
  import './figure-capability-panel.js';
18
18
  import './figure-mesh-choice.js';
19
+ import type { FigureGateFinding } from '../types.js';
19
20
  import type { FigureBudgetLine } from './maturity.js';
20
21
  import type { FirstSave } from './first-save.js';
21
22
  export declare class FigureEditor extends LitElement {
@@ -26,6 +27,12 @@ export declare class FigureEditor extends LitElement {
26
27
  state?: 'draft' | 'released';
27
28
  version: number;
28
29
  };
30
+ /**
31
+ * What blocks release now, as the page's verdict heard it: said beside 「발행」 with the way out at hand, so an author
32
+ * does not find it at the foot of the verdict tab (first 15 minutes, 2026-10-16; chief architect, Q1 B).
33
+ */
34
+ releaseBlockers: FigureGateFinding[];
35
+ private blockersOpen;
29
36
  /** The recipe templates (ADR-0095), read by the page from the server. */
30
37
  templates: RecipeTemplate[];
31
38
  /** Reads a template's guide; given by the page. */
@@ -52,6 +59,14 @@ export declare class FigureEditor extends LitElement {
52
59
  /** The same link offered again with the size range narrowed, after a link the declared range could not hold. */
53
60
  private retry?;
54
61
  private tab;
62
+ /** The view settings or the release history, open over the work from the bar. */
63
+ private overlay?;
64
+ /** The capability group open: the canvas marks the places only while their group is in sight. */
65
+ private capabilityOpen;
66
+ /** 「더 보기」 open, as this person left it last (chief architect 2026-10-16: kept per person). */
67
+ private moreOpen;
68
+ private static storedMoreOpen;
69
+ private setMoreOpen;
55
70
  /** The frame the bar folds - found after the first render, since the bar is drawn before it. */
56
71
  private panes?;
57
72
  /** What the middle shows: the parts being edited, or the figure placed on a board at eight sizes. */
@@ -187,6 +202,30 @@ export declare class FigureEditor extends LitElement {
187
202
  private renamePart;
188
203
  /** Size response, fastening and motion for the selected part, as authoring commands. */
189
204
  private renderLinks;
205
+ /**
206
+ * A part's groups in one order whatever its state (chief architect 2026-10-16): its size, its place, its row. The links
207
+ * draw the three groups and their rules; the part's own numbers go into them by slot. A fastened part's place and turn
208
+ * are its fastening's: the place group says it in one line and the locked cells are not drawn (user 2026-10-16).
209
+ */
210
+ private renderPartGroups;
211
+ /**
212
+ * What the figure does -- its places (ADR-0118) -- as a group of the properties, folded: a declaration made once,
213
+ * whose marks on the canvas show while it is open. A verdict that finds it empty still says where it is.
214
+ */
215
+ private renderCapability;
216
+ /**
217
+ * 「더 보기」: what few parts need, folded by default and left as this person last left it (chief architect
218
+ * 2026-10-16) -- the section, its corners and inside; transparency, glow and segments; the mirrored copy and the
219
+ * alignment tools.
220
+ */
221
+ private renderMore;
222
+ /** A bar button that opens the view settings or the release history over the work; pressed while it is open. */
223
+ private renderOverlayButton;
224
+ /**
225
+ * The view settings or the release history, over the work under the bar. Both stay drawn, hidden when closed: the
226
+ * history keeps what it read, as the tabs kept it. Esc closes it.
227
+ */
228
+ private renderOverlay;
190
229
  /**
191
230
  * The face a size drag keeps for the selected part, figure-model's rule (`figureScaleAnchorOf`): the gizmo shows it
192
231
  * while dragging and the commit keeps it, so nothing jumps on release. A part whose fastening is turned has none the
@@ -248,7 +287,9 @@ export declare class FigureEditor extends LitElement {
248
287
  private setPitch;
249
288
  /** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
250
289
  private releaseWords;
251
- /** To the release that exists: the history tab's release section, not a second one here. */
290
+ /** The findings that block release, under the bar beside 「발행」, each with the way out the verdict tab offers for it. */
291
+ private renderReleaseBlockers;
292
+ /** To the release that exists: the history's release section, opened from the bar, not a second one here. */
252
293
  private goRelease;
253
294
  private overhangOf;
254
295
  /** The selected part's motion at both ends of its range, made once per version and part. */