@things-factory/board-ai 10.1.20 → 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.
- package/dist-client/server/service/assistant.d.ts +5 -4
- package/dist-client/server/service/assistant.js +35 -35
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/styling/clear-tools.d.ts +2 -2
- package/dist-client/server/service/styling/clear-tools.js.map +1 -1
- package/dist-client/server/service/styling/copy-tools.d.ts +3 -3
- package/dist-client/server/service/styling/copy-tools.js +1 -1
- package/dist-client/server/service/styling/copy-tools.js.map +1 -1
- package/dist-client/server/service/styling/effect-tools.d.ts +2 -2
- package/dist-client/server/service/styling/effect-tools.js.map +1 -1
- package/dist-client/server/service/styling/fill-tools.d.ts +3 -3
- package/dist-client/server/service/styling/fill-tools.js +1 -1
- package/dist-client/server/service/styling/fill-tools.js.map +1 -1
- package/dist-client/server/service/styling/material-tools.d.ts +2 -2
- package/dist-client/server/service/styling/material-tools.js.map +1 -1
- package/dist-client/server/service/styling/registry.d.ts +6 -6
- package/dist-client/server/service/styling/registry.js +2 -2
- package/dist-client/server/service/styling/registry.js.map +1 -1
- package/dist-client/server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
- package/dist-client/server/service/styling/text-tools.d.ts +2 -2
- package/dist-client/server/service/styling/text-tools.js.map +1 -1
- package/dist-client/server/service/types.d.ts +6 -148
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-client/server/service/validation/board-model-schema.js +4 -4
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -1
- package/dist-client/server/service/validation/tool-validation.js +5 -5
- package/dist-client/server/service/validation/tool-validation.js.map +1 -1
- package/dist-server/index.d.ts +2 -2
- package/dist-server/index.js +2 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/assistant.d.ts +5 -4
- package/dist-server/service/assistant.js +37 -37
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.js +2 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +0 -1
- package/dist-server/service/index.js +0 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
- package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
- package/dist-server/service/patch-entry/patch-entry.js +1 -1
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
- package/dist-server/service/styling/clear-tools.d.ts +2 -2
- package/dist-server/service/styling/clear-tools.js.map +1 -1
- package/dist-server/service/styling/copy-tools.d.ts +3 -3
- package/dist-server/service/styling/copy-tools.js +1 -1
- package/dist-server/service/styling/copy-tools.js.map +1 -1
- package/dist-server/service/styling/effect-tools.d.ts +2 -2
- package/dist-server/service/styling/effect-tools.js.map +1 -1
- package/dist-server/service/styling/fill-tools.d.ts +3 -3
- package/dist-server/service/styling/fill-tools.js +1 -1
- package/dist-server/service/styling/fill-tools.js.map +1 -1
- package/dist-server/service/styling/material-tools.d.ts +2 -2
- package/dist-server/service/styling/material-tools.js.map +1 -1
- package/dist-server/service/styling/registry.d.ts +6 -6
- package/dist-server/service/styling/registry.js +2 -2
- package/dist-server/service/styling/registry.js.map +1 -1
- package/dist-server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-server/service/styling/stroke-tools.js.map +1 -1
- package/dist-server/service/styling/text-tools.d.ts +2 -2
- package/dist-server/service/styling/text-tools.js.map +1 -1
- package/dist-server/service/types.d.ts +6 -148
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-server/service/validation/board-model-schema.js +4 -4
- package/dist-server/service/validation/board-model-schema.js.map +1 -1
- package/dist-server/service/validation/tool-validation.js +5 -5
- package/dist-server/service/validation/tool-validation.js.map +1 -1
- package/package.json +7 -6
- package/server/index.ts +2 -2
- package/server/service/assistant-integration.test.ts +44 -44
- package/server/service/assistant-llm-smoke.test.ts +5 -5
- package/server/service/assistant.test.ts +86 -86
- package/server/service/assistant.ts +41 -43
- package/server/service/board-ai-resolver.ts +2 -2
- package/server/service/index.ts +0 -1
- package/server/service/patch-entry/board-patch-subscription.ts +1 -1
- package/server/service/patch-entry/patch-entry.ts +2 -2
- package/server/service/styling/clear-tools.ts +2 -2
- package/server/service/styling/copy-tools.ts +3 -3
- package/server/service/styling/effect-tools.ts +2 -2
- package/server/service/styling/fill-tools.test.ts +1 -1
- package/server/service/styling/fill-tools.ts +4 -4
- package/server/service/styling/material-tools.ts +2 -2
- package/server/service/styling/registry.ts +6 -6
- package/server/service/styling/stroke-tools.test.ts +1 -1
- package/server/service/styling/stroke-tools.ts +3 -3
- package/server/service/styling/text-tools.ts +2 -2
- package/server/service/tool-exposure.test.ts +7 -7
- package/server/service/types.ts +9 -110
- package/server/service/validation/board-model-schema.test.ts +3 -3
- package/server/service/validation/board-model-schema.ts +4 -4
- package/server/service/validation/tool-validation.ts +5 -5
- package/dist-client/tsconfig.tsbuildinfo +0 -1
- package/dist-server/service/apply-patch.d.ts +0 -47
- package/dist-server/service/apply-patch.js +0 -369
- package/dist-server/service/apply-patch.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/logs/connections-2026-09-12-15.log +0 -0
- package/server/service/apply-patch-drift.test.ts +0 -831
- package/server/service/apply-patch.test.ts +0 -757
- package/server/service/apply-patch.ts +0 -411
package/server/service/types.ts
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import type { BoardComponent, BoardModel } from '@things-factory/board-import'
|
|
11
11
|
|
|
12
12
|
/* 대화 한 줄은 도메인을 모른다 — 정의는 `@things-factory/ai-assistant` 에 있다(2026-09-10). */
|
|
13
|
+
/*
|
|
14
|
+
* The edit vocabulary is not this package's. A person dragging a box produces the same
|
|
15
|
+
* operations, so they live in @operato/scene-ops with the two appliers that carry them out.
|
|
16
|
+
*/
|
|
17
|
+
import type { SceneActionOp, SceneEditPatch } from '@operato/scene-ops'
|
|
13
18
|
import type { LLMMessage } from '@things-factory/ai-assistant'
|
|
14
19
|
export type { LLMMessage }
|
|
15
20
|
|
|
@@ -121,7 +126,7 @@ import type { AIAnalysisClaim, GroundedAnalysisContract } from '@things-factory/
|
|
|
121
126
|
export type { ToolUsage }
|
|
122
127
|
|
|
123
128
|
/** Board-specific extension of the shared evidence/approval analysis boundary. */
|
|
124
|
-
export interface BoardGroundedAnalysis extends GroundedAnalysisContract<any, AIAnalysisClaim,
|
|
129
|
+
export interface BoardGroundedAnalysis extends GroundedAnalysisContract<any, AIAnalysisClaim, SceneEditPatch | SceneActionOp[]> {
|
|
125
130
|
kind: 'board.analysis.v1'
|
|
126
131
|
}
|
|
127
132
|
|
|
@@ -130,12 +135,12 @@ export interface ChatResponse {
|
|
|
130
135
|
/** 사용자에게 보여줄 텍스트 응답 */
|
|
131
136
|
reply: string
|
|
132
137
|
/** 보드 변경이 있으면 patch */
|
|
133
|
-
patch?:
|
|
138
|
+
patch?: SceneEditPatch
|
|
134
139
|
/**
|
|
135
140
|
* Scene 조작 action (ephemeral — 모델 변경 없음 / undo 영향 없음).
|
|
136
141
|
* 시간순 시퀀스로 호스트가 things-scene API 직접 실행.
|
|
137
142
|
*/
|
|
138
|
-
actions?:
|
|
143
|
+
actions?: SceneActionOp[]
|
|
139
144
|
/** 모호한 입력 시 명확화 질문 (patch 없음) */
|
|
140
145
|
followUp?: string
|
|
141
146
|
/** AI 가 응답 만드는 과정에서 호출한 도구들 (시간순). UI fold-able 박스용. */
|
|
@@ -158,119 +163,13 @@ export interface ChatResponse {
|
|
|
158
163
|
analysis?: BoardGroundedAnalysis
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
export interface BoardEditPatch {
|
|
162
|
-
ops: BoardEditOp[]
|
|
163
|
-
/** 사용자 검수용 1-2 문장 요약 */
|
|
164
|
-
summary: string
|
|
165
|
-
/** 0..1 신뢰도 */
|
|
166
|
-
confidence: number
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 보드 변경 op.
|
|
171
|
-
*
|
|
172
|
-
* 식별자 정책 — 기존 컴포넌트 타깃팅은 `refid` (number) 만 사용.
|
|
173
|
-
* things-scene 의 모든 컴포넌트는 `refid` 를 자동 발급받는다 (universal).
|
|
174
|
-
* `model.id` 는 optional metadata 일 뿐 — 항상 존재하지 않으므로 targeting 에는
|
|
175
|
-
* 부적합. 별개 개념이므로 BoardEditOp / tool 인자에서도 별개 식별자로 분리하지
|
|
176
|
-
* 않고 refid 단일 채널로 일원화.
|
|
177
|
-
*
|
|
178
|
-
* 계층 — 보드는 **최상위 부모** (things-scene 의 model-layer) 이고 그 자체로 자기
|
|
179
|
-
* 속성을 갖는다 (fillStyle, width, height, fitMode, translate, scale, sky, skyColor,
|
|
180
|
-
* exposure, hemi/dirLight 계열, camera 계열). 자식 컴포넌트와 별개. 보드 속성 변경은
|
|
181
|
-
* `modifyBoard`, 자식 변경은 `modify` (refid 기반).
|
|
182
|
-
*
|
|
183
|
-
* 주의 — `name` 은 보드 *엔티티* (DB row) 의 컬럼이지 scene MODEL 의 필드가 아니다.
|
|
184
|
-
* things-scene 의 model-layer 가 인식 안 함. modifyBoard 로 name 을 보내면 model
|
|
185
|
-
* JSON 에 죽은 필드로 박힐 뿐 렌더링/동작에 영향 없음. 보드 라벨 변경은 GraphQL
|
|
186
|
-
* boardPatch (BoardPatch input 의 name 필드) 영역.
|
|
187
|
-
*
|
|
188
|
-
* style 변경 / 이동 / 크기 변경 등 자식 컴포넌트의 변경은 모두 `modify.patch` 안에
|
|
189
|
-
* 흡수. 보드 root 속성 변경은 `modifyBoard.patch` 로.
|
|
190
|
-
*/
|
|
191
|
-
/**
|
|
192
|
-
* Phase 2 — Scene 조작 op 들. 모델 차원 (좌표 / 부모-자식 관계 / z-order) 변경이지만
|
|
193
|
-
* things-scene 의 자체 API (align/distribute/group/ungroup/zorder) 가 일관된 결과를
|
|
194
|
-
* 보장하므로 호스트가 직접 호출. 모델 차원 시뮬레이션 (apply-patch) 은 단순화 —
|
|
195
|
-
* scene 호출 결과가 정본.
|
|
196
|
-
*/
|
|
197
|
-
export type AlignDirection =
|
|
198
|
-
| 'left'
|
|
199
|
-
| 'right'
|
|
200
|
-
| 'center'
|
|
201
|
-
| 'top'
|
|
202
|
-
| 'middle'
|
|
203
|
-
| 'bottom'
|
|
204
|
-
|
|
205
|
-
export type DistributeAxis = 'horizontal' | 'vertical'
|
|
206
|
-
|
|
207
|
-
export type ZorderDirection = 'front' | 'back' | 'forward' | 'backward'
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Sugar layout — `arrange` op 의 layout 종류.
|
|
211
|
-
*
|
|
212
|
-
* 의도: align/distribute 위에 얹는 high-level 의도 표현. AI 가 "3x2 그리드로",
|
|
213
|
-
* "한 줄로", "세로로 일렬" 같은 자연어를 픽셀 노가다 없이 단일 op 로 표현.
|
|
214
|
-
*
|
|
215
|
-
* left/top 만 변경 — width/height 는 유지. AI 가 사이즈도 바꾸려면 별도 modify.
|
|
216
|
-
*
|
|
217
|
-
* 위치 계산은 호스트 (things-scene 측) 가 담당 — 각 컴포넌트의 현재 width/height 를
|
|
218
|
-
* 정확히 알아야 하므로 model 차원 시뮬레이션은 SCENE_ONLY (apply-patch noop).
|
|
219
|
-
*/
|
|
220
|
-
export type ArrangeLayout =
|
|
221
|
-
| { type: 'grid'; cols: number; gap?: number; anchor?: { left: number; top: number } }
|
|
222
|
-
| {
|
|
223
|
-
type: 'row'
|
|
224
|
-
gap?: number
|
|
225
|
-
anchor?: { left: number; top: number }
|
|
226
|
-
align?: 'start' | 'center' | 'end'
|
|
227
|
-
}
|
|
228
|
-
| {
|
|
229
|
-
type: 'column'
|
|
230
|
-
gap?: number
|
|
231
|
-
anchor?: { left: number; top: number }
|
|
232
|
-
align?: 'start' | 'center' | 'end'
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* C-1 — Scene 조작 action 들 (ephemeral). 모델 변경 X, 따라서 BoardEditOp 와 별개
|
|
237
|
-
* 채널 (board-action-execute 이벤트). undo 히스토리 미영향, dirty flag 미영향.
|
|
238
|
-
*
|
|
239
|
-
* 종류:
|
|
240
|
-
* - selectComponents: scene.selected 직접 set (사용자 선택 변경)
|
|
241
|
-
* - centerToComponent: 특정 컴포넌트로 view 이동
|
|
242
|
-
* - fitToView: 보드 전체가 보이도록 fit
|
|
243
|
-
* - setSceneMode: edit / view 모드 전환
|
|
244
|
-
*/
|
|
245
|
-
export type BoardActionOp =
|
|
246
|
-
| { action: 'selectComponents'; refids: number[] }
|
|
247
|
-
| { action: 'centerToComponent'; refid: number; animated?: boolean }
|
|
248
|
-
| { action: 'fitToView'; mode?: 'fit' | 'ratio' | 'width' | 'height' }
|
|
249
|
-
| { action: 'setSceneMode'; mode: 'edit' | 'view' }
|
|
250
|
-
/** 다중 컴포넌트 outline highlight — search/finder UX 의 "이게 모두 매칭이다" 시각화.
|
|
251
|
-
* things-scene 의 highlightSearchResults API 위임 (2D/3D 모두 지원). */
|
|
252
|
-
| { action: 'highlightComponents'; refids: number[] }
|
|
253
|
-
|
|
254
|
-
export type BoardEditOp =
|
|
255
|
-
| { op: 'add'; component: BoardComponent }
|
|
256
|
-
| { op: 'remove'; refid: number }
|
|
257
|
-
| { op: 'modify'; refid: number; patch: Partial<BoardComponent> }
|
|
258
|
-
| { op: 'modifyBoard'; patch: Partial<BoardModel> }
|
|
259
|
-
| { op: 'replace'; board: BoardModel }
|
|
260
|
-
| { op: 'align'; refids: number[]; direction: AlignDirection }
|
|
261
|
-
| { op: 'distribute'; refids: number[]; axis: DistributeAxis }
|
|
262
|
-
| { op: 'group'; refids: number[] }
|
|
263
|
-
| { op: 'ungroup'; refid: number }
|
|
264
|
-
| { op: 'zorder'; refid: number; direction: ZorderDirection }
|
|
265
|
-
| { op: 'arrange'; refids: number[]; layout: ArrangeLayout }
|
|
266
|
-
|
|
267
166
|
/**
|
|
268
167
|
* BoardAIAssistant — 자연어 채팅으로 보드를 다루는 단일 인터페이스.
|
|
269
168
|
*
|
|
270
169
|
* 사용:
|
|
271
170
|
* const ai = new DefaultBoardAIAssistant(baseClient, { scopes: ['fmsim'] })
|
|
272
171
|
* const r = await ai.chat([{ role: 'user', content: 'AGV 3대 추가' }], currentBoard)
|
|
273
|
-
* if (r.patch) currentBoard =
|
|
172
|
+
* if (r.patch) currentBoard = applyScenePatch(currentBoard, r.patch)
|
|
274
173
|
*/
|
|
275
174
|
export interface BoardAIAssistant {
|
|
276
175
|
readonly id: string
|
|
@@ -622,7 +622,7 @@ describe('실 사용자 발견 시나리오 (회귀 방지)', () => {
|
|
|
622
622
|
})
|
|
623
623
|
})
|
|
624
624
|
|
|
625
|
-
describe('BoardPatchSchema —
|
|
625
|
+
describe('BoardPatchSchema — modifyScene 의 patch 검증', () => {
|
|
626
626
|
test('빈 patch 통과 (모든 필드 optional)', () => {
|
|
627
627
|
expect(BoardPatchSchema.safeParse({}).success).toBe(true)
|
|
628
628
|
})
|
|
@@ -650,7 +650,7 @@ describe('BoardPatchSchema — modifyBoard 의 patch 검증', () => {
|
|
|
650
650
|
})
|
|
651
651
|
|
|
652
652
|
test('★ fillStyle 안 gradientStops (stale 이름) — 거부 (FillStyleSchema strict)', () => {
|
|
653
|
-
// 본 사고의 핵심 회귀 방지 —
|
|
653
|
+
// 본 사고의 핵심 회귀 방지 — modifyScene 로 gradientStops 흘러들어오면 catch
|
|
654
654
|
const r = BoardPatchSchema.safeParse({
|
|
655
655
|
fillStyle: {
|
|
656
656
|
type: 'gradient',
|
|
@@ -682,7 +682,7 @@ describe('BoardPatchSchema — modifyBoard 의 patch 검증', () => {
|
|
|
682
682
|
})
|
|
683
683
|
|
|
684
684
|
test('shadow nested unknown key — 거부 (ShadowSchema strict)', () => {
|
|
685
|
-
// 다른 alias leak 시나리오 — setShadow 의 옛 이름 'blur' 가
|
|
685
|
+
// 다른 alias leak 시나리오 — setShadow 의 옛 이름 'blur' 가 modifyScene 를 통해
|
|
686
686
|
// shadow 안에 박히려 할 때 (실제로는 root 에 shadow 가 없지만 검증 흐름 보존)
|
|
687
687
|
const r = BoardPatchSchema.safeParse({
|
|
688
688
|
shadow: { blur: 8, offsetX: 4 } as any
|
|
@@ -336,7 +336,7 @@ export const ComponentSchema = ComponentBaseSchema.superRefine((component, ctx)
|
|
|
336
336
|
* • 식별: id, refid, type
|
|
337
337
|
*
|
|
338
338
|
* ⚠ `name` 은 things-scene model-layer 가 인식 안 함 — Board *엔티티* 의 컬럼이지
|
|
339
|
-
* scene MODEL 의 필드가 아님. AI 가
|
|
339
|
+
* scene MODEL 의 필드가 아님. AI 가 modifyScene 로 name 을 보내도 죽은 필드. 검증
|
|
340
340
|
* 시에도 제거 / 무시 정책.
|
|
341
341
|
*
|
|
342
342
|
* `passthrough` 인 이유 — 3D 광원/카메라/sky 등 키가 너무 많아 strict 로는 회귀 위험
|
|
@@ -384,14 +384,14 @@ export const ComponentPatchSchema = z
|
|
|
384
384
|
.passthrough() // 위치 / 크기 / 컴포넌트 specific 통과
|
|
385
385
|
|
|
386
386
|
/**
|
|
387
|
-
*
|
|
387
|
+
* modifyScene op 의 patch — 보드 root partial.
|
|
388
388
|
*
|
|
389
389
|
* 모든 root 필드 optional (BoardModelSchema 와 달리 width/height 도 optional).
|
|
390
390
|
* passthrough 로 알려지지 않은 root 키 (3D atmosphere, sky, camera 등) 통과.
|
|
391
391
|
* 다만 *알려진 nested object* (fillStyle 등) 는 strict schema 재사용 — 그 안에
|
|
392
392
|
* unknown key 가 들어오면 거부 (예: fillStyle 안 gradientStops 같은 stale 이름).
|
|
393
393
|
*
|
|
394
|
-
* AI 가 setFill 등에서 학습한 vocabulary 를
|
|
394
|
+
* AI 가 setFill 등에서 학습한 vocabulary 를 modifyScene 로 echo 시킬 때 stale
|
|
395
395
|
* key 가 fillStyle / shadow 같은 sub-object 안에 끼면 본 schema 가 catch.
|
|
396
396
|
*/
|
|
397
397
|
export const BoardPatchSchema = z
|
|
@@ -438,7 +438,7 @@ export function validateComponent(component: unknown): ValidationResult {
|
|
|
438
438
|
return { valid: false, errors: formatIssues(r.error) }
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
/**
|
|
441
|
+
/** modifyScene op 의 patch 검증. 보드 root partial. */
|
|
442
442
|
export function validateBoardPatch(patch: unknown): ValidationResult {
|
|
443
443
|
const r = BoardPatchSchema.safeParse(patch)
|
|
444
444
|
if (r.success) return { valid: true }
|
|
@@ -147,7 +147,7 @@ export function validateWriteToolCall(
|
|
|
147
147
|
}
|
|
148
148
|
return { valid: true }
|
|
149
149
|
}
|
|
150
|
-
case '
|
|
150
|
+
case 'modifyScene': {
|
|
151
151
|
// 보드 root partial 검증 — fillStyle/shadow 같은 nested 의 strict schema 가
|
|
152
152
|
// unknown key (e.g. fillStyle.gradientStops 같은 stale 이름) 를 거부.
|
|
153
153
|
// 이게 마지막 방어선 — 다른 styling tool 에서 학습된 vocabulary 가 leak 되면 catch.
|
|
@@ -159,7 +159,7 @@ export function validateWriteToolCall(
|
|
|
159
159
|
return {
|
|
160
160
|
valid: false,
|
|
161
161
|
errors: formatValidationErrors(v),
|
|
162
|
-
suggestion:
|
|
162
|
+
suggestion: modifySceneSuggestion()
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
return { valid: true }
|
|
@@ -174,7 +174,7 @@ export function validateWriteToolCall(
|
|
|
174
174
|
return {
|
|
175
175
|
valid: false,
|
|
176
176
|
errors: formatValidationErrors(v),
|
|
177
|
-
suggestion:
|
|
177
|
+
suggestion: modifySceneSuggestion()
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
return { valid: true }
|
|
@@ -186,9 +186,9 @@ export function validateWriteToolCall(
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
function
|
|
189
|
+
function modifySceneSuggestion(): string {
|
|
190
190
|
return (
|
|
191
|
-
'
|
|
191
|
+
'modifyScene 의 patch 는 things-scene 의 model-layer 가 인식하는 root key 만 사용. ' +
|
|
192
192
|
'fillStyle / strokeStyle / shadow 같은 nested object 의 안쪽 필드는 canonical 이름 (colorStops 등) 정확히 사용. ' +
|
|
193
193
|
'rect 등 자식 컴포넌트 변경은 modifyComponentByRefid 로 분리.'
|
|
194
194
|
)
|