@things-factory/board-ui 10.1.24 → 10.1.25

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 (34) hide show
  1. package/dist-client/board-placement-tray.js +3 -3
  2. package/dist-client/board-placement-tray.js.map +1 -1
  3. package/dist-client/board-placement-view.d.ts +1 -1
  4. package/dist-client/board-placement-view.js.map +1 -1
  5. package/dist-client/index.d.ts +0 -3
  6. package/dist-client/index.js +0 -3
  7. package/dist-client/index.js.map +1 -1
  8. package/dist-client/pages/board-modeller-page.d.ts +5 -5
  9. package/dist-client/pages/board-modeller-page.js +19 -21
  10. package/dist-client/pages/board-modeller-page.js.map +1 -1
  11. package/dist-client/pages/board-player-page.js +1 -1
  12. package/dist-client/pages/board-player-page.js.map +1 -1
  13. package/dist-client/pages/board-viewer-page.js +1 -1
  14. package/dist-client/pages/board-viewer-page.js.map +1 -1
  15. package/dist-client/utils/board-model-diff.d.ts +3 -3
  16. package/dist-client/utils/board-model-diff.js +0 -0
  17. package/dist-client/utils/board-model-diff.js.map +1 -1
  18. package/dist-client/utils/patch-apply-gate.js +1 -1
  19. package/dist-client/utils/patch-apply-gate.js.map +1 -1
  20. package/package.json +7 -6
  21. package/dist-client/board-placement-palette.d.ts +0 -39
  22. package/dist-client/board-placement-palette.js +0 -8
  23. package/dist-client/board-placement-palette.js.map +0 -1
  24. package/dist-client/board-provider.d.ts +0 -3
  25. package/dist-client/board-provider.js +0 -87
  26. package/dist-client/board-provider.js.map +0 -1
  27. package/dist-client/pages/board-action-dispatch.d.ts +0 -31
  28. package/dist-client/pages/board-action-dispatch.js +0 -80
  29. package/dist-client/pages/board-action-dispatch.js.map +0 -1
  30. package/dist-client/pages/board-edit-dispatch.d.ts +0 -74
  31. package/dist-client/pages/board-edit-dispatch.js +0 -299
  32. package/dist-client/pages/board-edit-dispatch.js.map +0 -1
  33. package/dist-client/tsconfig.tsbuildinfo +0 -1
  34. package/dist-server/tsconfig.tsbuildinfo +0 -1
@@ -2,8 +2,8 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { LitElement, html, css } from 'lit';
3
3
  import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { hasGroups, hasHierarchy, toPlacementGroups, toPlacementTree } from './board-placement-view.js';
5
- import { boardPlacementProvider } from './board-placement-palette.js';
6
- import { dispatchBoardEditOp } from './pages/board-edit-dispatch.js';
5
+ import { boardPlacementProvider } from '@operato/board/board-placement-palette.js';
6
+ import { dispatchSceneEditOp } from '@operato/scene-ops';
7
7
  /*
8
8
  * board 배치 트레이 = 능력 컴포저 UI — 등록된 BoardPlacementProvider 의 항목을 좌측 트레이로 노출.
9
9
  * 항목(엔티티)마다 가능한 **표현들(능력×목적, N:M)** 을 펼쳐 저작이 선택 배치. 1:1 드롭 아님 —
@@ -171,7 +171,7 @@ let BoardPlacementTray = class BoardPlacementTray extends LitElement {
171
171
  if (!comp)
172
172
  return;
173
173
  const off = (this._dropCount++ % 6) * 22;
174
- dispatchBoardEditOp(this.scene, { op: 'add', component: { ...comp, left: 80 + off, top: 80 + off } });
174
+ dispatchSceneEditOp(this.scene, { op: 'add', component: { ...comp, left: 80 + off, top: 80 + off } });
175
175
  this.scanPlaced();
176
176
  }
177
177
  /* 모두 자동 배치 — 각 항목의 첫 미배치 표현을 배치(빠른 시작). */
