@things-factory/board-ai 10.0.0-beta.64 → 10.0.0-beta.66
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.
- package/PLAN-styling-tools.md +1253 -0
- package/client/components/board-ai-chat.test.ts +29 -0
- package/client/components/board-ai-chat.ts +842 -71
- package/client/components/chat-input-builder.ts +8 -1
- package/client/components/default-catalog-entries.test.ts +59 -0
- package/client/components/default-catalog-entries.ts +100 -0
- package/client/components/default-slash-templates.test.ts +55 -0
- package/client/components/default-slash-templates.ts +69 -0
- package/client/components/markdown.test.ts +299 -1
- package/client/components/markdown.ts +332 -8
- package/client/components/mention-popup-helpers.ts +433 -0
- package/client/components/mention-popup.test.ts +588 -0
- package/client/components/mention-popup.ts +238 -0
- package/client/utils/board-edit-patch.ts +187 -13
- package/dist-client/client/components/board-ai-chat.d.ts +241 -0
- package/dist-client/{components → client/components}/board-ai-chat.js +769 -70
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/{components → client/components}/board-ai-chat.test.js +26 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/{components → client/components}/chat-input-builder.d.ts +9 -0
- package/dist-client/{components → client/components}/chat-input-builder.js +2 -1
- package/dist-client/client/components/chat-input-builder.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
- package/dist-client/client/components/default-catalog-entries.js +86 -0
- package/dist-client/client/components/default-catalog-entries.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.test.js +52 -0
- package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.d.ts +17 -0
- package/dist-client/client/components/default-slash-templates.js +53 -0
- package/dist-client/client/components/default-slash-templates.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
- package/dist-client/client/components/default-slash-templates.test.js +51 -0
- package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
- package/dist-client/client/components/markdown.d.ts +60 -0
- package/dist-client/client/components/markdown.js +422 -0
- package/dist-client/client/components/markdown.js.map +1 -0
- package/dist-client/client/components/markdown.test.d.ts +1 -0
- package/dist-client/client/components/markdown.test.js +418 -0
- package/dist-client/client/components/markdown.test.js.map +1 -0
- package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
- package/dist-client/client/components/mention-popup-helpers.js +330 -0
- package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
- package/dist-client/client/components/mention-popup.d.ts +35 -0
- package/dist-client/client/components/mention-popup.js +223 -0
- package/dist-client/client/components/mention-popup.js.map +1 -0
- package/dist-client/client/components/mention-popup.test.d.ts +1 -0
- package/dist-client/client/components/mention-popup.test.js +474 -0
- package/dist-client/client/components/mention-popup.test.js.map +1 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/{utils → client/utils}/board-edit-patch.d.ts +84 -0
- package/dist-client/client/utils/board-edit-patch.js +284 -0
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/agentic-loop.d.ts +87 -0
- package/dist-client/server/service/agentic-loop.js +274 -0
- package/dist-client/server/service/agentic-loop.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +84 -0
- package/dist-client/server/service/assistant.js +2158 -0
- package/dist-client/server/service/assistant.js.map +1 -0
- package/dist-client/server/service/catalog-resolver.d.ts +18 -0
- package/dist-client/server/service/catalog-resolver.js +93 -0
- package/dist-client/server/service/catalog-resolver.js.map +1 -0
- package/dist-client/server/service/component-tree.d.ts +68 -0
- package/dist-client/server/service/component-tree.js +68 -0
- package/dist-client/server/service/component-tree.js.map +1 -0
- package/dist-client/server/service/mention-resolver.d.ts +96 -0
- package/dist-client/server/service/mention-resolver.js +172 -0
- package/dist-client/server/service/mention-resolver.js.map +1 -0
- package/dist-client/server/service/prefab-registry.d.ts +73 -0
- package/dist-client/server/service/prefab-registry.js +252 -0
- package/dist-client/server/service/prefab-registry.js.map +1 -0
- package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
- package/dist-client/server/service/styling/clear-tools.js +43 -0
- package/dist-client/server/service/styling/clear-tools.js.map +1 -0
- package/dist-client/server/service/styling/color-utils.d.ts +43 -0
- package/dist-client/server/service/styling/color-utils.js +205 -0
- package/dist-client/server/service/styling/color-utils.js.map +1 -0
- package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
- package/dist-client/server/service/styling/copy-tools.js +78 -0
- package/dist-client/server/service/styling/copy-tools.js.map +1 -0
- package/dist-client/server/service/styling/effect-tools.d.ts +44 -0
- package/dist-client/server/service/styling/effect-tools.js +77 -0
- package/dist-client/server/service/styling/effect-tools.js.map +1 -0
- package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
- package/dist-client/server/service/styling/fill-tools.js +226 -0
- package/dist-client/server/service/styling/fill-tools.js.map +1 -0
- package/dist-client/server/service/styling/material-tools.d.ts +32 -0
- package/dist-client/server/service/styling/material-tools.js +88 -0
- package/dist-client/server/service/styling/material-tools.js.map +1 -0
- package/dist-client/server/service/styling/read-tools.d.ts +34 -0
- package/dist-client/server/service/styling/read-tools.js +42 -0
- package/dist-client/server/service/styling/read-tools.js.map +1 -0
- package/dist-client/server/service/styling/registry.d.ts +91 -0
- package/dist-client/server/service/styling/registry.js +345 -0
- package/dist-client/server/service/styling/registry.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-client/server/service/styling/stroke-tools.js +77 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +31 -0
- package/dist-client/server/service/styling/text-tools.js +69 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +254 -0
- package/dist-client/server/service/types.js +2 -0
- package/dist-client/server/service/types.js.map +1 -0
- package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-client/server/service/validation/board-model-schema.js +402 -0
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
- package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
- package/dist-client/server/service/validation/tool-validation.js +193 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/agentic-loop.d.ts +87 -0
- package/dist-server/service/agentic-loop.js +278 -0
- package/dist-server/service/agentic-loop.js.map +1 -0
- package/dist-server/service/apply-patch.d.ts +1 -0
- package/dist-server/service/apply-patch.js +173 -15
- package/dist-server/service/apply-patch.js.map +1 -1
- package/dist-server/service/assistant.d.ts +24 -15
- package/dist-server/service/assistant.js +1133 -256
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.d.ts +6 -0
- package/dist-server/service/board-ai-resolver.js +62 -5
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/catalog-resolver.d.ts +18 -0
- package/dist-server/service/catalog-resolver.js +98 -0
- package/dist-server/service/catalog-resolver.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +32 -0
- package/dist-server/service/chat-message/chat-message.js +42 -1
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +10 -1
- package/dist-server/service/chat-session/chat-session.js +38 -16
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
- package/dist-server/service/chat-session-participant/index.d.ts +3 -0
- package/dist-server/service/chat-session-participant/index.js +7 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +7 -1
- package/dist-server/service/chat-session-resolver.js +155 -7
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/component-tree.d.ts +68 -0
- package/dist-server/service/component-tree.js +73 -0
- package/dist-server/service/component-tree.js.map +1 -0
- package/dist-server/service/index.d.ts +5 -1
- package/dist-server/service/index.js +8 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/mention-marker.d.ts +67 -0
- package/dist-server/service/mention-marker.js +121 -0
- package/dist-server/service/mention-marker.js.map +1 -0
- package/dist-server/service/mention-resolver.d.ts +96 -0
- package/dist-server/service/mention-resolver.js +177 -0
- package/dist-server/service/mention-resolver.js.map +1 -0
- package/dist-server/service/prefab-registry.d.ts +73 -0
- package/dist-server/service/prefab-registry.js +258 -0
- package/dist-server/service/prefab-registry.js.map +1 -0
- package/dist-server/service/styling/clear-tools.d.ts +15 -0
- package/dist-server/service/styling/clear-tools.js +46 -0
- package/dist-server/service/styling/clear-tools.js.map +1 -0
- package/dist-server/service/styling/color-utils.d.ts +43 -0
- package/dist-server/service/styling/color-utils.js +210 -0
- package/dist-server/service/styling/color-utils.js.map +1 -0
- package/dist-server/service/styling/copy-tools.d.ts +27 -0
- package/dist-server/service/styling/copy-tools.js +81 -0
- package/dist-server/service/styling/copy-tools.js.map +1 -0
- package/dist-server/service/styling/effect-tools.d.ts +44 -0
- package/dist-server/service/styling/effect-tools.js +82 -0
- package/dist-server/service/styling/effect-tools.js.map +1 -0
- package/dist-server/service/styling/fill-tools.d.ts +60 -0
- package/dist-server/service/styling/fill-tools.js +229 -0
- package/dist-server/service/styling/fill-tools.js.map +1 -0
- package/dist-server/service/styling/material-tools.d.ts +32 -0
- package/dist-server/service/styling/material-tools.js +92 -0
- package/dist-server/service/styling/material-tools.js.map +1 -0
- package/dist-server/service/styling/read-tools.d.ts +34 -0
- package/dist-server/service/styling/read-tools.js +45 -0
- package/dist-server/service/styling/read-tools.js.map +1 -0
- package/dist-server/service/styling/registry.d.ts +91 -0
- package/dist-server/service/styling/registry.js +352 -0
- package/dist-server/service/styling/registry.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-server/service/styling/stroke-tools.js +80 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +31 -0
- package/dist-server/service/styling/text-tools.js +73 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/types.d.ts +120 -3
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-server/service/validation/board-model-schema.js +409 -0
- package/dist-server/service/validation/board-model-schema.js.map +1 -0
- package/dist-server/service/validation/tool-validation.d.ts +31 -0
- package/dist-server/service/validation/tool-validation.js +196 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/server/service/agentic-loop.test.ts +705 -0
- package/server/service/agentic-loop.ts +428 -0
- package/server/service/apply-patch-drift.test.ts +829 -0
- package/server/service/apply-patch.test.ts +122 -5
- package/server/service/apply-patch.ts +180 -19
- package/server/service/assistant-integration.test.ts +922 -0
- package/server/service/assistant-llm-smoke.test.ts +296 -0
- package/server/service/assistant.test.ts +817 -104
- package/server/service/assistant.ts +1172 -293
- package/server/service/board-ai-resolver.ts +64 -5
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message.ts +61 -2
- package/server/service/chat-session/chat-session.ts +40 -13
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +5 -0
- package/server/service/chat-session-resolver.ts +141 -7
- package/server/service/component-tree.test.ts +248 -0
- package/server/service/component-tree.ts +135 -0
- package/server/service/index.ts +6 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +142 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.test.ts +178 -0
- package/server/service/styling/clear-tools.ts +62 -0
- package/server/service/styling/color-utils.test.ts +130 -0
- package/server/service/styling/color-utils.ts +226 -0
- package/server/service/styling/copy-tools.test.ts +152 -0
- package/server/service/styling/copy-tools.ts +115 -0
- package/server/service/styling/effect-tools.test.ts +259 -0
- package/server/service/styling/effect-tools.ts +116 -0
- package/server/service/styling/fill-tools.test.ts +331 -0
- package/server/service/styling/fill-tools.ts +293 -0
- package/server/service/styling/material-tools.test.ts +294 -0
- package/server/service/styling/material-tools.ts +114 -0
- package/server/service/styling/read-tools.test.ts +78 -0
- package/server/service/styling/read-tools.ts +67 -0
- package/server/service/styling/registry.test.ts +203 -0
- package/server/service/styling/registry.ts +423 -0
- package/server/service/styling/stroke-tools.test.ts +231 -0
- package/server/service/styling/stroke-tools.ts +127 -0
- package/server/service/styling/text-tools.test.ts +235 -0
- package/server/service/styling/text-tools.ts +97 -0
- package/server/service/styling/tier1-tools.test.ts +299 -0
- package/server/service/types.ts +89 -3
- package/server/service/validation/board-model-schema.test.ts +778 -0
- package/server/service/validation/board-model-schema.ts +446 -0
- package/server/service/validation/tool-validation.ts +228 -0
- package/translations/en.json +12 -4
- package/translations/ja.json +11 -3
- package/translations/ko.json +12 -4
- package/translations/ms.json +11 -3
- package/translations/zh.json +11 -3
- package/dist-client/components/board-ai-chat.d.ts +0 -127
- package/dist-client/components/board-ai-chat.js.map +0 -1
- package/dist-client/components/board-ai-chat.test.js.map +0 -1
- package/dist-client/components/chat-input-builder.js.map +0 -1
- package/dist-client/components/markdown.d.ts +0 -16
- package/dist-client/components/markdown.js +0 -167
- package/dist-client/components/markdown.js.map +0 -1
- package/dist-client/components/markdown.test.js +0 -187
- package/dist-client/components/markdown.test.js.map +0 -1
- package/dist-client/index.js.map +0 -1
- package/dist-client/utils/board-edit-patch.js +0 -159
- package/dist-client/utils/board-edit-patch.js.map +0 -1
- /package/dist-client/{components → client/components}/board-ai-chat.test.d.ts +0 -0
- /package/dist-client/{components/markdown.test.d.ts → client/components/default-catalog-entries.test.d.ts} +0 -0
- /package/dist-client/{index.d.ts → client/index.d.ts} +0 -0
- /package/dist-client/{index.js → client/index.js} +0 -0
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
# Component Styling Tools — Plan
|
|
2
|
+
|
|
3
|
+
> **Goal**: AI 어시스턴트가 컴포넌트의 2D / 3D 스타일링을 델리키트하고 정확하게
|
|
4
|
+
> 처리하도록 — fillStyle (4 modes), strokeStyle, text/font, shadow, 3D material,
|
|
5
|
+
> texture 까지 보편 속성 모두 다루는 tool 군 설계 + 구현.
|
|
6
|
+
>
|
|
7
|
+
> 스타일링 품질 = AI 서포트 효용성 + 생산성 직결. AI 가 "원샷에 모드 전환 + 색
|
|
8
|
+
> 변경 + 부분 업데이트" 같은 케이스를 자연스럽게 처리해야 한다.
|
|
9
|
+
>
|
|
10
|
+
> **Status**: planning (작성 2026-04-28). 단계별 구현 진행.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. 스타일 표면 inventory
|
|
15
|
+
|
|
16
|
+
things-scene 의 styling 표면 audit 결과. 각 인터페이스가 그대로 컴포넌트 state 에
|
|
17
|
+
존재 (e.g., `state.fillStyle` / `state.strokeStyle` / `state.fontFamily` / `state.material3d`).
|
|
18
|
+
|
|
19
|
+
### 1.1 `FillStyle` — 4 modes (`src/interfaces/rendering.ts:32`)
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
interface FillStyle extends StyleOptions {
|
|
23
|
+
type: 'solid' | 'gradient' | 'pattern' | 'image'
|
|
24
|
+
color?: string
|
|
25
|
+
opacity?: number
|
|
26
|
+
// gradient extras
|
|
27
|
+
gradientType?: string // 'linear' | 'radial'
|
|
28
|
+
rotation?: number
|
|
29
|
+
colors?: string[]
|
|
30
|
+
stops?: number[]
|
|
31
|
+
// pattern/image extras
|
|
32
|
+
image?: string // URL or asset ref
|
|
33
|
+
// 그 외 모드별 임의 키 (additionalProperties: true)
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Modes (사용자 의도)**:
|
|
38
|
+
- **none**: `state.fillStyle` undefined / null / `{type:'none'}` — 배경 없음
|
|
39
|
+
- **solid**: `{type:'solid', color, opacity?}` — 단색
|
|
40
|
+
- **gradient**: `{type:'gradient', gradientType:'linear'|'radial', colors:[], stops:[], rotation?, opacity?}`
|
|
41
|
+
- **pattern**: `{type:'pattern', image, opacity?, repeat?, ...}` — 이미지 반복
|
|
42
|
+
- **image**: `{type:'image', image, ...}` — 이미지 1회 (stretch/contain)
|
|
43
|
+
|
|
44
|
+
### 1.2 `StrokeStyle` (`src/interfaces/rendering.ts:41`)
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface StrokeStyle {
|
|
48
|
+
width?: number
|
|
49
|
+
color?: string
|
|
50
|
+
opacity?: number
|
|
51
|
+
lineCap?: 'butt' | 'round' | 'square'
|
|
52
|
+
lineJoin?: 'miter' | 'round' | 'bevel'
|
|
53
|
+
lineDash?: number[] // [] = solid, [4,4] = dashed, [1,4] = dotted...
|
|
54
|
+
lineDashOffset?: number
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Dash presets (시멘틱)**: solid / dashed / dotted / dashdot — 사용자 의도 → 배열 변환.
|
|
59
|
+
|
|
60
|
+
### 1.3 `TextStyle` (`src/interfaces/rendering.ts:54`)
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
interface TextStyle {
|
|
64
|
+
font?: string // shorthand
|
|
65
|
+
fontSize?: number
|
|
66
|
+
fontColor?: string
|
|
67
|
+
fontFamily?: string
|
|
68
|
+
fontStyle?: string // 'normal' | 'italic' | 'oblique'
|
|
69
|
+
fontWeight?: string // 'normal' | 'bold' | '100'..'900'
|
|
70
|
+
textAlign?: 'left' | 'center' | 'right' | 'start' | 'end'
|
|
71
|
+
textBaseline?: 'top' | 'middle' | 'bottom' | 'hanging' | 'alphabetic' | 'ideographic'
|
|
72
|
+
lineHeight?: number
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**참고**: text content (`state.text`) 는 별도 필드. font 스타일과 분리 처리.
|
|
77
|
+
|
|
78
|
+
### 1.4 `Shadow` (`src/interfaces/rendering.ts:69`)
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
interface Shadow {
|
|
82
|
+
color?: string
|
|
83
|
+
offsetX?: number
|
|
84
|
+
offsetY?: number
|
|
85
|
+
blur?: number
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 1.5 `Material3D` (`src/threed/material-3d.ts`)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface Material3D {
|
|
93
|
+
preset?: 'default' | 'metal' | 'glass' | 'plastic' | 'wood' | 'ceramic' | 'rubber' | 'custom'
|
|
94
|
+
metalness?: number // 0..1
|
|
95
|
+
roughness?: number // 0..1
|
|
96
|
+
emissive?: string // 발광색 hex
|
|
97
|
+
emissiveIntensity?: number // 0..5
|
|
98
|
+
opacity?: number // 0..1 (component alpha 와 독립)
|
|
99
|
+
envMapIntensity?: number // 0..3 환경맵 반사 강도
|
|
100
|
+
side?: 'double' | 'front' | 'back'
|
|
101
|
+
castShadow?: boolean // default true
|
|
102
|
+
receiveShadow?: boolean // default false
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Preset 베이스 + override**: `resolveMaterial3d(material)` 함수가 preset 의 default 위에
|
|
107
|
+
명시 속성 덮어쓰는 구조. preset 만 주면 그 시맨틱 재질, 개별 속성 추가 시 fine-tune.
|
|
108
|
+
|
|
109
|
+
### 1.6 Texture / UV (3D)
|
|
110
|
+
|
|
111
|
+
`src/threed/texture/fillstyle-texture.ts:58` `buildPatternTexture` / `:180` `buildFillStyleTexture` —
|
|
112
|
+
2D fillStyle 을 3D mesh 의 texture 로 변환하는 시스템. UV offset/repeat 자동 계산.
|
|
113
|
+
|
|
114
|
+
**현재는 자동 처리** — AI 가 직접 UV 조작할 일 적음. Phase 후반에 manual 조정 tool 추가 가능.
|
|
115
|
+
|
|
116
|
+
### 1.7 Scene-level Lighting (`src/threed/managers/types.ts:19`)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface LightingConfig {
|
|
120
|
+
hemisphere: { skyColor, groundColor, intensity }
|
|
121
|
+
directional?: { color, intensity, attachToCamera?, position?, castShadow?, ... }
|
|
122
|
+
point?: { color, intensity, decay, power, castShadow, position, ... }
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
씬 전체 조명 — 별도 phase (Environment + Light Mood 프리셋이 이미 있음, 메모리
|
|
127
|
+
[project_preset_architecture.md] 참고).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 2. 설계 철학
|
|
132
|
+
|
|
133
|
+
### 2.1 8가지 핵심 원칙
|
|
134
|
+
|
|
135
|
+
| # | 원칙 | 실천 |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 1 | **Mode-aware** | 'solid' → 'gradient' 같은 모드 전환 시 fillStyle 통째 교체. partial merge X. |
|
|
138
|
+
| 2 | **Idempotent** | 같은 입력 반복 = 같은 결과. 누적 X. inverse op 정확. |
|
|
139
|
+
| 3 | **Partial-update friendly** | "투명도만" / "stroke 색만" 자연스럽게. 다른 속성 보존. |
|
|
140
|
+
| 4 | **Semantic-first** | `applyEmphasis('subtle')` / `applySemantic('warning')` 같은 시맨틱 우선. raw 는 fallback. |
|
|
141
|
+
| 5 | **Inverse 자동** | 모든 styling tool 이 inverse op 생성 → 기존 patch revert 시스템과 통합. |
|
|
142
|
+
| 6 | **Multi-target** | 한 번에 여러 컴포넌트 (refids 배열). |
|
|
143
|
+
| 7 | **Validation graceful** | 잘못된 색/숫자 → 명확한 에러 + 정정 제안 (AI 가 다시 시도 가능). |
|
|
144
|
+
| 8 | **Data-binding 인지** | 데이터 바인딩 적용 중 컴포넌트는 충돌 경고 (또는 unbind 후 적용 옵션). |
|
|
145
|
+
|
|
146
|
+
### 2.2 토큰 효율
|
|
147
|
+
|
|
148
|
+
AI 가 자주 쓰는 의도를 **짧은 호출** 로 표현 가능해야 한다. 예시:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
// 길고 모호 (BAD)
|
|
152
|
+
modifyComponentByRefid(refid=35, patch={
|
|
153
|
+
fillStyle: { type: 'solid', color: '#ff0000', opacity: 0.5 },
|
|
154
|
+
strokeStyle: { color: '#000000', width: 2, lineDash: [4, 4] }
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// 짧고 명확 (GOOD)
|
|
158
|
+
setFill(refids=[35], mode='solid', color='red', opacity=0.5)
|
|
159
|
+
setStroke(refids=[35], color='black', width=2, dash='dashed')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 2.3 시맨틱 Layer
|
|
163
|
+
|
|
164
|
+
다단계 추상화:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Tier 3: applySemantic('warning') — 의도 (Material 3 토큰)
|
|
168
|
+
Tier 2: applyStylePreset('card-elevated') — 명명된 스타일
|
|
169
|
+
Tier 1: setFill(mode='solid', color='red') — 구조화 raw
|
|
170
|
+
Tier 0: modifyComponentByRefid(...) — full raw (escape hatch)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
AI 는 의도 명확하면 Tier 3 / 2 사용, 정밀 제어 필요 시 Tier 1, 모르는 영역은 Tier 0.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 3. Tool 카탈로그
|
|
178
|
+
|
|
179
|
+
### 3.1 Tier 1 — Core Styling (10 tools)
|
|
180
|
+
|
|
181
|
+
#### `setFill`
|
|
182
|
+
컴포넌트 배경 (fillStyle) 설정. mode 명시 필수.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
input: {
|
|
186
|
+
refids: number[]
|
|
187
|
+
mode: 'none' | 'solid' | 'gradient' | 'pattern' | 'image'
|
|
188
|
+
// solid:
|
|
189
|
+
color?: string
|
|
190
|
+
// gradient:
|
|
191
|
+
gradientType?: 'linear' | 'radial' // default 'linear'
|
|
192
|
+
gradientStops?: Array<{ color: string; position: number }> // position 0..1
|
|
193
|
+
rotation?: number // degrees, default 0
|
|
194
|
+
// pattern/image:
|
|
195
|
+
image?: string // URL or asset ref
|
|
196
|
+
// 공통:
|
|
197
|
+
opacity?: number // 0..1
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Mode 전환 룰**: 새 mode 명시 시 fillStyle 통째 교체. 같은 mode 면 부분 merge.
|
|
202
|
+
|
|
203
|
+
#### `setStroke`
|
|
204
|
+
컴포넌트 테두리.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
input: {
|
|
208
|
+
refids: number[]
|
|
209
|
+
enabled?: boolean // false → strokeStyle 제거
|
|
210
|
+
color?: string
|
|
211
|
+
width?: number
|
|
212
|
+
dash?: 'solid' | 'dashed' | 'dotted' | 'dashdot' | number[] // preset → 배열 변환
|
|
213
|
+
lineCap?: 'butt' | 'round' | 'square'
|
|
214
|
+
lineJoin?: 'miter' | 'round' | 'bevel'
|
|
215
|
+
opacity?: number
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `setText`
|
|
220
|
+
텍스트 *내용* 변경. 폰트 스타일은 `setFont` 별개.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
input: {
|
|
224
|
+
refids: number[]
|
|
225
|
+
text: string
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### `setFont`
|
|
230
|
+
폰트 / 텍스트 스타일.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
input: {
|
|
234
|
+
refids: number[]
|
|
235
|
+
fontFamily?: string
|
|
236
|
+
fontSize?: number
|
|
237
|
+
fontWeight?: 'normal' | 'bold' | 'lighter' | 'bolder' | number
|
|
238
|
+
fontStyle?: 'normal' | 'italic' | 'oblique'
|
|
239
|
+
fontColor?: string
|
|
240
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify'
|
|
241
|
+
textBaseline?: 'top' | 'middle' | 'bottom'
|
|
242
|
+
lineHeight?: number
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### `setShadow`
|
|
247
|
+
그림자.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
input: {
|
|
251
|
+
refids: number[]
|
|
252
|
+
enabled?: boolean // false → shadow 제거
|
|
253
|
+
color?: string
|
|
254
|
+
blur?: number
|
|
255
|
+
offsetX?: number
|
|
256
|
+
offsetY?: number
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `setOpacity`
|
|
261
|
+
컴포넌트 전체 알파 (`state.alpha` 또는 `state.opacity`).
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
input: {
|
|
265
|
+
refids: number[]
|
|
266
|
+
opacity: number // 0..1
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### `setVisible`
|
|
271
|
+
표시 / 숨김 (`state.hidden`).
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
input: {
|
|
275
|
+
refids: number[]
|
|
276
|
+
visible: boolean
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `setMaterial`
|
|
281
|
+
3D PBR 재질.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
input: {
|
|
285
|
+
refids: number[]
|
|
286
|
+
preset?: 'default' | 'metal' | 'glass' | 'plastic' | 'wood' | 'ceramic' | 'rubber' | 'custom'
|
|
287
|
+
metalness?: number // 0..1
|
|
288
|
+
roughness?: number // 0..1
|
|
289
|
+
emissive?: string
|
|
290
|
+
emissiveIntensity?: number // 0..5
|
|
291
|
+
opacity?: number // 0..1, 3D-only (component alpha 와 별개)
|
|
292
|
+
envMapIntensity?: number // 0..3
|
|
293
|
+
side?: 'double' | 'front' | 'back'
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### `setShadowProperties`
|
|
298
|
+
3D 그림자 동작.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
input: {
|
|
302
|
+
refids: number[]
|
|
303
|
+
castShadow?: boolean
|
|
304
|
+
receiveShadow?: boolean
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### `clearStyle`
|
|
309
|
+
특정 styling aspect 초기화 — 데이터 바인딩 / preset 으로 돌리기 전.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
input: {
|
|
313
|
+
refids: number[]
|
|
314
|
+
aspects: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font'>
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 3.2 Tier 2 — Productivity (5 tools)
|
|
319
|
+
|
|
320
|
+
#### `copyStyle`
|
|
321
|
+
한 컴포넌트의 스타일을 다른 컴포넌트들에 복사.
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
input: {
|
|
325
|
+
sourceRefid: number
|
|
326
|
+
targetRefids: number[]
|
|
327
|
+
aspects?: Array<'fill' | 'stroke' | 'font' | 'shadow' | 'material' | 'opacity'>
|
|
328
|
+
// default: 모든 styling aspect 복사
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `harmonizeStyles`
|
|
333
|
+
선택된 컴포넌트들의 한 aspect 를 동일하게 (가장 흔한 값으로).
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
input: {
|
|
337
|
+
refids: number[]
|
|
338
|
+
aspect: 'fill' | 'stroke' | 'font' | 'shadow'
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### `getComponentStyle`
|
|
343
|
+
현재 styling 전체 또는 부분 조회 (read-only).
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
input: {
|
|
347
|
+
refid: number
|
|
348
|
+
aspects?: Array<'fill' | 'stroke' | 'font' | 'shadow' | 'material' | 'opacity'>
|
|
349
|
+
}
|
|
350
|
+
output: { fillStyle?, strokeStyle?, fontStyle?, shadow?, material3d?, opacity? }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### `summarizeStyles`
|
|
354
|
+
여러 컴포넌트 스타일 요약 — AI 컨텍스트 토큰 절감.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
input: { refids: number[] }
|
|
358
|
+
output: string // "5개 컴포넌트: 모두 빨간 solid + 검정 1px stroke + shadow 없음"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### `applyEmphasis`
|
|
362
|
+
강조 단계 시맨틱.
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
input: {
|
|
366
|
+
refids: number[]
|
|
367
|
+
level: 'none' | 'subtle' | 'medium' | 'strong'
|
|
368
|
+
}
|
|
369
|
+
// 구현: level → shadow + stroke + opacity 자동 조합
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 3.3 Tier 3 — Semantic & Theming (6 tools)
|
|
373
|
+
|
|
374
|
+
#### `applySemantic`
|
|
375
|
+
Material 3 시맨틱 컬러 적용.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
input: {
|
|
379
|
+
refids: number[]
|
|
380
|
+
intent: 'success' | 'warning' | 'error' | 'info' | 'primary' | 'neutral'
|
|
381
|
+
variant?: 'filled' | 'outlined' | 'tonal' // default 'filled'
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### `applyState`
|
|
386
|
+
UI 상태 시맨틱.
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
input: {
|
|
390
|
+
refids: number[]
|
|
391
|
+
state: 'active' | 'disabled' | 'highlighted' | 'preview'
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `getStylePresets`
|
|
396
|
+
사용 가능한 preset 목록 (시스템 + 사용자 정의).
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
output: Array<{ name: string; description?: string; aspects: string[] }>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### `applyStylePreset`
|
|
403
|
+
이름으로 preset 적용.
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
input: { refids: number[]; presetName: string }
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### `extractColorPalette`
|
|
410
|
+
컴포넌트들에서 사용 색상 추출 → 팔레트.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
input: { refids: number[] }
|
|
414
|
+
output: Array<{ color: string; usage: number; sources: string[] }>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### `recolor`
|
|
418
|
+
일괄 색상 매핑 변경.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
input: {
|
|
422
|
+
refids: number[]
|
|
423
|
+
mapping: Array<{ from: string; to: string }>
|
|
424
|
+
// tolerance?: number // 색상 유사 매칭 허용 (later)
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 3.4 Tier 4 — 3D Advanced (4 tools)
|
|
429
|
+
|
|
430
|
+
#### `setTexture`
|
|
431
|
+
3D mesh 의 텍스처 매핑 manual 조정.
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
input: {
|
|
435
|
+
refids: number[]
|
|
436
|
+
url?: string
|
|
437
|
+
repeat?: { x: number; y: number }
|
|
438
|
+
offset?: { x: number; y: number }
|
|
439
|
+
rotation?: number
|
|
440
|
+
encoding?: 'sRGB' | 'linear'
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### `setMaterialMood`
|
|
445
|
+
씬 환경 감각에 맞는 material 보정 — Environment preset 과 결합.
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
input: {
|
|
449
|
+
refids: number[]
|
|
450
|
+
mood: 'studio' | 'outdoor-sunny' | 'outdoor-cloudy' | 'warm-indoor' | 'cool-night'
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### `setEmissive`
|
|
455
|
+
발광 quick-access — 전구 / LED / 화면 표현.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
input: {
|
|
459
|
+
refids: number[]
|
|
460
|
+
color?: string
|
|
461
|
+
intensity?: number // 0..5
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### `set3DTransform`
|
|
466
|
+
3D 위치/회전/크기 (위치는 2D centerTo 로도 가능하지만 3D 만 의미 있는 회전/오프셋).
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
input: {
|
|
470
|
+
refids: number[]
|
|
471
|
+
rotation3D?: { x?: number; y?: number; z?: number } // degrees
|
|
472
|
+
offset3D?: { x?: number; y?: number; z?: number }
|
|
473
|
+
scale3D?: { x?: number; y?: number; z?: number }
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 3.5 Tier 5 — Animation / Interaction (later, optional)
|
|
478
|
+
|
|
479
|
+
- `setHoverStyle(refids, ...)` — hover 시 변경 스타일
|
|
480
|
+
- `setActiveStyle(refids, ...)` — active 시 변경 스타일
|
|
481
|
+
- `setTransitionDuration(refids, ms)` — animation 속도
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## 4. Mode 전환 매트릭스
|
|
486
|
+
|
|
487
|
+
### 4.1 setFill mode 전환
|
|
488
|
+
|
|
489
|
+
| from → to | 처리 |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `solid` → `gradient` | 기존 `color` 가 첫 stop, 자동 생성 두 번째 stop (어두운 변형) — 사용자가 stops 명시하면 그대로 |
|
|
492
|
+
| `gradient` → `solid` | 첫 stop 색 또는 평균색이 새 `color` |
|
|
493
|
+
| `solid` → `pattern`/`image` | 색상 정보 버림, image 필수 |
|
|
494
|
+
| `*` → `none` | fillStyle 제거 (state 에서 delete) |
|
|
495
|
+
| 같은 mode | 부분 merge (color 만 줘도 stops/rotation 보존) |
|
|
496
|
+
|
|
497
|
+
### 4.2 setMaterial preset 처리
|
|
498
|
+
|
|
499
|
+
| 입력 | 결과 |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `preset` 만 | preset 의 기본값 reset (resolveMaterial3d) |
|
|
502
|
+
| `preset` + 개별 속성 | preset 위에 개별 속성 override |
|
|
503
|
+
| 개별 속성만 | 기존 preset 유지 + 개별 속성만 변경 |
|
|
504
|
+
| `preset='custom'` + 속성 | preset 효과 X, 속성만 raw 적용 |
|
|
505
|
+
|
|
506
|
+
### 4.3 색상 입력 정규화
|
|
507
|
+
|
|
508
|
+
- hex (`#ff0000`, `#f00`)
|
|
509
|
+
- 명명 색 (`red`, `transparent`, ...) → CSS named colors 매핑
|
|
510
|
+
- rgb/rgba/hsl/hsla — 파싱 후 hex 또는 그대로 보존
|
|
511
|
+
- Material 3 토큰 (`var(--md-sys-color-primary)`) — board theme 적용 시점에 resolve
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 5. 검증 (Validation) 정책
|
|
516
|
+
|
|
517
|
+
### 5.1 입력 검증
|
|
518
|
+
|
|
519
|
+
각 tool 의 검증 책임:
|
|
520
|
+
- 색상 — CSS color string 파싱 가능?
|
|
521
|
+
- 숫자 범위 — opacity 0..1, metalness 0..1, fontSize > 0 등
|
|
522
|
+
- 모드별 필수 필드 — `mode='gradient'` 면 `gradientStops` 또는 자동 생성
|
|
523
|
+
|
|
524
|
+
### 5.2 충돌 감지
|
|
525
|
+
|
|
526
|
+
- 데이터 바인딩 적용 컴포넌트 → 경고 + `force` 옵션 또는 에러
|
|
527
|
+
- Group / 잠긴 컴포넌트 → 정책 결정 필요
|
|
528
|
+
|
|
529
|
+
### 5.3 에러 메시지
|
|
530
|
+
|
|
531
|
+
AI 가 자동 회복 가능하도록:
|
|
532
|
+
```json
|
|
533
|
+
{
|
|
534
|
+
"error": "Invalid color value: 'reddish'. Did you mean 'red'? Valid: hex (#ff0000) or CSS named colors.",
|
|
535
|
+
"suggestion": "red"
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## 6. 구현 전략
|
|
542
|
+
|
|
543
|
+
### 6.1 파일 구성
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
packages/board-ai/server/service/
|
|
547
|
+
├── styling/
|
|
548
|
+
│ ├── index.ts — tool 등록 + dispatch
|
|
549
|
+
│ ├── color-utils.ts — 색상 파싱/정규화/시맨틱 매핑
|
|
550
|
+
│ ├── fill-tools.ts — setFill / mode 전환 로직
|
|
551
|
+
│ ├── stroke-tools.ts — setStroke / dash preset
|
|
552
|
+
│ ├── text-tools.ts — setText / setFont
|
|
553
|
+
│ ├── shadow-tools.ts — setShadow
|
|
554
|
+
│ ├── material-tools.ts — setMaterial / setShadowProperties (3D)
|
|
555
|
+
│ ├── opacity-visibility.ts — setOpacity / setVisible
|
|
556
|
+
│ ├── compose-tools.ts — copyStyle / harmonizeStyles
|
|
557
|
+
│ ├── read-tools.ts — getComponentStyle / summarizeStyles
|
|
558
|
+
│ ├── semantic-tools.ts — applyEmphasis / applySemantic / applyState
|
|
559
|
+
│ ├── preset-tools.ts — getStylePresets / applyStylePreset
|
|
560
|
+
│ ├── color-palette-tools.ts — extractColorPalette / recolor
|
|
561
|
+
│ └── 3d-advanced-tools.ts — setTexture / setEmissive / set3DTransform
|
|
562
|
+
├── styling-presets/
|
|
563
|
+
│ ├── system-presets.ts — 빌트인 preset 정의
|
|
564
|
+
│ └── semantic-tokens.ts — Material 3 시맨틱 매핑
|
|
565
|
+
└── assistant.ts — tool registry 통합
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 6.2 dispatch 통합
|
|
569
|
+
|
|
570
|
+
각 tool 은 기존 `apply-patch.ts` 의 op 형태로 변환되어 적용. 새 op type 필요 없음 —
|
|
571
|
+
`modify` op 의 patch 부분으로 흡수 (기존 inverse 자동 생성 시스템 그대로 활용).
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
// 예: setFill(refids=[35], mode='solid', color='red')
|
|
575
|
+
// → BoardEditPatch:
|
|
576
|
+
{
|
|
577
|
+
ops: [
|
|
578
|
+
{ op: 'modify', refid: 35, patch: { fillStyle: { type: 'solid', color: '#ff0000' } } }
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
inverse 는 dispatcher 가 적용 직전 state 에서 자동 추출.
|
|
584
|
+
|
|
585
|
+
### 6.3 테스트 전략
|
|
586
|
+
|
|
587
|
+
각 tool 별:
|
|
588
|
+
- Unit: input validation, mode 전환, 색상 정규화
|
|
589
|
+
- Integration: apply → assertions on resulting state, inverse 정확성
|
|
590
|
+
- Edge: 데이터 바인딩 충돌, 빈 refids, 잘못된 mode
|
|
591
|
+
|
|
592
|
+
### 6.4 AI prompt 갱신
|
|
593
|
+
|
|
594
|
+
- Tool 별 description 에 사용 예시
|
|
595
|
+
- "AI: Fill 변경 시 setFill 우선 사용. modifyComponentByRefid 는 위 tool 들이 못
|
|
596
|
+
처리하는 fallback 만." 같은 가이드
|
|
597
|
+
- Semantic tool 우선 (Tier 3) → Tier 2 → Tier 1 → Tier 0 (raw modify)
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## 7. Phasing
|
|
602
|
+
|
|
603
|
+
| Phase | 범위 | 추정 | Deliverable |
|
|
604
|
+
|---|---|---|---|
|
|
605
|
+
| **P0** | 추가 audit (Component state 키 정리, 컴포넌트별 specific styling) + plan 보완 | 0.5 일 | 문서 갱신 |
|
|
606
|
+
| **P1** | Tier 1 — 10 tools 구현 + 테스트 | 2 일 | 핵심 styling 동작 |
|
|
607
|
+
| **P2** | Tier 2 — 5 tools (productivity) | 1 일 | copy / harmonize / read |
|
|
608
|
+
| **P3** | Tier 3 — 6 tools (semantic / theming) | 1.5 일 | applyEmphasis / Semantic / preset |
|
|
609
|
+
| **P4** | Tier 4 — 4 tools (3D advanced) | 1 일 | texture / emissive / mood |
|
|
610
|
+
| **P5** | AI prompt 갱신 + 통합 테스트 + 사용자 가이드 | 0.5 일 | 사용 준비 완료 |
|
|
611
|
+
|
|
612
|
+
**총 6 ~ 7 일** — 신중히 진행.
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## 8. 결정 필요 사항 (체크리스트)
|
|
617
|
+
|
|
618
|
+
- [ ] **Tier 5 (Animation/Interaction) 포함 여부** — 후순위? 별도 phase?
|
|
619
|
+
- [ ] **시맨틱 색 출처** — Material 3 시스템 토큰 vs board theme 자체 정의 vs 둘 다
|
|
620
|
+
- [ ] **데이터 바인딩 충돌 정책** — error / warn+force / auto-unbind
|
|
621
|
+
- [ ] **Group 컴포넌트 처리** — 자식까지 propagate? 자식만? 그룹 자체만?
|
|
622
|
+
- [ ] **Locked / readonly 컴포넌트** — error / 무시 / 경고
|
|
623
|
+
- [ ] **호버/액티브 스타일 영속 모델** — 별도 state 필드 vs 기존 변형
|
|
624
|
+
- [ ] **Color 입력 normalize 정책** — hex 변환 후 저장? 입력 형태 보존?
|
|
625
|
+
- [ ] **Multi-target 의 partial failure** — 일부 실패 시 전체 롤백 vs 성공한 것만 적용
|
|
626
|
+
- [ ] **사용자 정의 preset 영속** — 도메인별? 사용자별? 보드별?
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## 9. 향후 확장 가능성
|
|
631
|
+
|
|
632
|
+
- **Style snapshot / version**: 컴포넌트의 스타일 시점별 저장 → 롤백
|
|
633
|
+
- **Style diff visualization**: 두 컴포넌트 / 두 시점 스타일 비교 시각화
|
|
634
|
+
- **AI 의 색감 학습**: 사용자가 자주 쓰는 색조합 → 추천
|
|
635
|
+
- **외부 디자인 시스템 import**: Figma 토큰 / Material 3 파일 가져오기
|
|
636
|
+
- **접근성 검증**: 대비비 (contrast ratio) 자동 체크 + 개선 제안
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## 10. 사용자에게 보일 결과 (예시)
|
|
641
|
+
|
|
642
|
+
**Before** (현재):
|
|
643
|
+
> 사용자: "이 모터들을 빨간 solid 배경으로 + 파란 dashed 1px 테두리로 + 0.5 투명도"
|
|
644
|
+
>
|
|
645
|
+
> AI: `modifyComponentByRefid(refid=35, patch={fillStyle:{type:'solid', color:'#ff0000', opacity:0.5}, strokeStyle:{color:'#0000ff', width:1, lineDash:[4,4]}})` × N번
|
|
646
|
+
>
|
|
647
|
+
> 토큰 많고 모드 전환 시 실수 빈도 높음.
|
|
648
|
+
|
|
649
|
+
**After** (구현 완료):
|
|
650
|
+
> AI: `setFill(refids=[35,36,37], mode='solid', color='red', opacity=0.5)` +
|
|
651
|
+
> `setStroke(refids=[35,36,37], color='blue', width=1, dash='dashed')`
|
|
652
|
+
>
|
|
653
|
+
> 토큰 1/3, 의도 명확, 모드 전환 안전, multi-target 한 번.
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
**다음 액션**: P0 (추가 audit) → P1 (Tier 1 구현) 시작. 결정 사항 (Section 8) 확인하며 진행.
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## 11. Critical Success Factors
|
|
662
|
+
|
|
663
|
+
> 이 기능의 성공이 board-ai 전체의 성공을 좌우한다.
|
|
664
|
+
> 단순 tool 25개 추가 ≠ 성공. 사용자가 자연어로 의도를 표현하면 AI 가 정확한
|
|
665
|
+
> tool 을 골라 정확한 인자로 호출, 결과가 의도와 일치 — 이게 됐을 때 성공.
|
|
666
|
+
|
|
667
|
+
### 11.1 Discoverability — AI 가 올바른 tool 을 고르게
|
|
668
|
+
|
|
669
|
+
25+ tool 중 AI 가 옳은 걸 고르려면:
|
|
670
|
+
|
|
671
|
+
**Tool naming convention**
|
|
672
|
+
- 동사 + 대상 일관: `setFill` / `setStroke` / `setText` (`update*`/`change*` 혼재 X)
|
|
673
|
+
- 시맨틱 tier 는 `apply*` 접두 (`applyEmphasis` / `applySemantic`) — Tier 구분 시각화
|
|
674
|
+
- 읽기는 `get*` (`getComponentStyle`)
|
|
675
|
+
- 일괄/관계는 `copy*` / `harmonize*` / `extract*`
|
|
676
|
+
|
|
677
|
+
**Tool description 의 우선순위 가이드**
|
|
678
|
+
각 tool description 에 명시:
|
|
679
|
+
```
|
|
680
|
+
"Use this when the user asks to change the BACKGROUND/FILL of components.
|
|
681
|
+
Prefer this over modifyComponentByRefid for fill changes — handles mode
|
|
682
|
+
transitions safely."
|
|
683
|
+
```
|
|
684
|
+
AI 는 description 의 "Use this when" 으로 매칭. 따라서 자연어 의도 키워드를 description 에 풍부하게.
|
|
685
|
+
|
|
686
|
+
**Tool grouping in system prompt**
|
|
687
|
+
```
|
|
688
|
+
Styling tools (in order of preference):
|
|
689
|
+
- Semantic: applySemantic, applyEmphasis, applyState
|
|
690
|
+
- Specific: setFill, setStroke, setFont, setShadow, setMaterial
|
|
691
|
+
- Read: getComponentStyle, summarizeStyles
|
|
692
|
+
- Compose: copyStyle, harmonizeStyles
|
|
693
|
+
- Raw fallback: modifyComponentByRefid
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### 11.2 AI prompting 전략
|
|
697
|
+
|
|
698
|
+
**원칙: AI 가 선호 순서를 학습하도록 system prompt 가 명시**
|
|
699
|
+
|
|
700
|
+
```
|
|
701
|
+
1. 사용자 의도가 시맨틱하면 (강조 / 경고 / 비활성)
|
|
702
|
+
→ applySemantic / applyEmphasis / applyState 우선
|
|
703
|
+
2. 명시적 색/굵기 지정이면
|
|
704
|
+
→ setFill / setStroke / setFont
|
|
705
|
+
3. 여러 컴포넌트 동일 변경이면
|
|
706
|
+
→ 한 번의 tool call 에 refids 배열로
|
|
707
|
+
4. 한 컴포넌트 → 다른 컴포넌트 같게
|
|
708
|
+
→ copyStyle
|
|
709
|
+
5. 위 어디에도 안 맞으면
|
|
710
|
+
→ modifyComponentByRefid (raw fallback)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**현재 상태 인지**
|
|
714
|
+
AI 가 변경 전에 `getComponentStyle` / `summarizeStyles` 호출 → 현재 상태 파악 후
|
|
715
|
+
의도적 변경. "이미 빨간 모터를 더 빨갛게 해줘" 같은 모호 케이스 정확히 처리.
|
|
716
|
+
|
|
717
|
+
**색상 입력 변환**
|
|
718
|
+
AI 가 "warm red" 같은 자연어 색을 입력 시 tool 이 자체 변환 — color-utils 가 시맨틱
|
|
719
|
+
이름 → hex 매핑. AI prompt 에 "named colors are accepted: red, warm-red, ..." 명시.
|
|
720
|
+
|
|
721
|
+
### 11.3 사용자 시나리오 (실제)
|
|
722
|
+
|
|
723
|
+
**Scenario 1 — 일괄 디자인 변경**
|
|
724
|
+
> "이 대시보드의 게이지 3개를 모두 다크 배경 + 청록 강조 + 그림자 살짝"
|
|
725
|
+
>
|
|
726
|
+
> AI:
|
|
727
|
+
> 1. `findComponents(query='gauge')` → refids
|
|
728
|
+
> 2. `setFill(refids, mode='solid', color='#0a1929')`
|
|
729
|
+
> 3. `setStroke(refids, color='#06b6d4', width=2)`
|
|
730
|
+
> 4. `setShadow(refids, blur=8, offsetY=2, color='rgba(0,0,0,0.3)')`
|
|
731
|
+
|
|
732
|
+
**Scenario 2 — 시맨틱**
|
|
733
|
+
> "이 알람 영역들 위험 표시 강조"
|
|
734
|
+
>
|
|
735
|
+
> AI:
|
|
736
|
+
> 1. `findComponents(query='alarm')`
|
|
737
|
+
> 2. `applySemantic(refids, intent='error', variant='filled')`
|
|
738
|
+
> 3. `applyEmphasis(refids, level='strong')`
|
|
739
|
+
|
|
740
|
+
**Scenario 3 — 반복 작업 (copy 패턴)**
|
|
741
|
+
> "1번 모터처럼 다른 모터들도 같게 만들어줘"
|
|
742
|
+
>
|
|
743
|
+
> AI:
|
|
744
|
+
> 1. `findComponents(query='motor')` → refids
|
|
745
|
+
> 2. `copyStyle(sourceRefid=35, targetRefids=[36,37,38], aspects=['fill','stroke'])`
|
|
746
|
+
|
|
747
|
+
**Scenario 4 — 점진 조정**
|
|
748
|
+
> "조금 더 부드럽게" (이전 대화: 빨강 적용)
|
|
749
|
+
>
|
|
750
|
+
> AI:
|
|
751
|
+
> 1. `getComponentStyle(refid=35)` — 현재 색 #ff0000 파악
|
|
752
|
+
> 2. `setFill(refids=[35], color='#ff6666', opacity=0.85)` — saturation/lightness 조정
|
|
753
|
+
|
|
754
|
+
**Scenario 5 — 3D 재질 시맨틱**
|
|
755
|
+
> "이 트랙들은 닳은 고무 느낌으로"
|
|
756
|
+
>
|
|
757
|
+
> AI:
|
|
758
|
+
> 1. `setMaterial(refids, preset='rubber', roughness=0.95)` — preset + roughness override
|
|
759
|
+
|
|
760
|
+
**Scenario 6 — 모드 전환**
|
|
761
|
+
> "이 영역 단색 빨강 대신 위→아래 빨강→어두운빨강 그라디언트"
|
|
762
|
+
>
|
|
763
|
+
> AI:
|
|
764
|
+
> 1. `setFill(refids=[35], mode='gradient', gradientType='linear', rotation=90,
|
|
765
|
+
> gradientStops=[{color:'#ff0000', position:0},{color:'#660000', position:1}])`
|
|
766
|
+
|
|
767
|
+
### 11.4 품질 지표 (성공 측정)
|
|
768
|
+
|
|
769
|
+
**Tool 사용 정확도**
|
|
770
|
+
- AI 가 첫 호출에 올바른 tool 선택 비율 — 목표 ≥ 90%
|
|
771
|
+
- modifyComponentByRefid (raw fallback) 사용 비율 — 목표 ≤ 5% (styling 영역에서)
|
|
772
|
+
- 평균 tool call 수 / 의도 — 단순할수록 좋음 (한 의도 1~2 call)
|
|
773
|
+
|
|
774
|
+
**사용자 만족도**
|
|
775
|
+
- "다시 시도" / 수정 요청 빈도 — 낮을수록
|
|
776
|
+
- 완료 후 사용자 직접 후가공 빈도 — 낮을수록
|
|
777
|
+
|
|
778
|
+
**견고성**
|
|
779
|
+
- 잘못된 색/숫자 입력 시 graceful 회복 — AI 가 에러 보고 재시도 성공
|
|
780
|
+
- Mode 전환 시 의도치 않은 결과 — 0 목표
|
|
781
|
+
|
|
782
|
+
**관찰 방법**
|
|
783
|
+
- ChatMessage 의 toolUsages 에 styling tool 사용 trace 영속됨 (이미 구현)
|
|
784
|
+
- 추후 dashboard / 분석으로 위 지표 측정 가능
|
|
785
|
+
|
|
786
|
+
### 11.5 디테일 매개체 — Color Utils
|
|
787
|
+
|
|
788
|
+
`color-utils.ts` 가 핵심 보조. 잘 만들면 AI 호출이 자연스러움:
|
|
789
|
+
|
|
790
|
+
**파싱 / 정규화**
|
|
791
|
+
- hex / hex-short / rgb / rgba / hsl / hsla / 명명 색 / Material 3 토큰
|
|
792
|
+
- → 정규 hex (저장용) / hex+alpha 분리 (opacity 별도)
|
|
793
|
+
|
|
794
|
+
**시맨틱 매핑**
|
|
795
|
+
- 'warm-red' → `#e74c3c`
|
|
796
|
+
- 'forest-green' → `#228b22`
|
|
797
|
+
- 색 위계 (light/dark/muted/vibrant) — `lighten('red', 30%)` / `darken` / `desaturate`
|
|
798
|
+
|
|
799
|
+
**색상 추론**
|
|
800
|
+
- `complement(color)` — 보색
|
|
801
|
+
- `analogous(color, n)` — 인접 색
|
|
802
|
+
- `pickContrast(bg, options)` — 가독성 좋은 텍스트 색
|
|
803
|
+
|
|
804
|
+
**Material 3 토큰 resolve**
|
|
805
|
+
- `--md-sys-color-primary` → 현재 board theme 의 hex
|
|
806
|
+
|
|
807
|
+
이 utils 가 두꺼울수록 AI 가 자연어 색상 표현 자유로워짐.
|
|
808
|
+
|
|
809
|
+
### 11.6 Iteration loop 존중
|
|
810
|
+
|
|
811
|
+
사용자가 한 번에 끝내는 경우 거의 없음 — 점진 조정이 정상.
|
|
812
|
+
|
|
813
|
+
**상태 보존 패턴**
|
|
814
|
+
- AI 가 "조금 더 어둡게" 하면 → 직전 상태에서 -10% lightness, 다른 속성 보존
|
|
815
|
+
- AI 가 시맨틱 후 raw 조정 → 시맨틱 메타 보존 (다음에 시맨틱 변경 시 raw 변경 무시 X)
|
|
816
|
+
|
|
817
|
+
이를 위해 `getComponentStyle` 응답에 "마지막 styling op type" 메타 같이 — 이건 P3
|
|
818
|
+
이후 검토.
|
|
819
|
+
|
|
820
|
+
### 11.7 Anti-patterns (금지)
|
|
821
|
+
|
|
822
|
+
- ❌ Tool 마다 input 형식 제멋대로 (어떤 건 `refid: number`, 어떤 건 `target: id` — 일관성 X)
|
|
823
|
+
- ❌ Mode 전환 시 partial merge 시도 (예: solid → gradient 인데 기존 color 만 바꾸려 함)
|
|
824
|
+
- ❌ "이 tool 은 fillStyle 만 다룸" 같은 좁은 description — AI 가 의도 매칭 못 함
|
|
825
|
+
- ❌ AI 가 raw modifyComponentByRefid 자주 사용 → 시맨틱 tool 가이드 부족
|
|
826
|
+
- ❌ Validation 에러를 raw "Bad input" 메시지로 — 자동 회복 불가
|
|
827
|
+
|
|
828
|
+
### 11.8 무엇이 "완성" 인가
|
|
829
|
+
|
|
830
|
+
> "사용자가 디자인을 자연어로 설명하면, AI 가 우리 tool 군으로 정확히 표현하고,
|
|
831
|
+
> 결과가 사용자 의도와 90%+ 일치한다."
|
|
832
|
+
|
|
833
|
+
이게 안 되면 AI 가치 = 0. 위 11.1 ~ 11.6 항목들이 다 작동해야 비로소 "완성".
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## 12. **데이터 바인딩 styling — 생산성의 핵심**
|
|
838
|
+
|
|
839
|
+
> 정적 스타일링은 시작점일 뿐. **스타일과 데이터가 결합된 동적 표현**이 things-scene
|
|
840
|
+
> 의 진짜 가치이고, AI 가 이걸 자연스럽게 다루면 사용자 생산성이 폭증한다.
|
|
841
|
+
> 동시에 가장 어려운 영역 — 룰 / accessor / converter / 조건 / 상태 모두 다뤄야.
|
|
842
|
+
|
|
843
|
+
### 12.1 things-scene 의 데이터 바인딩 모델
|
|
844
|
+
|
|
845
|
+
`src/interfaces/data.ts:4` `Mapping`:
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
interface Mapping {
|
|
849
|
+
rule: string // 평가식 (값 가공 — jsonata-style 또는 함수)
|
|
850
|
+
target: string // 적용 대상 — 'fillStyle' / 'strokeStyle' / '[fillStyle:color]'
|
|
851
|
+
property?: string // 타입 힌트 — 'fillStyle' / 'strokeStyle' / 'ref' 등
|
|
852
|
+
accessor?: string // 데이터 소스 경로 — 'tags.MOTOR1.value'
|
|
853
|
+
source?: any // 데이터 소스 참조
|
|
854
|
+
converter?: string // 추가 변환 함수
|
|
855
|
+
param?: any // 룰에 전달되는 파라미터
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
컴포넌트의 `state.mappings: Mapping[]` 으로 영속. 런타임에 `_mappings` 가 binding
|
|
860
|
+
객체로 활성화되어 데이터 변경 시 자동 styling 업데이트.
|
|
861
|
+
|
|
862
|
+
**Target 표현법**:
|
|
863
|
+
- `fillStyle` — fillStyle 전체 교체
|
|
864
|
+
- `[fillStyle:color]` — fillStyle.color 만 변경 (sub-property)
|
|
865
|
+
- `[strokeStyle:width]` — strokeStyle.width
|
|
866
|
+
- `[material3d:emissive]` — 3D 발광색
|
|
867
|
+
- `hidden` — 표시 여부
|
|
868
|
+
- `text` — 텍스트 내용
|
|
869
|
+
|
|
870
|
+
### 12.2 Common 시나리오 (실제 사용)
|
|
871
|
+
|
|
872
|
+
#### 시나리오 A — 임계치 색상
|
|
873
|
+
> "온도 80도 넘으면 빨강, 50~80은 주황, 그 아래는 초록"
|
|
874
|
+
|
|
875
|
+
이 한 문장이 mapping 으로 변환:
|
|
876
|
+
```js
|
|
877
|
+
{
|
|
878
|
+
accessor: 'tags.TEMP.value',
|
|
879
|
+
target: '[fillStyle:color]',
|
|
880
|
+
rule: 'value > 80 ? "#ef4444" : value > 50 ? "#f59e0b" : "#22c55e"'
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
#### 시나리오 B — 값-색상 그라디언트 매핑
|
|
885
|
+
> "0~100 값을 파랑→빨강 색 스펙트럼으로"
|
|
886
|
+
|
|
887
|
+
```js
|
|
888
|
+
{
|
|
889
|
+
accessor: 'tags.LEVEL.value',
|
|
890
|
+
target: '[fillStyle:color]',
|
|
891
|
+
rule: 'interpolateColor(value, 0, 100, "#3b82f6", "#ef4444")'
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
#### 시나리오 C — 상태 기반 발광 (3D)
|
|
896
|
+
> "MOTOR 가 running 일 때 노란 발광, stopped 면 끄기"
|
|
897
|
+
|
|
898
|
+
```js
|
|
899
|
+
{
|
|
900
|
+
accessor: 'tags.MOTOR.status',
|
|
901
|
+
target: '[material3d:emissive]',
|
|
902
|
+
rule: 'value === "running" ? "#fde047" : "#000000"'
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
accessor: 'tags.MOTOR.status',
|
|
906
|
+
target: '[material3d:emissiveIntensity]',
|
|
907
|
+
rule: 'value === "running" ? 2 : 0'
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### 시나리오 D — 알람 시 표시 / 깜빡임
|
|
912
|
+
> "ALARM 활성 시 빨간 테두리 굵게 + 점멸"
|
|
913
|
+
|
|
914
|
+
```js
|
|
915
|
+
{
|
|
916
|
+
accessor: 'tags.ALARM.value',
|
|
917
|
+
target: '[strokeStyle:color]',
|
|
918
|
+
rule: 'value ? "#dc2626" : "#94a3b8"'
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
accessor: 'tags.ALARM.value',
|
|
922
|
+
target: '[strokeStyle:width]',
|
|
923
|
+
rule: 'value ? 3 : 1'
|
|
924
|
+
},
|
|
925
|
+
// 점멸은 animation API 와 결합 — 별 phase
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
#### 시나리오 E — 조건부 가시성
|
|
929
|
+
> "REJECT 갯수가 0이면 알람 패널 숨김"
|
|
930
|
+
|
|
931
|
+
```js
|
|
932
|
+
{
|
|
933
|
+
accessor: 'tags.REJECT_COUNT.value',
|
|
934
|
+
target: 'hidden',
|
|
935
|
+
rule: 'value === 0'
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
#### 시나리오 F — 텍스트 + 색상 동시
|
|
940
|
+
> "현재 가동률을 큰 글자로 보여주고, 90% 미만이면 빨간색"
|
|
941
|
+
|
|
942
|
+
```js
|
|
943
|
+
[
|
|
944
|
+
{ accessor: 'tags.UTIL.value', target: 'text', rule: 'value + "%"' },
|
|
945
|
+
{ accessor: 'tags.UTIL.value', target: '[fontColor]',
|
|
946
|
+
rule: 'value < 90 ? "#ef4444" : "#0f172a"' }
|
|
947
|
+
]
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
### 12.3 Tool 카탈로그 — 데이터 바인딩 styling
|
|
951
|
+
|
|
952
|
+
**대원칙**: AI 가 "온도 80 넘으면 빨강" 같은 자연어 → 정확한 mapping 객체로 변환.
|
|
953
|
+
정적 styling tool 의 high-level 시맨틱과 **동일한 톤** 으로 설계. 차이는 "값에 따라"
|
|
954
|
+
가 들어가는 것.
|
|
955
|
+
|
|
956
|
+
#### Tier B1 — Read
|
|
957
|
+
|
|
958
|
+
##### `getDataBindings`
|
|
959
|
+
컴포넌트의 현재 mapping 들 조회 (요약 포함).
|
|
960
|
+
|
|
961
|
+
```ts
|
|
962
|
+
input: { refid: number }
|
|
963
|
+
output: Array<{
|
|
964
|
+
index: number
|
|
965
|
+
target: string // 'fillStyle' / '[fillStyle:color]' 등
|
|
966
|
+
accessor: string // 데이터 소스 경로
|
|
967
|
+
rule: string // 평가식 요약 (긴 경우 압축)
|
|
968
|
+
ruleType: 'threshold' | 'range-map' | 'state-map' | 'expression' // AI 가 의도 추론 가능
|
|
969
|
+
description: string // 사람 친화 설명 — "TEMP 값이 80 초과 시 빨강"
|
|
970
|
+
}>
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
##### `summarizeDataBindings`
|
|
974
|
+
여러 컴포넌트의 binding 패턴 요약 — AI 토큰 절감.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
input: { refids: number[] }
|
|
978
|
+
output: string // "5개 컴포넌트가 모두 tags.LEVEL.value 의 임계치 (>80 빨강) 로 fillStyle 바인딩"
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
##### `findComponentsWithBinding`
|
|
982
|
+
특정 데이터 소스 사용하는 컴포넌트 검색.
|
|
983
|
+
|
|
984
|
+
```ts
|
|
985
|
+
input: {
|
|
986
|
+
accessor?: string // 'tags.TEMP.*' (와일드카드)
|
|
987
|
+
targetPattern?: string // 'fillStyle' / '[material3d:*]'
|
|
988
|
+
}
|
|
989
|
+
output: Array<{ refid, target, rule }>
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### Tier B2 — Create / Modify (semantic high-level)
|
|
993
|
+
|
|
994
|
+
##### `bindThresholdStyle`
|
|
995
|
+
**가장 자주 쓸 tool**. 임계치 기반 색상/속성 매핑.
|
|
996
|
+
|
|
997
|
+
```ts
|
|
998
|
+
input: {
|
|
999
|
+
refids: number[]
|
|
1000
|
+
accessor: string // 데이터 소스 경로
|
|
1001
|
+
target: 'fill' | 'stroke' | 'emissive' | 'border-color' | 'text-color' | 'opacity' | string
|
|
1002
|
+
thresholds: Array<{ above: number; value: any; label?: string }>
|
|
1003
|
+
default?: any // 어느 임계치도 안 맞으면
|
|
1004
|
+
}
|
|
1005
|
+
// 예: above 80 → red, above 50 → orange, default → green
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
서버가 자동으로 `Mapping` 객체로 변환 (rule expression 생성).
|
|
1009
|
+
|
|
1010
|
+
##### `bindValueRangeStyle`
|
|
1011
|
+
값 범위 → 연속 색상 그라디언트.
|
|
1012
|
+
|
|
1013
|
+
```ts
|
|
1014
|
+
input: {
|
|
1015
|
+
refids: number[]
|
|
1016
|
+
accessor: string
|
|
1017
|
+
target: 'fill' | 'emissive' | string
|
|
1018
|
+
range: { min: number; max: number }
|
|
1019
|
+
colors: { atMin: string; atMax: string; atMid?: string }
|
|
1020
|
+
// 또는 colorStops: [{ position, color }]
|
|
1021
|
+
}
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
##### `bindStateStyle`
|
|
1025
|
+
상태 (state machine) 기반 매핑.
|
|
1026
|
+
|
|
1027
|
+
```ts
|
|
1028
|
+
input: {
|
|
1029
|
+
refids: number[]
|
|
1030
|
+
accessor: string
|
|
1031
|
+
target: string
|
|
1032
|
+
states: Record<string, any> // { running: '#fde047', stopped: '#000000', error: '#ef4444' }
|
|
1033
|
+
default?: any
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
##### `bindConditionalVisibility`
|
|
1038
|
+
값 조건 → 표시/숨김.
|
|
1039
|
+
|
|
1040
|
+
```ts
|
|
1041
|
+
input: {
|
|
1042
|
+
refids: number[]
|
|
1043
|
+
accessor: string
|
|
1044
|
+
condition: 'truthy' | 'falsy' | 'equals' | 'not-equals' | 'gt' | 'lt' | 'between' | 'in' | 'expression'
|
|
1045
|
+
param?: any // condition 별 인자
|
|
1046
|
+
hideWhen: 'matches' | 'not-matches'
|
|
1047
|
+
}
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
##### `bindTextValue`
|
|
1051
|
+
값 → 텍스트 (포맷 옵션 포함).
|
|
1052
|
+
|
|
1053
|
+
```ts
|
|
1054
|
+
input: {
|
|
1055
|
+
refids: number[]
|
|
1056
|
+
accessor: string
|
|
1057
|
+
format?: 'number' | 'percent' | 'currency' | 'date' | 'time' | 'custom'
|
|
1058
|
+
customFormat?: string // e.g., '{value} ℃' 또는 strftime-like
|
|
1059
|
+
precision?: number // 소수점 자릿수
|
|
1060
|
+
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
##### `bindExpression` (raw escape hatch)
|
|
1064
|
+
임의 룰 — 위 시맨틱 tool 로 표현 안 되는 케이스.
|
|
1065
|
+
|
|
1066
|
+
```ts
|
|
1067
|
+
input: {
|
|
1068
|
+
refids: number[]
|
|
1069
|
+
accessor: string
|
|
1070
|
+
target: string
|
|
1071
|
+
rule: string // jsonata 또는 함수 표현식
|
|
1072
|
+
description?: string // AI 가 의도 기록 (디버깅용)
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
#### Tier B3 — Remove / Inspect
|
|
1077
|
+
|
|
1078
|
+
##### `removeBinding`
|
|
1079
|
+
특정 binding 제거.
|
|
1080
|
+
|
|
1081
|
+
```ts
|
|
1082
|
+
input: {
|
|
1083
|
+
refid: number
|
|
1084
|
+
target?: string // 특정 target 만 / 미지정 시 모든 binding
|
|
1085
|
+
index?: number // 특정 index 의 binding (precise)
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
##### `clearBindings`
|
|
1090
|
+
모든 binding 제거 (정적 styling 으로 복귀).
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
input: { refids: number[] }
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
##### `replaceBinding`
|
|
1097
|
+
기존 binding 통째 교체 (안전한 in-place 수정).
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
input: {
|
|
1101
|
+
refid: number
|
|
1102
|
+
index: number
|
|
1103
|
+
newBinding: { accessor, target, rule, ... }
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
#### Tier B4 — Composite
|
|
1108
|
+
|
|
1109
|
+
##### `copyBindings`
|
|
1110
|
+
한 컴포넌트의 binding 들을 다른 컴포넌트들에 복제.
|
|
1111
|
+
|
|
1112
|
+
```ts
|
|
1113
|
+
input: {
|
|
1114
|
+
sourceRefid: number
|
|
1115
|
+
targetRefids: number[]
|
|
1116
|
+
rebindAccessor?: { from: string; to: string }
|
|
1117
|
+
// 예: tags.MOTOR1.value → tags.MOTOR{n}.value 식 패턴
|
|
1118
|
+
}
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
##### `harmonizeBindings`
|
|
1122
|
+
여러 컴포넌트의 styling binding 통일 — 일관된 임계치/색상.
|
|
1123
|
+
|
|
1124
|
+
```ts
|
|
1125
|
+
input: {
|
|
1126
|
+
refids: number[]
|
|
1127
|
+
master: number // 이 컴포넌트의 binding 을 기준으로
|
|
1128
|
+
aspects?: Array<'fill' | 'stroke' | 'visibility' | ...>
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
##### `convertStaticToBinding`
|
|
1133
|
+
정적 styling 을 데이터 바인딩으로 전환 — 디자인 → 동적 변환 헬퍼.
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
input: {
|
|
1137
|
+
refids: number[]
|
|
1138
|
+
aspect: 'fill' | 'stroke' | 'visibility' | ...
|
|
1139
|
+
accessor: string
|
|
1140
|
+
// 자동 추론: 현재 색상 보존 + accessor 의 임계치 고민 → AI 와 협업해 thresholds 결정
|
|
1141
|
+
}
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
### 12.4 핵심 도전 과제
|
|
1145
|
+
|
|
1146
|
+
#### Rule expression 생성
|
|
1147
|
+
|
|
1148
|
+
AI 가 자연어 → `rule` (코드) 로 변환해야 하는데:
|
|
1149
|
+
- jsonata 와 일반 JS 표현식 구별
|
|
1150
|
+
- 보안: arbitrary code 실행 방지 — sandbox / whitelist
|
|
1151
|
+
- 검증: 평가 시도 + 에러 잡기
|
|
1152
|
+
- AI 가 자체 생성한 rule 이 동작하는지 server-side 사전 평가 필요
|
|
1153
|
+
|
|
1154
|
+
**해결**:
|
|
1155
|
+
- 시맨틱 tool (bindThresholdStyle 등) 이 rule 을 자동 생성 — AI 는 raw rule 거의 안 씀
|
|
1156
|
+
- bindExpression (raw) 만 rule 받지만 server 가 sandbox 평가 + 결과 타입 검증
|
|
1157
|
+
|
|
1158
|
+
#### Accessor 자동완성
|
|
1159
|
+
|
|
1160
|
+
사용자: "MOTOR1 의 값으로 색 바꿔줘"
|
|
1161
|
+
AI: 어떤 accessor 가 MOTOR1 인지 모름. → `findDataSources(query)` tool 필요.
|
|
1162
|
+
|
|
1163
|
+
##### `findDataSources` (B1 추가)
|
|
1164
|
+
```ts
|
|
1165
|
+
input: { query: string; limit?: number }
|
|
1166
|
+
output: Array<{ accessor: string; type: string; sample?: any; description?: string }>
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
things-factory 의 데이터 시스템과 연동 — tags / variables / metrics / sensors 등.
|
|
1170
|
+
이 tool 의 품질이 binding 정확도 결정.
|
|
1171
|
+
|
|
1172
|
+
#### Type 호환성
|
|
1173
|
+
|
|
1174
|
+
target `fillStyle` 은 string 또는 객체 받음. AI 가 임계치로 `'#ff0000'` 같은 string
|
|
1175
|
+
을 정확히 생성해야. type hint (mapping.property) 가 helping.
|
|
1176
|
+
|
|
1177
|
+
#### Mapping 충돌
|
|
1178
|
+
|
|
1179
|
+
같은 컴포넌트에 fillStyle 과 [fillStyle:color] 동시 binding 시 충돌. server 가 감지 +
|
|
1180
|
+
경고 + 자동 정리 옵션.
|
|
1181
|
+
|
|
1182
|
+
### 12.5 사용자 시나리오 (binding)
|
|
1183
|
+
|
|
1184
|
+
**Scenario 1 — 한 줄 자연어 → 작동하는 dashboard**
|
|
1185
|
+
> "이 게이지 5개를 각자의 sensor 값에 맞게 0~100 범위 파랑→빨강 그라디언트로"
|
|
1186
|
+
|
|
1187
|
+
AI:
|
|
1188
|
+
1. `findDataSources(query='sensor')` → [tags.S1.value, tags.S2.value, ...]
|
|
1189
|
+
2. `findComponents(query='gauge')` → [refids]
|
|
1190
|
+
3. `bindValueRangeStyle(refids[i], accessor='tags.S{i}.value', target='fill',
|
|
1191
|
+
range={min:0,max:100}, colors={atMin:'#3b82f6',atMax:'#ef4444'})`
|
|
1192
|
+
|
|
1193
|
+
**Scenario 2 — 기존 mapping 패턴 복제**
|
|
1194
|
+
> "MOTOR1 처럼 다른 모터들도 같은 룰로 emissive 변경"
|
|
1195
|
+
|
|
1196
|
+
AI:
|
|
1197
|
+
1. `getDataBindings(refid=motor1_refid)` → 룰 파악
|
|
1198
|
+
2. `copyBindings(sourceRefid=motor1, targetRefids=[m2,m3,m4],
|
|
1199
|
+
rebindAccessor={from:'tags.MOTOR1.', to:'tags.MOTOR{n}.'})`
|
|
1200
|
+
|
|
1201
|
+
**Scenario 3 — 디자인 → 동적 전환**
|
|
1202
|
+
> "이 빨간 박스들을 ALARM 값에 따라 켜지는 걸로 바꿔"
|
|
1203
|
+
|
|
1204
|
+
AI:
|
|
1205
|
+
1. `getComponentStyle(refid)` → 현재 빨강 #ef4444 파악
|
|
1206
|
+
2. `convertStaticToBinding(refids, aspect='fill', accessor='tags.ALARM.value')`
|
|
1207
|
+
3. AI 가 임계치 추론 (값 truthy 일 때 빨강, falsy 일 때 회색) → `bindStateStyle`
|
|
1208
|
+
호출 with `{1: '#ef4444', 0: '#94a3b8'}`
|
|
1209
|
+
|
|
1210
|
+
### 12.6 binding tool 의 phasing
|
|
1211
|
+
|
|
1212
|
+
기존 plan 에 추가:
|
|
1213
|
+
|
|
1214
|
+
| Phase | 추가 범위 | 추정 |
|
|
1215
|
+
|---|---|---|
|
|
1216
|
+
| **PB0** | data-binding 모델 정밀 audit (evaluator / accessor / converter 동작) | 0.5 일 |
|
|
1217
|
+
| **PB1** | Tier B1 (read) — getDataBindings / summarize / findComponentsWithBinding / findDataSources | 1 일 |
|
|
1218
|
+
| **PB2** | Tier B2 (create) — bindThresholdStyle / bindValueRangeStyle / bindStateStyle / bindConditionalVisibility / bindTextValue | 2 일 |
|
|
1219
|
+
| **PB3** | Tier B3 (remove) + Tier B4 (composite — copyBindings / convertStaticToBinding) | 1 일 |
|
|
1220
|
+
| **PB4** | bindExpression (raw) + sandbox 평가 + 검증 | 1 일 |
|
|
1221
|
+
| **PB5** | AI prompt 갱신 + 시나리오 통합 테스트 | 0.5 일 |
|
|
1222
|
+
|
|
1223
|
+
**총 binding 부분 6 일** + 정적 styling 6~7 일 = **약 12~13일** 의 풀 풀 plan.
|
|
1224
|
+
|
|
1225
|
+
### 12.7 왜 이게 게임 체인저인가
|
|
1226
|
+
|
|
1227
|
+
**정적 styling 만**: AI 는 멋진 디자인 만들 수 있음. 그러나 "운영 화면" 으로 가면
|
|
1228
|
+
값에 따라 변하는 표현이 필수. AI 가 정적까지만 하면 사용자가 손으로 binding 작업 →
|
|
1229
|
+
AI 의 도움 가치 50%.
|
|
1230
|
+
|
|
1231
|
+
**Binding 까지 잘하면**: 사용자 한 문장 → 동적 dashboard 완성. 디자인 + 데이터 흐름
|
|
1232
|
+
같이 자동화. 진짜 "operator AI" 가 됨. 생산성 차이 10배 이상.
|
|
1233
|
+
|
|
1234
|
+
특히 things-factory 의 도메인 (제조 / 공정 / 시설 모니터링) 에서는 정적 디자인 가치
|
|
1235
|
+
보다 **동적 운영 화면** 가치가 훨씬 큼. binding tool 이 약하면 board-ai 는 디자인
|
|
1236
|
+
도구에 머물고, 운영 도구로 못 큼.
|
|
1237
|
+
|
|
1238
|
+
### 12.8 priorities — 정적 vs binding
|
|
1239
|
+
|
|
1240
|
+
**같이 가야 함**. 둘 중 하나만 잘 만들면 의미 적음:
|
|
1241
|
+
- 정적 styling 좋고 binding 약함 → "예쁜 게 다는 아니다" — 사용자 실망
|
|
1242
|
+
- binding 좋고 정적 styling 약함 → "기능은 되는데 못생겼다" — 사용자 실망
|
|
1243
|
+
|
|
1244
|
+
**제 권고 phasing**:
|
|
1245
|
+
1. P1 정적 Tier 1 (10 tools) ← 시각적 즉각 가치
|
|
1246
|
+
2. PB1 binding read tools ← AI 가 컨텍스트 파악
|
|
1247
|
+
3. PB2 binding create (high-level — threshold/range/state) ← 핵심 가치
|
|
1248
|
+
4. P2~P3 정적 productivity & semantic
|
|
1249
|
+
5. PB3+ binding 마무리
|
|
1250
|
+
|
|
1251
|
+
순환식 — 정적과 binding 을 번갈아 layer 쌓는 게 양쪽 다 단단해짐.
|
|
1252
|
+
|
|
1253
|
+
---
|