@@ -1 +1 @@
1
- {"version":3,"file":"board-placement-tray.js","sourceRoot":"","sources":["../client/board-placement-tray.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { hasGroups, hasHierarchy, toPlacementGroups, toPlacementTree } from './board-placement-view.js'\nimport { boardPlacementProvider, type BoardPlacementItem, type BoardPlacementView } from './board-placement-palette.js'\nimport { dispatchBoardEditOp } from './pages/board-edit-dispatch.js'\n\n/*\n * board 배치 트레이 = 능력 컴포저 UI — 등록된 BoardPlacementProvider 의 항목을 좌측 트레이로 노출.\n * 항목(엔티티)마다 가능한 **표현들(능력×목적, N:M)** 을 펼쳐 저작이 선택 배치. 1:1 드롭 아님 —\n * 한 엔티티에 여러 표현(모니터/지표/제어/에뮬레이션)을 얹을 수 있다. 표현별 배치 여부 표시(tag::type 스캔).\n * 배치 = board-edit 'add' op(model.tag=엔티티 id) → 라이브 결합(board-provider data(tag:)).\n * board 제품 무변경 — 통합층(board-ui)이 modeller 위에 얹는 일반 오버레이.\n */\n@customElement('board-placement-tray')\nexport class BoardPlacementTray extends LitElement {\n @property({ type: String }) boardId?: string\n @property({ type: Object }) scene: any\n\n @state() private items: BoardPlacementItem[] = []\n @state() private placedKeys: Set<string> = new Set() // `${tag}::${componentType}`\n @state() private placedTags: Set<string> = new Set() // tag (fallback 단일 view 판정)\n @state() private _pos = { x: 12, y: 12 }\n @state() private _collapsed = false\n private _drag: { sx: number; sy: number; ox: number; oy: number } | null = null\n private _timer?: any\n private _dropCount = 0\n /**\n * 어떻게 볼 것인가 — **계층**(소속 트리) 또는 **종류**(같은 것끼리 묶음).\n *\n * 호스트가 소속·종류를 안 주면 둘 다 없으므로 예전처럼 평평한 목록이다. 기본은 계층 — 현장은 어디에\n * 무엇이 있는지로 인식한다.\n */\n @state() private _view: 'tree' | 'kind' = 'tree'\n\n static styles = css`\n :host { display: contents; }\n .tray { position: absolute; top: 12px; left: 12px; width: 210px; max-height: calc(100% - 24px); overflow: auto; z-index: 5; background: #fff; border: 1px solid #d5dce0; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 10px 11px; display: flex; flex-direction: column; gap: 7px; }\n .tray.collapsed { overflow: visible; max-height: none; }\n /* 표준 스크롤바(앱 consoleStyles 규약: 얇은 5px·투명 트랙·은은한 thumb). */\n .tray { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,.2) transparent; }\n .tray::-webkit-scrollbar { width: 5px; height: 5px; }\n .tray::-webkit-scrollbar-track { background: transparent; }\n .tray::-webkit-scrollbar-thumb { background: rgba(0,0,0,.2); border-radius: 3px; }\n .tray::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.38); }\n .ttl { font-family: ui-monospace, Menlo, monospace; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: #57676f; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; touch-action: none; }\n .ttl b { color: #0b8f9c; font-weight: 700; }\n .ttl .grip::before { content: '⠿'; color: #8a959c; font-size: 12px; margin-right: 5px; }\n .rt { display: inline-flex; align-items: center; gap: 6px; }\n .collapse { border: 0; background: transparent; color: #57676f; cursor: pointer; font-size: 12px; line-height: 1; padding: 2px 3px; }\n .collapse:hover { color: #0b8f9c; }\n .hint { font-size: 10.5px; color: #8a959c; font-family: ui-monospace, Menlo, monospace; margin: 0; }\n .auto { border: 1px solid #0b8f9c88; background: #0b8f9c14; color: #0b8f9c; border-radius: 7px; padding: 6px 9px; font-size: 11.5px; font-weight: 640; cursor: pointer; font-family: ui-monospace, Menlo, monospace; }\n .auto:hover { background: #0b8f9c26; }\n .node { border: 1px solid #d5dce0; border-radius: 8px; padding: 6px 8px; display: flex; flex-direction: column; gap: 5px; font-family: ui-monospace, Menlo, monospace; }\n .nhead { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; }\n .nid { font-size: 12px; font-weight: 640; color: #17242c; }\n .nty { font-size: 9.5px; color: #8a959c; }\n .views { display: flex; flex-direction: column; gap: 3px; }\n .view { display: flex; justify-content: space-between; align-items: center; gap: 6px; text-align: left; background: transparent; border: 1px solid #cfe6e9; color: #0b8f9c; border-radius: 6px; padding: 4px 7px; font-size: 11px; cursor: pointer; font-family: ui-monospace, Menlo, monospace; }\n .view:hover { background: #0b8f9c12; border-color: #0b8f9c; }\n .view.placed { color: #1a8f4c; border-color: #b7e0c5; background: #1a8f4c0e; cursor: default; }\n .purpose { font-size: 9px; color: #8a959c; text-transform: uppercase; letter-spacing: .04em; }\n .vsw { display: flex; gap: 4px; margin: 0 0 6px; }\n .vsw button { flex: 1; padding: 3px 0; font-size: 10px; border: 1px solid var(--line, #d5dce0); background: transparent; color: inherit; border-radius: 5px; cursor: pointer; }\n .vsw button.on { background: var(--accent, #17242c); color: #fff; border-color: transparent; }\n /* 맥락으로 끼운 조상 — 놓을 것이 없으니 흐리게. 길을 보여 줄 뿐이다. */\n .node.context > .nhead { opacity: 0.55; }\n .grp { margin-bottom: 6px; }\n .ghead { font-size: 10px; opacity: 0.7; margin: 4px 0 2px; }\n .done { color: #1a8f4c; font-size: 11.5px; font-family: ui-monospace, Menlo, monospace; }\n @media (prefers-color-scheme: dark) {\n .tray { background: #141b21; border-color: #26323a; scrollbar-color: rgba(255,255,255,.18) transparent; }\n .tray::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); }\n .tray::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); }\n .ttl { color: #8b9aa3; } .ttl b { color: #22c3cf; }\n .node { border-color: #26323a; } .nid { color: #dde7ec; } .nty { color: #8b9aa3; }\n .view { border-color: #234; color: #22c3cf; } .view:hover { background: #22c3cf16; }\n .auto { border-color: #22c3cf88; background: #22c3cf1e; color: #22c3cf; }\n }\n `\n\n connectedCallback() {\n super.connectedCallback()\n this.reload()\n this._timer = setInterval(() => this.scanPlaced(), 2000)\n }\n disconnectedCallback() {\n super.disconnectedCallback()\n clearInterval(this._timer)\n window.removeEventListener('pointermove', this._onDrag)\n window.removeEventListener('pointerup', this._endDrag)\n }\n updated(changed: Map<string, unknown>) {\n if (changed.has('boardId')) this.reload()\n else if (changed.has('scene')) this.scanPlaced()\n }\n\n /* 헤더 드래그 이동(편집 공간 가림 방지) — 델타 기반. */\n private startDrag(e: PointerEvent) {\n e.preventDefault()\n this._drag = { sx: e.clientX, sy: e.clientY, ox: this._pos.x, oy: this._pos.y }\n window.addEventListener('pointermove', this._onDrag)\n window.addEventListener('pointerup', this._endDrag)\n }\n private _onDrag = (e: PointerEvent) => {\n const d = this._drag\n if (!d) return\n this._pos = { x: Math.max(0, d.ox + (e.clientX - d.sx)), y: Math.max(0, d.oy + (e.clientY - d.sy)) }\n }\n private _endDrag = () => {\n this._drag = null\n window.removeEventListener('pointermove', this._onDrag)\n window.removeEventListener('pointerup', this._endDrag)\n }\n\n private async reload() {\n const p = boardPlacementProvider()\n if (!p || !this.boardId) { this.items = []; return }\n try { this.items = await p.items(this.boardId) } catch { this.items = [] }\n this.scanPlaced()\n }\n private scanPlaced() {\n const provider = boardPlacementProvider()\n const map = this.scene?.rootContainer?.refidIndexMap\n const keys = new Set<string>()\n const tags = new Set<string>()\n map?.forEach?.((comp: any) => {\n const t = comp?.get?.('tag')\n if (!t) return\n tags.add(String(t))\n /* 표현별 판정 = 호스트가 배치된 컴포넌트에서 되짚어 준 key. 컴포넌트에 저장된 값을 읽지 않는다\n (예전에 `viewKey` 를 심었다가 떼면서, 심는 쪽과 읽는 쪽이 어긋나 판정이 죽은 적이 있다).\n 되짚기를 제공하지 않는 호스트는 `type` 을 key 로 쓴다고 보고 비교한다. */\n const derived = provider?.viewKeyOfComponent?.(comp?.model ?? comp)\n const type = comp?.get?.('type') ?? comp?.model?.type\n if (derived) keys.add(`${t}::${derived}`)\n else if (type) keys.add(`${t}::${type}`)\n })\n this.placedKeys = keys\n this.placedTags = tags\n this.requestUpdate()\n }\n\n /* 항목의 표현들(능력×목적). provider 가 viewsFor 지원하면 그 결과(빈 배열=표현 없음)를 그대로,\n 미지원이면 단일 fallback. → 표현 없는 엔티티는 죽은 버튼 없이 자연히 트레이에서 빠짐. */\n private viewsOf(item: BoardPlacementItem): BoardPlacementView[] {\n const p = boardPlacementProvider()\n if (p?.viewsFor) return p.viewsFor(item.id)\n return [{ key: '', label: '배치' }]\n }\n private viewPlaced(id: string, key: string): boolean {\n return key ? this.placedKeys.has(`${id}::${key}`) : this.placedTags.has(id)\n }\n /* 미배치(=아직 안 놓은 표현이 하나라도 있는) 항목. */\n private get unplaced(): BoardPlacementItem[] {\n return this.items.filter(i => this.viewsOf(i).some(v => !this.viewPlaced(i.id, v.key)))\n }\n\n private place(item: BoardPlacementItem, viewKey: string) {\n const p = boardPlacementProvider()\n if (!p || !this.scene) return\n const comp = p.componentFor(item, viewKey || undefined)\n if (!comp) return\n const off = (this._dropCount++ % 6) * 22\n dispatchBoardEditOp(this.scene, { op: 'add', component: { ...comp, left: 80 + off, top: 80 + off } } as any)\n this.scanPlaced()\n }\n /* 모두 자동 배치 — 각 항목의 첫 미배치 표현을 배치(빠른 시작). */\n private placeAll() {\n for (const it of this.unplaced) {\n const v = this.viewsOf(it).find(v => !this.viewPlaced(it.id, v.key))\n if (v) this.place(it, v.key)\n }\n this.scanPlaced()\n }\n\n /** 이 항목에 아직 안 놓은 표현이 있나 — 트리에서 \"놓을 것이 있는지\" 를 이걸로 가른다. */\n private hasUnplaced(item: BoardPlacementItem): boolean {\n return this.viewsOf(item).some(v => !this.viewPlaced(item.id, v.key))\n }\n\n /** 항목 한 줄 — 이름과 그 항목에 놓을 수 있는 표현들. */\n private renderItem(it: BoardPlacementItem, depth = 0, context = false) {\n const views = this.viewsOf(it)\n return html`<div class=\"node ${context ? 'context' : ''}\" style=${depth ? `margin-left:${depth * 12}px` : ''}>\n <div class=\"nhead\"><span class=\"nid\">${it.label}</span>${it.sub ? html`<span class=\"nty\">${it.sub}</span>` : ''}</div>\n ${views.length\n ? html`<div class=\"views\">\n ${views.map(v => {\n const placed = this.viewPlaced(it.id, v.key)\n return html`<button class=\"view ${placed ? 'placed' : ''}\" ?disabled=${placed}\n @click=${() => this.place(it, v.key)} title=${placed ? '배치됨' : '이 표현으로 배치'}>\n <span>${placed ? '✓ ' : ''}${v.label}</span>${v.sub ? html`<span class=\"purpose\">${v.sub}</span>` : ''}\n </button>`\n })}\n </div>`\n : ''}\n </div>`\n }\n\n render() {\n const p = boardPlacementProvider()\n if (!p || !this.items.length) return html``\n const un = this.unplaced\n const co = this._collapsed\n /* 호스트가 소속·종류를 주지 않으면 뷰 전환이 의미 없다 — 예전처럼 평평하게 보여 준다. */\n const tree = hasHierarchy(this.items)\n const kinds = hasGroups(this.items)\n const view = tree && kinds ? this._view : tree ? 'tree' : kinds ? 'kind' : 'flat'\n\n return html`<div class=\"tray ${co ? 'collapsed' : ''}\" style=\"left:${this._pos.x}px;top:${this._pos.y}px\">\n <div class=\"ttl\" @pointerdown=${(e: PointerEvent) => this.startDrag(e)}>\n <span class=\"grip\">${p.label || '미배치'}</span>\n <span class=\"rt\"><b>${un.length}</b>\n <button class=\"collapse\" @pointerdown=${(e: Event) => e.stopPropagation()} @click=${() => (this._collapsed = !co)} title=${co ? '펼치기' : '접기'}>${co ? '▸' : '▾'}</button>\n </span>\n </div>\n ${co\n ? ''\n : un.length\n ? html`\n ${tree && kinds\n ? html`<div class=\"vsw\" @pointerdown=${(e: Event) => e.stopPropagation()}>\n <button class=\"${view === 'tree' ? 'on' : ''}\" @click=${() => (this._view = 'tree')} title=\"공간 계층으로 — 어디에 무엇이 있나\">계층</button>\n <button class=\"${view === 'kind' ? 'on' : ''}\" @click=${() => (this._view = 'kind')} title=\"종류별로 — 같은 것 여럿에 한 번에\">종류</button>\n </div>`\n : ''}\n <button class=\"auto\" @click=${() => this.placeAll()} title=\"각 항목의 첫 표현을 일괄 배치\">모두 자동 배치</button>\n <div class=\"hint\">항목의 표현을 골라 배치. 여러 표현을 얹을 수 있습니다.</div>\n ${view === 'tree'\n ? /* 놓을 것이 남은 항목만 남기되 **조상은 길로 함께** 보여 준다 — 트리가 끊기면 어디에\n 속한 것인지 알 수 없다. */\n toPlacementTree(this.items, it => this.hasUnplaced(it)).map(r => this.renderItem(r.item, r.depth, r.context))\n : view === 'kind'\n ? toPlacementGroups(un).map(\n g => html`<div class=\"grp\"><div class=\"ghead\">${g.group} <b>${g.items.length}</b></div>\n ${g.items.map(it => this.renderItem(it))}</div>`\n )\n : un.map(it => this.renderItem(it))}`\n : html`<div class=\"done\">✓ 모두 배치됨</div>`}\n </div>`\n }\n}\n"]}
1
+ {"version":3,"file":"board-placement-tray.js","sourceRoot":"","sources":["../client/board-placement-tray.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { hasGroups, hasHierarchy, toPlacementGroups, toPlacementTree } from './board-placement-view.js'\nimport { boardPlacementProvider, type BoardPlacementItem, type BoardPlacementView } from '@operato/board/board-placement-palette.js'\nimport { dispatchSceneEditOp } from '@operato/scene-ops'\n\n/*\n * board 배치 트레이 = 능력 컴포저 UI — 등록된 BoardPlacementProvider 의 항목을 좌측 트레이로 노출.\n * 항목(엔티티)마다 가능한 **표현들(능력×목적, N:M)** 을 펼쳐 저작이 선택 배치. 1:1 드롭 아님 —\n * 한 엔티티에 여러 표현(모니터/지표/제어/에뮬레이션)을 얹을 수 있다. 표현별 배치 여부 표시(tag::type 스캔).\n * 배치 = board-edit 'add' op(model.tag=엔티티 id) → 라이브 결합(board-provider data(tag:)).\n * board 제품 무변경 — 통합층(board-ui)이 modeller 위에 얹는 일반 오버레이.\n */\n@customElement('board-placement-tray')\nexport class BoardPlacementTray extends LitElement {\n @property({ type: String }) boardId?: string\n @property({ type: Object }) scene: any\n\n @state() private items: BoardPlacementItem[] = []\n @state() private placedKeys: Set<string> = new Set() // `${tag}::${componentType}`\n @state() private placedTags: Set<string> = new Set() // tag (fallback 단일 view 판정)\n @state() private _pos = { x: 12, y: 12 }\n @state() private _collapsed = false\n private _drag: { sx: number; sy: number; ox: number; oy: number } | null = null\n private _timer?: any\n private _dropCount = 0\n /**\n * 어떻게 볼 것인가 — **계층**(소속 트리) 또는 **종류**(같은 것끼리 묶음).\n *\n * 호스트가 소속·종류를 안 주면 둘 다 없으므로 예전처럼 평평한 목록이다. 기본은 계층 — 현장은 어디에\n * 무엇이 있는지로 인식한다.\n */\n @state() private _view: 'tree' | 'kind' = 'tree'\n\n static styles = css`\n :host { display: contents; }\n .tray { position: absolute; top: 12px; left: 12px; width: 210px; max-height: calc(100% - 24px); overflow: auto; z-index: 5; background: #fff; border: 1px solid #d5dce0; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 10px 11px; display: flex; flex-direction: column; gap: 7px; }\n .tray.collapsed { overflow: visible; max-height: none; }\n /* 표준 스크롤바(앱 consoleStyles 규약: 얇은 5px·투명 트랙·은은한 thumb). */\n .tray { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,.2) transparent; }\n .tray::-webkit-scrollbar { width: 5px; height: 5px; }\n .tray::-webkit-scrollbar-track { background: transparent; }\n .tray::-webkit-scrollbar-thumb { background: rgba(0,0,0,.2); border-radius: 3px; }\n .tray::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.38); }\n .ttl { font-family: ui-monospace, Menlo, monospace; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: #57676f; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; touch-action: none; }\n .ttl b { color: #0b8f9c; font-weight: 700; }\n .ttl .grip::before { content: '⠿'; color: #8a959c; font-size: 12px; margin-right: 5px; }\n .rt { display: inline-flex; align-items: center; gap: 6px; }\n .collapse { border: 0; background: transparent; color: #57676f; cursor: pointer; font-size: 12px; line-height: 1; padding: 2px 3px; }\n .collapse:hover { color: #0b8f9c; }\n .hint { font-size: 10.5px; color: #8a959c; font-family: ui-monospace, Menlo, monospace; margin: 0; }\n .auto { border: 1px solid #0b8f9c88; background: #0b8f9c14; color: #0b8f9c; border-radius: 7px; padding: 6px 9px; font-size: 11.5px; font-weight: 640; cursor: pointer; font-family: ui-monospace, Menlo, monospace; }\n .auto:hover { background: #0b8f9c26; }\n .node { border: 1px solid #d5dce0; border-radius: 8px; padding: 6px 8px; display: flex; flex-direction: column; gap: 5px; font-family: ui-monospace, Menlo, monospace; }\n .nhead { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; }\n .nid { font-size: 12px; font-weight: 640; color: #17242c; }\n .nty { font-size: 9.5px; color: #8a959c; }\n .views { display: flex; flex-direction: column; gap: 3px; }\n .view { display: flex; justify-content: space-between; align-items: center; gap: 6px; text-align: left; background: transparent; border: 1px solid #cfe6e9; color: #0b8f9c; border-radius: 6px; padding: 4px 7px; font-size: 11px; cursor: pointer; font-family: ui-monospace, Menlo, monospace; }\n .view:hover { background: #0b8f9c12; border-color: #0b8f9c; }\n .view.placed { color: #1a8f4c; border-color: #b7e0c5; background: #1a8f4c0e; cursor: default; }\n .purpose { font-size: 9px; color: #8a959c; text-transform: uppercase; letter-spacing: .04em; }\n .vsw { display: flex; gap: 4px; margin: 0 0 6px; }\n .vsw button { flex: 1; padding: 3px 0; font-size: 10px; border: 1px solid var(--line, #d5dce0); background: transparent; color: inherit; border-radius: 5px; cursor: pointer; }\n .vsw button.on { background: var(--accent, #17242c); color: #fff; border-color: transparent; }\n /* 맥락으로 끼운 조상 — 놓을 것이 없으니 흐리게. 길을 보여 줄 뿐이다. */\n .node.context > .nhead { opacity: 0.55; }\n .grp { margin-bottom: 6px; }\n .ghead { font-size: 10px; opacity: 0.7; margin: 4px 0 2px; }\n .done { color: #1a8f4c; font-size: 11.5px; font-family: ui-monospace, Menlo, monospace; }\n @media (prefers-color-scheme: dark) {\n .tray { background: #141b21; border-color: #26323a; scrollbar-color: rgba(255,255,255,.18) transparent; }\n .tray::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); }\n .tray::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); }\n .ttl { color: #8b9aa3; } .ttl b { color: #22c3cf; }\n .node { border-color: #26323a; } .nid { color: #dde7ec; } .nty { color: #8b9aa3; }\n .view { border-color: #234; color: #22c3cf; } .view:hover { background: #22c3cf16; }\n .auto { border-color: #22c3cf88; background: #22c3cf1e; color: #22c3cf; }\n }\n `\n\n connectedCallback() {\n super.connectedCallback()\n this.reload()\n this._timer = setInterval(() => this.scanPlaced(), 2000)\n }\n disconnectedCallback() {\n super.disconnectedCallback()\n clearInterval(this._timer)\n window.removeEventListener('pointermove', this._onDrag)\n window.removeEventListener('pointerup', this._endDrag)\n }\n updated(changed: Map<string, unknown>) {\n if (changed.has('boardId')) this.reload()\n else if (changed.has('scene')) this.scanPlaced()\n }\n\n /* 헤더 드래그 이동(편집 공간 가림 방지) — 델타 기반. */\n private startDrag(e: PointerEvent) {\n e.preventDefault()\n this._drag = { sx: e.clientX, sy: e.clientY, ox: this._pos.x, oy: this._pos.y }\n window.addEventListener('pointermove', this._onDrag)\n window.addEventListener('pointerup', this._endDrag)\n }\n private _onDrag = (e: PointerEvent) => {\n const d = this._drag\n if (!d) return\n this._pos = { x: Math.max(0, d.ox + (e.clientX - d.sx)), y: Math.max(0, d.oy + (e.clientY - d.sy)) }\n }\n private _endDrag = () => {\n this._drag = null\n window.removeEventListener('pointermove', this._onDrag)\n window.removeEventListener('pointerup', this._endDrag)\n }\n\n private async reload() {\n const p = boardPlacementProvider()\n if (!p || !this.boardId) { this.items = []; return }\n try { this.items = await p.items(this.boardId) } catch { this.items = [] }\n this.scanPlaced()\n }\n private scanPlaced() {\n const provider = boardPlacementProvider()\n const map = this.scene?.rootContainer?.refidIndexMap\n const keys = new Set<string>()\n const tags = new Set<string>()\n map?.forEach?.((comp: any) => {\n const t = comp?.get?.('tag')\n if (!t) return\n tags.add(String(t))\n /* 표현별 판정 = 호스트가 배치된 컴포넌트에서 되짚어 준 key. 컴포넌트에 저장된 값을 읽지 않는다\n (예전에 `viewKey` 를 심었다가 떼면서, 심는 쪽과 읽는 쪽이 어긋나 판정이 죽은 적이 있다).\n 되짚기를 제공하지 않는 호스트는 `type` 을 key 로 쓴다고 보고 비교한다. */\n const derived = provider?.viewKeyOfComponent?.(comp?.model ?? comp)\n const type = comp?.get?.('type') ?? comp?.model?.type\n if (derived) keys.add(`${t}::${derived}`)\n else if (type) keys.add(`${t}::${type}`)\n })\n this.placedKeys = keys\n this.placedTags = tags\n this.requestUpdate()\n }\n\n /* 항목의 표현들(능력×목적). provider 가 viewsFor 지원하면 그 결과(빈 배열=표현 없음)를 그대로,\n 미지원이면 단일 fallback. → 표현 없는 엔티티는 죽은 버튼 없이 자연히 트레이에서 빠짐. */\n private viewsOf(item: BoardPlacementItem): BoardPlacementView[] {\n const p = boardPlacementProvider()\n if (p?.viewsFor) return p.viewsFor(item.id)\n return [{ key: '', label: '배치' }]\n }\n private viewPlaced(id: string, key: string): boolean {\n return key ? this.placedKeys.has(`${id}::${key}`) : this.placedTags.has(id)\n }\n /* 미배치(=아직 안 놓은 표현이 하나라도 있는) 항목. */\n private get unplaced(): BoardPlacementItem[] {\n return this.items.filter(i => this.viewsOf(i).some(v => !this.viewPlaced(i.id, v.key)))\n }\n\n private place(item: BoardPlacementItem, viewKey: string) {\n const p = boardPlacementProvider()\n if (!p || !this.scene) return\n const comp = p.componentFor(item, viewKey || undefined)\n if (!comp) return\n const off = (this._dropCount++ % 6) * 22\n dispatchSceneEditOp(this.scene, { op: 'add', component: { ...comp, left: 80 + off, top: 80 + off } } as any)\n this.scanPlaced()\n }\n /* 모두 자동 배치 — 각 항목의 첫 미배치 표현을 배치(빠른 시작). */\n private placeAll() {\n for (const it of this.unplaced) {\n const v = this.viewsOf(it).find(v => !this.viewPlaced(it.id, v.key))\n if (v) this.place(it, v.key)\n }\n this.scanPlaced()\n }\n\n /** 이 항목에 아직 안 놓은 표현이 있나 — 트리에서 \"놓을 것이 있는지\" 를 이걸로 가른다. */\n private hasUnplaced(item: BoardPlacementItem): boolean {\n return this.viewsOf(item).some(v => !this.viewPlaced(item.id, v.key))\n }\n\n /** 항목 한 줄 — 이름과 그 항목에 놓을 수 있는 표현들. */\n private renderItem(it: BoardPlacementItem, depth = 0, context = false) {\n const views = this.viewsOf(it)\n return html`<div class=\"node ${context ? 'context' : ''}\" style=${depth ? `margin-left:${depth * 12}px` : ''}>\n <div class=\"nhead\"><span class=\"nid\">${it.label}</span>${it.sub ? html`<span class=\"nty\">${it.sub}</span>` : ''}</div>\n ${views.length\n ? html`<div class=\"views\">\n ${views.map(v => {\n const placed = this.viewPlaced(it.id, v.key)\n return html`<button class=\"view ${placed ? 'placed' : ''}\" ?disabled=${placed}\n @click=${() => this.place(it, v.key)} title=${placed ? '배치됨' : '이 표현으로 배치'}>\n <span>${placed ? '✓ ' : ''}${v.label}</span>${v.sub ? html`<span class=\"purpose\">${v.sub}</span>` : ''}\n </button>`\n })}\n </div>`\n : ''}\n </div>`\n }\n\n render() {\n const p = boardPlacementProvider()\n if (!p || !this.items.length) return html``\n const un = this.unplaced\n const co = this._collapsed\n /* 호스트가 소속·종류를 주지 않으면 뷰 전환이 의미 없다 — 예전처럼 평평하게 보여 준다. */\n const tree = hasHierarchy(this.items)\n const kinds = hasGroups(this.items)\n const view = tree && kinds ? this._view : tree ? 'tree' : kinds ? 'kind' : 'flat'\n\n return html`<div class=\"tray ${co ? 'collapsed' : ''}\" style=\"left:${this._pos.x}px;top:${this._pos.y}px\">\n <div class=\"ttl\" @pointerdown=${(e: PointerEvent) => this.startDrag(e)}>\n <span class=\"grip\">${p.label || '미배치'}</span>\n <span class=\"rt\"><b>${un.length}</b>\n <button class=\"collapse\" @pointerdown=${(e: Event) => e.stopPropagation()} @click=${() => (this._collapsed = !co)} title=${co ? '펼치기' : '접기'}>${co ? '▸' : '▾'}</button>\n </span>\n </div>\n ${co\n ? ''\n : un.length\n ? html`\n ${tree && kinds\n ? html`<div class=\"vsw\" @pointerdown=${(e: Event) => e.stopPropagation()}>\n <button class=\"${view === 'tree' ? 'on' : ''}\" @click=${() => (this._view = 'tree')} title=\"공간 계층으로 — 어디에 무엇이 있나\">계층</button>\n <button class=\"${view === 'kind' ? 'on' : ''}\" @click=${() => (this._view = 'kind')} title=\"종류별로 — 같은 것 여럿에 한 번에\">종류</button>\n </div>`\n : ''}\n <button class=\"auto\" @click=${() => this.placeAll()} title=\"각 항목의 첫 표현을 일괄 배치\">모두 자동 배치</button>\n <div class=\"hint\">항목의 표현을 골라 배치. 여러 표현을 얹을 수 있습니다.</div>\n ${view === 'tree'\n ? /* 놓을 것이 남은 항목만 남기되 **조상은 길로 함께** 보여 준다 — 트리가 끊기면 어디에\n 속한 것인지 알 수 없다. */\n toPlacementTree(this.items, it => this.hasUnplaced(it)).map(r => this.renderItem(r.item, r.depth, r.context))\n : view === 'kind'\n ? toPlacementGroups(un).map(\n g => html`<div class=\"grp\"><div class=\"ghead\">${g.group} <b>${g.items.length}</b></div>\n ${g.items.map(it => this.renderItem(it))}</div>`\n )\n : un.map(it => this.renderItem(it))}`\n : html`<div class=\"done\">✓ 모두 배치됨</div>`}\n </div>`\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { BoardPlacementItem } from './board-placement-palette.js';
1
+ import type { BoardPlacementItem } from '@operato/board/board-placement-palette.js';
2
2
  export interface PlacementRow {
3
3
  item: BoardPlacementItem;
4
4
  /** 들여쓰기 깊이. 화면은 이 값만 곱하면 된다. */
@@ -1 +1 @@
1
- {"version":3,"file":"board-placement-view.js","sourceRoot":"","sources":["../client/board-placement-view.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 배치 대상 목록을 **두 가지로 보는** 방법 — 순수.\n *\n * 호스트는 항목만 공급하고(`BoardPlacementItem`), 어떻게 볼지는 보드 층이 정한다. 항목에 `parentId`\n * (소속)와 `group`(종류)이 달려 있으면 두 뷰가 성립한다.\n *\n * 계층 뷰 소속 트리 — \"이 자리에 무엇을 놓을까\". 현장 감각으로 찾는다.\n * 종류 뷰 같은 종류끼리 묶음 — \"같은 것 여럿에 한 번에\". 대량 저작의 단위다.\n *\n * 둘 다 **항목을 잃지 않는다.** 목록에서 조용히 빠지면 저작하는 사람은 \"그런 게 없네\" 로 끝난다 —\n * 오류가 아니라서 아무도 모른다.\n */\nimport type { BoardPlacementItem } from './board-placement-palette.js'\n\nexport interface PlacementRow {\n item: BoardPlacementItem\n /** 들여쓰기 깊이. 화면은 이 값만 곱하면 된다. */\n depth: number\n /** 길을 보여 주려고 끼운 조상 — 그 자체는 놓을 것이 없다. */\n context?: boolean\n}\n\n/**\n * 계층 뷰 — 부모 순서로 늘어놓고 깊이를 함께 준다.\n *\n * `keep` 을 주면 그 항목들만 남기되 **조상은 길로 함께 남긴다**(맥락). 조상을 빼면 트리가 끊겨 어디에\n * 속한 것인지 알 수 없다. 부모를 못 찾은 항목은 버리지 않고 최상위로 올린다.\n */\nexport function toPlacementTree(items: readonly BoardPlacementItem[], keep?: (item: BoardPlacementItem) => boolean): PlacementRow[] {\n const byId = new Map(items.map(i => [i.id, i]))\n const parentOf = (i: BoardPlacementItem) => (i.parentId && byId.has(i.parentId) ? i.parentId : '')\n\n /* 남길 것 + 그 조상들. 조상은 맥락으로 표시해 \"놓을 것이 있는 항목\" 과 구별한다. */\n let visible: Set<string> | null = null\n const context = new Set<string>()\n if (keep) {\n visible = new Set(items.filter(keep).map(i => i.id))\n for (const id of [...visible]) {\n let p = parentOf(byId.get(id)!)\n while (p && !visible.has(p) && !context.has(p)) {\n context.add(p)\n p = parentOf(byId.get(p)!)\n }\n }\n }\n\n const children = new Map<string, BoardPlacementItem[]>()\n for (const it of items) {\n if (visible && !visible.has(it.id) && !context.has(it.id)) continue\n const p = parentOf(it)\n if (!children.has(p)) children.set(p, [])\n children.get(p)!.push(it)\n }\n\n const rows: PlacementRow[] = []\n const walk = (parent: string, depth: number) => {\n for (const it of children.get(parent) ?? []) {\n rows.push({ item: it, depth, ...(context.has(it.id) ? { context: true } : {}) })\n walk(it.id, depth + 1)\n }\n }\n walk('', 0)\n\n return rows\n}\n\n/**\n * 종류 뷰 — `group` 이 있는 항목만 묶는다.\n *\n * `group` 이 없는 항목(계층 단계 같은 것)은 **종류가 아니므로** 여기 나오지 않는다. 그것들은 계층 뷰에서\n * 고른다. 묶음 이름순으로 낸다.\n */\nexport function toPlacementGroups(items: readonly BoardPlacementItem[]): { group: string; items: BoardPlacementItem[] }[] {\n const by = new Map<string, BoardPlacementItem[]>()\n for (const it of items) {\n if (!it.group) continue\n if (!by.has(it.group)) by.set(it.group, [])\n by.get(it.group)!.push(it)\n }\n\n return [...by.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([group, items]) => ({ group, items }))\n}\n\n/** 두 뷰를 쓸 수 있는 목록인가 — 호스트가 소속·종류를 주지 않으면 예전처럼 평평하게 보여 준다. */\nexport function hasHierarchy(items: readonly BoardPlacementItem[]): boolean {\n return items.some(i => !!i.parentId)\n}\n\nexport function hasGroups(items: readonly BoardPlacementItem[]): boolean {\n return items.some(i => !!i.group)\n}\n"]}
1
+ {"version":3,"file":"board-placement-view.js","sourceRoot":"","sources":["../client/board-placement-view.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 배치 대상 목록을 **두 가지로 보는** 방법 — 순수.\n *\n * 호스트는 항목만 공급하고(`BoardPlacementItem`), 어떻게 볼지는 보드 층이 정한다. 항목에 `parentId`\n * (소속)와 `group`(종류)이 달려 있으면 두 뷰가 성립한다.\n *\n * 계층 뷰 소속 트리 — \"이 자리에 무엇을 놓을까\". 현장 감각으로 찾는다.\n * 종류 뷰 같은 종류끼리 묶음 — \"같은 것 여럿에 한 번에\". 대량 저작의 단위다.\n *\n * 둘 다 **항목을 잃지 않는다.** 목록에서 조용히 빠지면 저작하는 사람은 \"그런 게 없네\" 로 끝난다 —\n * 오류가 아니라서 아무도 모른다.\n */\nimport type { BoardPlacementItem } from '@operato/board/board-placement-palette.js'\n\nexport interface PlacementRow {\n item: BoardPlacementItem\n /** 들여쓰기 깊이. 화면은 이 값만 곱하면 된다. */\n depth: number\n /** 길을 보여 주려고 끼운 조상 — 그 자체는 놓을 것이 없다. */\n context?: boolean\n}\n\n/**\n * 계층 뷰 — 부모 순서로 늘어놓고 깊이를 함께 준다.\n *\n * `keep` 을 주면 그 항목들만 남기되 **조상은 길로 함께 남긴다**(맥락). 조상을 빼면 트리가 끊겨 어디에\n * 속한 것인지 알 수 없다. 부모를 못 찾은 항목은 버리지 않고 최상위로 올린다.\n */\nexport function toPlacementTree(items: readonly BoardPlacementItem[], keep?: (item: BoardPlacementItem) => boolean): PlacementRow[] {\n const byId = new Map(items.map(i => [i.id, i]))\n const parentOf = (i: BoardPlacementItem) => (i.parentId && byId.has(i.parentId) ? i.parentId : '')\n\n /* 남길 것 + 그 조상들. 조상은 맥락으로 표시해 \"놓을 것이 있는 항목\" 과 구별한다. */\n let visible: Set<string> | null = null\n const context = new Set<string>()\n if (keep) {\n visible = new Set(items.filter(keep).map(i => i.id))\n for (const id of [...visible]) {\n let p = parentOf(byId.get(id)!)\n while (p && !visible.has(p) && !context.has(p)) {\n context.add(p)\n p = parentOf(byId.get(p)!)\n }\n }\n }\n\n const children = new Map<string, BoardPlacementItem[]>()\n for (const it of items) {\n if (visible && !visible.has(it.id) && !context.has(it.id)) continue\n const p = parentOf(it)\n if (!children.has(p)) children.set(p, [])\n children.get(p)!.push(it)\n }\n\n const rows: PlacementRow[] = []\n const walk = (parent: string, depth: number) => {\n for (const it of children.get(parent) ?? []) {\n rows.push({ item: it, depth, ...(context.has(it.id) ? { context: true } : {}) })\n walk(it.id, depth + 1)\n }\n }\n walk('', 0)\n\n return rows\n}\n\n/**\n * 종류 뷰 — `group` 이 있는 항목만 묶는다.\n *\n * `group` 이 없는 항목(계층 단계 같은 것)은 **종류가 아니므로** 여기 나오지 않는다. 그것들은 계층 뷰에서\n * 고른다. 묶음 이름순으로 낸다.\n */\nexport function toPlacementGroups(items: readonly BoardPlacementItem[]): { group: string; items: BoardPlacementItem[] }[] {\n const by = new Map<string, BoardPlacementItem[]>()\n for (const it of items) {\n if (!it.group) continue\n if (!by.has(it.group)) by.set(it.group, [])\n by.get(it.group)!.push(it)\n }\n\n return [...by.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([group, items]) => ({ group, items }))\n}\n\n/** 두 뷰를 쓸 수 있는 목록인가 — 호스트가 소속·종류를 주지 않으면 예전처럼 평평하게 보여 준다. */\nexport function hasHierarchy(items: readonly BoardPlacementItem[]): boolean {\n return items.some(i => !!i.parentId)\n}\n\nexport function hasGroups(items: readonly BoardPlacementItem[]): boolean {\n return items.some(i => !!i.group)\n}\n"]}
@@ -1,9 +1,6 @@
1
- export * from './board-provider.js';
2
1
  export * from './pages/board-viewer-page.js';
3
2
  export * from './pages/board-player-page.js';
4
3
  export * from './pages/board-modeller-page.js';
5
- export * from './pages/board-edit-dispatch.js';
6
- export * from './pages/board-action-dispatch.js';
7
4
  export * from './setting-let/board-view-setting-let.js';
8
5
  export * from './apptools/favorite-tool.js';
9
6
  export * from './graphql/index.js';
@@ -1,11 +1,8 @@
1
- export * from './board-provider.js';
2
1
  export * from './pages/board-viewer-page.js';
3
2
  export * from './pages/board-player-page.js';
4
3
  export * from './pages/board-modeller-page.js';
5
4
  /* AI 패치/액션의 things-scene in-place 적용기 — 순수 함수. 자체 모델러 셸을 가진 호스트
6
5
  * (예: operato-twin 의 트윈 모델러)가 같은 적용 규칙을 쓰도록 공개한다(중복 구현 금지). */
7
- export * from './pages/board-edit-dispatch.js';
8
- export * from './pages/board-action-dispatch.js';
9
6
  export * from './setting-let/board-view-setting-let.js';
10
7
  export * from './apptools/favorite-tool.js';
11
8
  export * from './graphql/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AAEnC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C;+DAC+D;AAC/D,cAAc,gCAAgC,CAAA;AAC9C,cAAc,kCAAkC,CAAA;AAEhD,cAAc,yCAAyC,CAAA;AACvD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA","sourcesContent":["export * from './board-provider.js'\n\nexport * from './pages/board-viewer-page.js'\nexport * from './pages/board-player-page.js'\nexport * from './pages/board-modeller-page.js'\n/* AI 패치/액션의 things-scene in-place 적용기 — 순수 함수. 자체 모델러 셸을 가진 호스트\n * (예: operato-twin 의 트윈 모델러)가 같은 적용 규칙을 쓰도록 공개한다(중복 구현 금지). */\nexport * from './pages/board-edit-dispatch.js'\nexport * from './pages/board-action-dispatch.js'\n\nexport * from './setting-let/board-view-setting-let.js'\nexport * from './apptools/favorite-tool.js'\nexport * from './graphql/index.js'\nexport * from './viewparts/index.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AACA,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C;+DAC+D;AAE/D,cAAc,yCAAyC,CAAA;AACvD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA","sourcesContent":["\nexport * from './pages/board-viewer-page.js'\nexport * from './pages/board-player-page.js'\nexport * from './pages/board-modeller-page.js'\n/* AI 패치/액션의 things-scene in-place 적용기 — 순수 함수. 자체 모델러 셸을 가진 호스트\n * (예: operato-twin 의 트윈 모델러)가 같은 적용 규칙을 쓰도록 공개한다(중복 구현 금지). */\n\nexport * from './setting-let/board-view-setting-let.js'\nexport * from './apptools/favorite-tool.js'\nexport * from './graphql/index.js'\nexport * from './viewparts/index.js'\n"]}
@@ -4,8 +4,8 @@ import '@operato/oops';
4
4
  import '@things-factory/board-ai';
5
5
  import { BoardModeller } from '@operato/board/ox-board-modeller.js';
6
6
  import { PageView } from '@operato/shell';
7
+ import { findSceneComponent } from '@operato/scene-ops';
7
8
  import '../board-placement-tray.js';
8
- import { findSceneComponent } from './board-action-dispatch.js';
9
9
  export { findSceneComponent };
10
10
  export declare class BoardModellerPage extends PageView {
11
11
  constructor();
@@ -128,7 +128,7 @@ export declare class BoardModellerPage extends PageView {
128
128
  * 'replace' op (보드 전체 교체) 만은 부득이 wholesale 경로로 빠진다 — 이 경우는
129
129
  * 사용자에게 명시적으로 안내 후 진행.
130
130
  */
131
- private onBoardEditPatch;
131
+ private onSceneEditPatch;
132
132
  /**
133
133
  * 'replace' op 적용 — scene 통째로 재생성 (undo 히스토리 초기화).
134
134
  *
@@ -142,7 +142,7 @@ export declare class BoardModellerPage extends PageView {
142
142
  private _unsubscribeBoardPatches;
143
143
  /**
144
144
  * 원격 patch 도착 — apply-once 게이트 통과 시 in-place 적용. 자기발신 echo 는 게이트가 차단
145
- * (로컬 onBoardEditPatch 가 이미 같은 patchId 를 claim). 원격분은 revert 인버스 미보관(프로토타입).
145
+ * (로컬 onSceneEditPatch 가 이미 같은 patchId 를 claim). 원격분은 revert 인버스 미보관(프로토타입).
146
146
  */
147
147
  private _onRemotePatch;
148
148
  /**
@@ -164,10 +164,10 @@ export declare class BoardModellerPage extends PageView {
164
164
  /**
165
165
  * AI 가 ephemeral scene action (selection / view / mode) 을 실행하라고 요청.
166
166
  *
167
- * board-edit-patch 와 별개 채널 — 모델 변경 X, undo 영향 X, dirty flag 무관.
167
+ * scene-edit-patch 와 별개 채널 — 모델 변경 X, undo 영향 X, dirty flag 무관.
168
168
  * things-scene API 직접 호출. 잘못된 action / 누락된 컴포넌트는 console.warn 만.
169
169
  */
170
- private onBoardActionExecute;
170
+ private onSceneActionExecute;
171
171
  /**
172
172
  * AI 가 만든 컴포넌트를 things-scene 이 기대하는 완전한 형태로 normalize.
173
173
  * BoardModeller 에 등록된 template.model (default 값) 위에 AI 응답을 덮어쓴다.
@@ -11,17 +11,15 @@ import { LoadTracker, SceneSearchEngine } from '@hatiolab/things-scene';
11
11
  import { OxPropertyEditor } from '@operato/property-editor';
12
12
  import { PageView, FontController } from '@operato/shell';
13
13
  import { hasPrivilege } from '@things-factory/auth-base/dist-client/index.js';
14
- import { applyBoardEditPatchVerbose } from '@things-factory/board-ai';
14
+ import { applyScenePatchVerbose, dispatchSceneAction, dispatchSceneEditOp, findSceneComponent } from '@operato/scene-ops';
15
15
  import { client, gqlContext, subscribe } from '@operato/graphql';
16
16
  import { i18next } from '@operato/i18n';
17
17
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
18
- import { provider } from '../board-provider.js';
18
+ import { provider } from '@operato/board/board-provider.js';
19
19
  import '../board-placement-tray.js'; // 배치 팔레트 오버레이(호스트-등록 provider 있을 때만 표시)
20
- import { boardPlacementProvider } from '../board-placement-palette.js';
20
+ import { boardPlacementProvider } from '@operato/board/board-placement-palette.js';
21
21
  import components from 'things-scene-components-with-tools';
22
22
  import { notify, notifyError } from '../utils/notify-helper.js';
23
- import { findSceneComponent, dispatchBoardAction } from './board-action-dispatch.js';
24
- import { dispatchBoardEditOp } from './board-edit-dispatch.js';
25
23
  import { PatchApplyGate } from '../utils/patch-apply-gate.js';
26
24
  import { diffBoardModel } from '../utils/board-model-diff.js';
27
25
  export { findSceneComponent };
@@ -206,7 +204,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
206
204
  * 'replace' op (보드 전체 교체) 만은 부득이 wholesale 경로로 빠진다 — 이 경우는
207
205
  * 사용자에게 명시적으로 안내 후 진행.
208
206
  */
209
- this.onBoardEditPatch = (e) => {
207
+ this.onSceneEditPatch = (e) => {
210
208
  const { patch, patchId } = e.detail || {};
211
209
  if (!patch)
212
210
  return;
@@ -230,7 +228,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
230
228
  const inverseOps = [];
231
229
  const normalize = (c) => this.normalizeComponent(c);
232
230
  for (const op of patch.ops) {
233
- const r = dispatchBoardEditOp(scene, op, { normalize });
231
+ const r = dispatchSceneEditOp(scene, op, { normalize });
234
232
  if (r.applied) {
235
233
  inverseOps.push(...r.inverseOps);
236
234
  }
@@ -247,7 +245,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
247
245
  .map((op) => {
248
246
  if (op.op === 'modify' || op.op === 'remove')
249
247
  return `refid=${op.refid}`;
250
- if (op.op === 'modifyBoard')
248
+ if (op.op === 'modifyScene')
251
249
  return 'modifyBoard (scene root unavailable)';
252
250
  return op.op;
253
251
  })
@@ -290,7 +288,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
290
288
  summary: 'revert',
291
289
  confidence: 1
292
290
  };
293
- // hasReplace 면 wholesale, 그렇지 않으면 in-place — 일반 onBoardEditPatch 와 동일
291
+ // hasReplace 면 wholesale, 그렇지 않으면 in-place — 일반 onSceneEditPatch 와 동일
294
292
  const hasReplace = reversed.some(op => op.op === 'replace');
295
293
  if (hasReplace) {
296
294
  this.applyPatchWholesale(reverseFakePatch);
@@ -309,10 +307,10 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
309
307
  /**
310
308
  * AI 가 ephemeral scene action (selection / view / mode) 을 실행하라고 요청.
311
309
  *
312
- * board-edit-patch 와 별개 채널 — 모델 변경 X, undo 영향 X, dirty flag 무관.
310
+ * scene-edit-patch 와 별개 채널 — 모델 변경 X, undo 영향 X, dirty flag 무관.
313
311
  * things-scene API 직접 호출. 잘못된 action / 누락된 컴포넌트는 console.warn 만.
314
312
  */
315
- this.onBoardActionExecute = (e) => {
313
+ this.onSceneActionExecute = (e) => {
316
314
  const { actions } = e.detail || {};
317
315
  if (!Array.isArray(actions) || actions.length === 0)
318
316
  return;
@@ -322,7 +320,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
322
320
  let modeChanged = false;
323
321
  for (const action of actions) {
324
322
  try {
325
- dispatchBoardAction(scene, action);
323
+ dispatchSceneAction(scene, action);
326
324
  if (action?.action === 'setSceneMode')
327
325
  modeChanged = true;
328
326
  }
@@ -699,10 +697,10 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
699
697
  try {
700
698
  const baseBoard = this.getLiveBoard();
701
699
  const inverseOps = baseBoard
702
- ? [{ op: 'replace', board: JSON.parse(JSON.stringify(baseBoard)) }]
700
+ ? [{ op: 'replace', model: JSON.parse(JSON.stringify(baseBoard)) }]
703
701
  : [];
704
- const report = applyBoardEditPatchVerbose(baseBoard, patch);
705
- const next = report.board;
702
+ const report = applyScenePatchVerbose(baseBoard, patch);
703
+ const next = report.model;
706
704
  if (report.applied.length === 0)
707
705
  return;
708
706
  const normalized = (next.components || []).map((c) => this.normalizeComponent(c));
@@ -755,14 +753,14 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
755
753
  }
756
754
  /**
757
755
  * 원격 patch 도착 — apply-once 게이트 통과 시 in-place 적용. 자기발신 echo 는 게이트가 차단
758
- * (로컬 onBoardEditPatch 가 이미 같은 patchId 를 claim). 원격분은 revert 인버스 미보관(프로토타입).
756
+ * (로컬 onSceneEditPatch 가 이미 같은 patchId 를 claim). 원격분은 revert 인버스 미보관(프로토타입).
759
757
  */
760
758
  _onRemotePatch(entry) {
761
759
  if (!entry?.id)
762
760
  return;
763
761
  if (!this._patchGate.tryClaim(entry.id))
764
762
  return;
765
- // server PatchEntry.opsJson 은 GraphQLJSON — 이미 parsed BoardEditOp[] 로 도착(추가 parse 불필요).
763
+ // server PatchEntry.opsJson 은 GraphQLJSON — 이미 parsed SceneEditOp[] 로 도착(추가 parse 불필요).
766
764
  const ops = Array.isArray(entry.opsJson) ? entry.opsJson : [];
767
765
  if (!ops.length)
768
766
  return;
@@ -785,7 +783,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
785
783
  continue;
786
784
  }
787
785
  try {
788
- dispatchBoardEditOp(scene, op, { normalize });
786
+ dispatchSceneEditOp(scene, op, { normalize });
789
787
  }
790
788
  catch (ex) {
791
789
  notifyError(ex);
@@ -800,7 +798,7 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
800
798
  applyInverseOpsInPlace(scene, ops) {
801
799
  const normalize = (c) => this.normalizeComponent(c);
802
800
  for (const op of ops) {
803
- dispatchBoardEditOp(scene, op, { normalize });
801
+ dispatchSceneEditOp(scene, op, { normalize });
804
802
  }
805
803
  }
806
804
  /** 서버 PatchEntry.reverted=true 영속 — fail 해도 UI 는 이미 되돌렸으니 warn 만. */
@@ -942,9 +940,9 @@ let BoardModellerPage = class BoardModellerPage extends PageView {
942
940
  .selectedRefids=${this.extractSelectedRefids()}
943
941
  @session-switch=${this.onChatSessionSwitch}
944
942
  @session-create=${this.onChatSessionCreate}
945
- @board-edit-patch=${this.onBoardEditPatch}
943
+ @scene-edit-patch=${this.onSceneEditPatch}
946
944
  @board-edit-revert=${this.onBoardEditRevert}
947
- @board-action-execute=${this.onBoardActionExecute}>
945
+ @scene-action-execute=${this.onSceneActionExecute}>
948
946
  </ox-assistant-chat>
949
947
  </div>
950
948
  `