@things-factory/figure-service 10.1.210 → 10.1.212
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/README.md +1 -1
- package/dist-server/service/figure/event-subscriber.js +2 -2
- package/dist-server/service/figure/event-subscriber.js.map +1 -1
- package/dist-server/service/figure/figure-format.js +1 -1
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +5 -5
- package/dist-server/service/figure/figure-mutation.js +6 -6
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-query.d.ts +3 -3
- package/dist-server/service/figure/figure-query.js +4 -4
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-rename.d.ts +4 -4
- package/dist-server/service/figure/figure-rename.js +2 -2
- package/dist-server/service/figure/figure-rename.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +9 -9
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.d.ts +2 -2
- package/dist-server/service/figure/figure-type.js +2 -2
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +2 -2
- package/dist-server/service/figure/figure.js +1 -1
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/recipes/belt-surface/guide.ko.md +1 -1
- package/recipes/roller-row/facts.md +1 -1
- package/recipes/roller-row/guide.ko.md +1 -1
- package/recipes/sliding-door/facts.md +1 -1
- package/recipes/sliding-door/guide.ko.md +1 -1
- package/server/service/figure/catalog-db.test.ts +1 -1
- package/server/service/figure/event-subscriber.ts +2 -2
- package/server/service/figure/figure-blueprint-query.test.ts +3 -3
- package/server/service/figure/figure-format.test.ts +7 -7
- package/server/service/figure/figure-format.ts +1 -1
- package/server/service/figure/figure-llm-smoke.test.ts +1 -1
- package/server/service/figure/figure-mutation.ts +6 -6
- package/server/service/figure/figure-propose-steps.test.ts +2 -2
- package/server/service/figure/figure-query.ts +4 -4
- package/server/service/figure/figure-rename-db.test.ts +2 -2
- package/server/service/figure/figure-rename.test.ts +3 -3
- package/server/service/figure/figure-rename.ts +6 -6
- package/server/service/figure/figure-subject.test.ts +1 -1
- package/server/service/figure/figure-tools.test.ts +19 -19
- package/server/service/figure/figure-tools.ts +9 -9
- package/server/service/figure/figure-type.ts +2 -2
- package/server/service/figure/figure.ts +2 -2
- package/translations/ko.json +31 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.212",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@hatiolab/figure-model": "^0.11.0",
|
|
29
29
|
"@operato/typeorm-history": "^10.0.0",
|
|
30
|
-
"@things-factory/ai-client-base": "^10.1.
|
|
31
|
-
"@things-factory/attachment-base": "^10.1.
|
|
32
|
-
"@things-factory/auth-base": "^10.1.
|
|
30
|
+
"@things-factory/ai-client-base": "^10.1.212",
|
|
31
|
+
"@things-factory/attachment-base": "^10.1.212",
|
|
32
|
+
"@things-factory/auth-base": "^10.1.212",
|
|
33
33
|
"@things-factory/env": "^10.1.149",
|
|
34
|
-
"@things-factory/setting-base": "^10.1.
|
|
34
|
+
"@things-factory/setting-base": "^10.1.212",
|
|
35
35
|
"@things-factory/shell": "^10.1.208",
|
|
36
36
|
"graphql-scalars": "^1.22.4",
|
|
37
37
|
"graphql-upload": "^15.0.2",
|
|
38
38
|
"jszip": "^3.10.1",
|
|
39
39
|
"koa-router": "^7.4.0"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "1e1cdb79fca2915580429af451b6cf1dae653a93"
|
|
42
42
|
}
|
|
@@ -121,7 +121,7 @@ describe('태그가 정말 써진다', () => {
|
|
|
121
121
|
expect((await readBack(saved.id))!.tags).toEqual([])
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
it('안 준
|
|
124
|
+
it('안 준 피규어는 태그가 없다 — 빈 배열로 갈음하지 않는다', async () => {
|
|
125
125
|
const saved = await seed()
|
|
126
126
|
|
|
127
127
|
expect((await readBack(saved.id))!.tags).toBeNull()
|
|
@@ -8,14 +8,14 @@ import { FigureHistory } from './figure-history.js'
|
|
|
8
8
|
/**
|
|
9
9
|
* 판본을 남긴다 — **발행된 저장에만.**
|
|
10
10
|
*
|
|
11
|
-
* `@operato/typeorm-history` 는 원래 모든 저장을 베낀다. 그런데
|
|
11
|
+
* `@operato/typeorm-history` 는 원래 모든 저장을 베낀다. 그런데 피규어는 저작 중에 수십 번 저장되고,
|
|
12
12
|
* 그 전부를 남기면 판본 목록이 「내가 오늘 몇 번 눌렀나」가 된다. 사람이 판이라고 부르는 것은
|
|
13
13
|
* **발행한 것**이므로 그때만 베낀다. board 가 같은 규칙으로 돈다(`board-service` 의 같은 이름 파일).
|
|
14
14
|
*
|
|
15
15
|
* 발행판의 정본(`__releaseSource`)은 크기 범위를 적은 발행판 사본이다 — Figure 행에는 작성자가 좁힌 끝만 남는다.
|
|
16
16
|
*
|
|
17
17
|
* 발행 메모는 Figure 의 칸이 아니다. `releaseFigure` 가 저장할 엔티티에 `__releaseComment` 로 얹어
|
|
18
|
-
* 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지
|
|
18
|
+
* 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지 피규어의 것이 아니다. 정본 형식
|
|
19
19
|
* (`__releaseFormat`), 잰 커널의 판(`__releaseKernel`, ADR-0109 C1), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과
|
|
20
20
|
* 윗모습(`__releaseTopView`)도 같은 길로 옮긴다(ADR-0104).
|
|
21
21
|
*/
|
|
@@ -100,7 +100,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)에 발
|
|
|
100
100
|
await repo.save({
|
|
101
101
|
domain: testDomain,
|
|
102
102
|
type: 'DRAFT_FIGURE',
|
|
103
|
-
name: '초안
|
|
103
|
+
name: '초안 피규어',
|
|
104
104
|
state: 'draft',
|
|
105
105
|
source: sampleSource,
|
|
106
106
|
version: 0
|
|
@@ -116,7 +116,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)에 발
|
|
|
116
116
|
await repo.save({
|
|
117
117
|
domain: testDomain,
|
|
118
118
|
type: 'RELEASED_FIGURE',
|
|
119
|
-
name: '발행
|
|
119
|
+
name: '발행 피규어',
|
|
120
120
|
state: 'released',
|
|
121
121
|
source: sampleSource,
|
|
122
122
|
version: 1
|
|
@@ -141,7 +141,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)에 발
|
|
|
141
141
|
const figure = await repo.save({
|
|
142
142
|
domain: testDomain,
|
|
143
143
|
type: 'UPDATING_FIGURE',
|
|
144
|
-
name: '수정 중인
|
|
144
|
+
name: '수정 중인 피규어',
|
|
145
145
|
state: 'draft',
|
|
146
146
|
source: draftSource,
|
|
147
147
|
version: 1
|
|
@@ -158,8 +158,8 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
158
158
|
})
|
|
159
159
|
|
|
160
160
|
/*
|
|
161
|
-
(2026-09-24) 드라이룸 램프처럼 — 붙어 있던 부품이
|
|
162
|
-
여기서는 손으로 만든다: 램프의 높이가
|
|
161
|
+
(2026-09-24) 드라이룸 램프처럼 — 붙어 있던 부품이 피규어를 키우면 떨어진다.
|
|
162
|
+
여기서는 손으로 만든다: 램프의 높이가 피규어 높이(size.y)를 따라 올라가고, 몸통은 그대로다.
|
|
163
163
|
*/
|
|
164
164
|
const splitting = () => {
|
|
165
165
|
let asset = createFigureAsset('Lamp post')
|
|
@@ -185,8 +185,8 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
185
185
|
expect(`${finding!.message}${finding!.why}${finding!.how}`).not.toMatch(/come away|piece/)
|
|
186
186
|
})
|
|
187
187
|
|
|
188
|
-
/* 한 비율로 커진다고 선언한
|
|
189
|
-
it('한 비율로 커진다고 선언한
|
|
188
|
+
/* 한 비율로 커진다고 선언한 피규어에서 제 숫자를 가진 부품(수석 아키텍트 판정 2026-09-24 조건 ②). */
|
|
189
|
+
it('한 비율로 커진다고 선언한 피규어에서 따라 커지지 않는 부품을 막고 이름을 댄다', () => {
|
|
190
190
|
let asset = createFigureAsset('Uniform')
|
|
191
191
|
asset = addFigureBox(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
192
192
|
for (const axis of ['x', 'y', 'z']) {
|
|
@@ -197,7 +197,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
197
197
|
const finding = inspectSource(serializeFigureAsset(asset), null, translatorOf(KO)).findings.find(f => f.code === 'sizing-not-uniform')
|
|
198
198
|
|
|
199
199
|
expect(finding?.blocking).toBe(true)
|
|
200
|
-
expect(finding!.message).toBe("이
|
|
200
|
+
expect(finding!.message).toBe("이 피규어는 한 비율로 커지는데, 크기를 X 로 2배로 하면 'body' 부품은 같이 커지지 않습니다.")
|
|
201
201
|
expect(finding!.how).toContain('크기에 비례')
|
|
202
202
|
})
|
|
203
203
|
|
|
@@ -234,10 +234,10 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
234
234
|
})
|
|
235
235
|
|
|
236
236
|
/*
|
|
237
|
-
자산에는 type 이 없어 임시 이름 검사가 돌지 않았고, 새
|
|
237
|
+
자산에는 type 이 없어 임시 이름 검사가 돌지 않았고, 새 피규어가 `FIGURE_<uuid>` 로 발행됐다(:3300, 2026-09-24).
|
|
238
238
|
발행하면 이름이 굳으므로 행의 type 으로 거절한다.
|
|
239
239
|
*/
|
|
240
|
-
it('임시 이름인
|
|
240
|
+
it('임시 이름인 피규어는 발행을 막는다 — 이름은 행에 있다', () => {
|
|
241
241
|
/* The name a new figure gets — the format's rule, the one the gate recognises. */
|
|
242
242
|
const placeholder = placeholderType()
|
|
243
243
|
const finding = inspectSource(figureSource(), null, translatorOf(KO), placeholder).findings.find(f => f.code === 'type-is-placeholder')
|
|
@@ -107,7 +107,7 @@ function assertFormat(sourceJson: string): void {
|
|
|
107
107
|
}
|
|
108
108
|
if ((parsed as { version?: unknown } | null)?.version !== FIGURE_ASSET_VERSION)
|
|
109
109
|
throw new FigureSourceError([
|
|
110
|
-
{ code: 'source-format-unknown', message: `정본의 판을 알 수 없다 —
|
|
110
|
+
{ code: 'source-format-unknown', message: `정본의 판을 알 수 없다 — 피규어 자산은 version '${FIGURE_ASSET_VERSION}' 을 적는다`, at: 'version' }
|
|
111
111
|
])
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 실제 LLM Figure 후보 생성 smoke test (기본 비활성).
|
|
3
3
|
*
|
|
4
4
|
* 결정적 시험은 프롬프트·도구·검사 배선을 고정하지만, 실제 모델이 명령 목록(ADR-0094)을 써서 게이트를
|
|
5
|
-
* 지나는
|
|
5
|
+
* 지나는 피규어를 만드는지는 대신할 수 없다. 이 시험은 `RUN_FIGURE_LLM_SMOKE=1`과 공급자 API 키가 모두 있을 때만 실행한다.
|
|
6
6
|
* DB·GraphQL·Figure 저장을 건드리지 않고 후보 생성 함수만 호출하므로, 외부 영향은 API 비용뿐이다.
|
|
7
7
|
*
|
|
8
8
|
* 실행:
|
|
@@ -273,7 +273,7 @@ export class FigureMutation {
|
|
|
273
273
|
**타입은 발행 전에만 바뀐다** — ADR-0063 결정 4.
|
|
274
274
|
|
|
275
275
|
발행된 뒤에는 이미 배치된 도면이 이 이름을 가리키고, 바꾸면 그 도면이 아무 오류 없이
|
|
276
|
-
빈 자리가 된다. 발행된 적 없는
|
|
276
|
+
빈 자리가 된다. 발행된 적 없는 피규어는 가리키는 도면이 있을 수 없다 — 도면은 최신
|
|
277
277
|
발행판을 그리므로 그려질 자리가 없다.
|
|
278
278
|
|
|
279
279
|
행과 정본 둘 다 고친다. 정본 안에도 `type` 이 있어서 한쪽만 고치면 저장된 JSON 과
|
|
@@ -358,7 +358,7 @@ export class FigureMutation {
|
|
|
358
358
|
*
|
|
359
359
|
* ## 무엇이 일어나나
|
|
360
360
|
*
|
|
361
|
-
* `state` 가 `released` 가 되고, 판 번호가 하나 오르고, 그 순간의
|
|
361
|
+
* `state` 가 `released` 가 되고, 판 번호가 하나 오르고, 그 순간의 피규어가 판본으로 남는다
|
|
362
362
|
* (`FigureHistory` — 스냅샷은 구독자가 뜬다). 저작 중 저장은 이 셋 중 아무것도 하지 않는다.
|
|
363
363
|
*
|
|
364
364
|
* ## 왜 이미 발행된 것을 거절하나
|
|
@@ -368,12 +368,12 @@ export class FigureMutation {
|
|
|
368
368
|
*
|
|
369
369
|
* ## 번호는 판본에서 센다
|
|
370
370
|
*
|
|
371
|
-
*
|
|
371
|
+
* 피규어의 현재 번호가 아니라 **판본의 최대 번호 + 1** 이다. 되돌리기로 옛 판이 현재가 되어도
|
|
372
372
|
* 다음 발행이 이미 쓴 번호를 다시 쓰지 않는다.
|
|
373
373
|
*
|
|
374
374
|
* ## 게이트를 통과해야 한다
|
|
375
375
|
*
|
|
376
|
-
* 크기를 바꿨을 때 조각이 갈라지는
|
|
376
|
+
* 크기를 바꿨을 때 조각이 갈라지는 피규어는 발행되지 않는다(`figure-gate.ts`). 소비처는 형상을
|
|
377
377
|
* 늘려 쓰므로 — 그것이 이 형식의 값이다 — 늘려도 견디는지가 약속의 일부다. 화면은 같은 판정을
|
|
378
378
|
* `inspectFigure` 로 미리 물어 이유를 보여 준다.
|
|
379
379
|
*/
|
|
@@ -473,7 +473,7 @@ export class FigureMutation {
|
|
|
473
473
|
*
|
|
474
474
|
* ## 비용은 다시 잰다
|
|
475
475
|
*
|
|
476
|
-
* 판본이 든 점수·삼각형은 **그때의 형식**으로 잰 값이다. 지금 이
|
|
476
|
+
* 판본이 든 점수·삼각형은 **그때의 형식**으로 잰 값이다. 지금 이 피규어가 그 내용이 되었으므로
|
|
477
477
|
* 오늘 기준으로 다시 계산한다. 옛 값은 판본에 그대로 남아 있다.
|
|
478
478
|
*/
|
|
479
479
|
@Directive('@transaction')
|
|
@@ -592,7 +592,7 @@ export class FigureMutation {
|
|
|
592
592
|
/**
|
|
593
593
|
* 저작 보조에게 제안을 시킨다(ADR-0094).
|
|
594
594
|
*
|
|
595
|
-
* **저장하지 않는다.** 모델러 명령 목록과 그것이 만드는
|
|
595
|
+
* **저장하지 않는다.** 모델러 명령 목록과 그것이 만드는 피규어를 돌려줄 뿐이고, 정본이 되는 것은 저작자가 모델러에
|
|
596
596
|
* 적용하고 저장한 뒤다. 커널이 거절한 명령이나 게이트가 잡은 것은 여기까지 오지 않는다 — 제안기가 사유를 모델에
|
|
597
597
|
* 돌려주고 다시 시킨다. 끝내 못 만들면 **사유와 함께 던진다.** 반쯤 만든 것을 돌려주지 않는다.
|
|
598
598
|
*/
|
|
@@ -256,8 +256,8 @@ describe('a failure told to a person', () => {
|
|
|
256
256
|
|
|
257
257
|
it('says the first line in the reader\'s language, the reasons as they were', () => {
|
|
258
258
|
const said: any[] = []
|
|
259
|
-
const text = proposalFailureForPerson(failure, (key, params) => (said.push([key, params]), '
|
|
260
|
-
expect(text).toBe('
|
|
259
|
+
const text = proposalFailureForPerson(failure, (key, params) => (said.push([key, params]), '피규어를 만들지 못했습니다.'))
|
|
260
|
+
expect(text).toBe('피규어를 만들지 못했습니다.\nATTACH_FACE: steps[4]: attach: faces along x')
|
|
261
261
|
expect(said).toEqual([['error.figure-proposal-failed', { defaultValue: 'The figure could not be made.' }]])
|
|
262
262
|
})
|
|
263
263
|
|
|
@@ -53,7 +53,7 @@ export class FigureQuery {
|
|
|
53
53
|
|
|
54
54
|
`simple-json` 이라 칸에는 `["반출","자동"]` 이 통째로 들어 있고, LIKE 는 그 글자를
|
|
55
55
|
본다. 「반출」로 찾으면 걸린다. 정확히 그 태그만 걸리는 것은 아니지만(이름에 「반출」이
|
|
56
|
-
든
|
|
56
|
+
든 피규어도 걸린다) 찾기로는 그것이 오히려 낫다.
|
|
57
57
|
*/
|
|
58
58
|
searchables: ['type', 'name', 'description', 'category', 'tags']
|
|
59
59
|
})
|
|
@@ -97,7 +97,7 @@ export class FigureQuery {
|
|
|
97
97
|
* 발행된 판들 — 최근 것부터.
|
|
98
98
|
*
|
|
99
99
|
* 저작 중 저장은 여기 없다. 판본은 발행에서만 생기므로(`releaseFigure`), 이 목록이 곧
|
|
100
|
-
* 「이
|
|
100
|
+
* 「이 피규어가 몇 번 약속되었나」다.
|
|
101
101
|
*/
|
|
102
102
|
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
103
103
|
@Query(returns => [FigureHistory], { description: 'Released versions of a figure, newest first.' })
|
|
@@ -117,7 +117,7 @@ export class FigureQuery {
|
|
|
117
117
|
/**
|
|
118
118
|
* 지금 소비처가 쓰는 판.
|
|
119
119
|
*
|
|
120
|
-
*
|
|
120
|
+
* 피규어의 현재 정본은 저작 중일 수 있다(초안). 배치된 것이 보는 것은 **마지막으로 발행된 판**이고,
|
|
121
121
|
* 그것을 이 자리가 답한다.
|
|
122
122
|
*/
|
|
123
123
|
/** What a kept GLB holds, read again from its bytes (ADR-0129): the modeller asks when it reopens a figure. */
|
|
@@ -254,7 +254,7 @@ export class FigureQuery {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
|
-
* 소비처(operato-board, operato-twin, things-scene)가 발행된
|
|
257
|
+
* 소비처(operato-board, operato-twin, things-scene)가 발행된 피규어를 받아 가는 문.
|
|
258
258
|
*
|
|
259
259
|
* 이름은 보드와의 계약이라 그대로 둔다(바꾸는 일은 따로 한다). 돌려주는 것은 자산이다 —
|
|
260
260
|
* `{ type, asset }`. 그리는 쪽(`registerFigure`)이 커널로 컴파일한다.
|
|
@@ -79,7 +79,7 @@ async function namespaced(domain: any) {
|
|
|
79
79
|
|
|
80
80
|
let DOMAIN: { id: string }
|
|
81
81
|
|
|
82
|
-
/*
|
|
82
|
+
/* 피규어가 도메인을 가리킨다 — 행이 없으면 외래키가 먼저 막는다. */
|
|
83
83
|
async function seedDomain() {
|
|
84
84
|
const saved = await namespaced(await dataSource!.getRepository(shell.Domain).save({ name: 'rename-probe', subdomain: 'rename-probe' }))
|
|
85
85
|
DOMAIN = { id: saved.id }
|
|
@@ -127,7 +127,7 @@ describe('이름을 바꾸기 전에 읽는 사실', () => {
|
|
|
127
127
|
expect((await facts(figure, 'robot-arm')).hasHistory).toBe(true)
|
|
128
128
|
})
|
|
129
129
|
|
|
130
|
-
it('같은 도메인에서 그 이름을 쓰는 다른
|
|
130
|
+
it('같은 도메인에서 그 이름을 쓰는 다른 피규어를 찾는다', async () => {
|
|
131
131
|
const figure = await seedFigure()
|
|
132
132
|
await seedFigure({ type: 'conveyor', name: '컨베이어' })
|
|
133
133
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 초안의 타입을 고치는 규칙 — ADR-0063 결정 4.
|
|
3
3
|
*
|
|
4
|
-
* 이 규칙이 지키는 것은 하나다: **발행된 이름은 안 바뀐다.** 도면은 그 이름 하나로
|
|
4
|
+
* 이 규칙이 지키는 것은 하나다: **발행된 이름은 안 바뀐다.** 도면은 그 이름 하나로 피규어를
|
|
5
5
|
* 찾으므로, 바뀌면 배치된 자리가 아무 오류 없이 빈다.
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync } from 'fs'
|
|
@@ -21,7 +21,7 @@ const never = { version: 0, hasHistory: false, takenByAnother: false }
|
|
|
21
21
|
|
|
22
22
|
describe('초안의 type 고치기', () => {
|
|
23
23
|
/*
|
|
24
|
-
자산에는 type 칸이 없고 형식은 모르는 칸을 거절한다. 원본에 이름을 써 넣었더니 그
|
|
24
|
+
자산에는 type 칸이 없고 형식은 모르는 칸을 거절한다. 원본에 이름을 써 넣었더니 그 피규어를 다시 열 수
|
|
25
25
|
없었다(`parseFigureAsset` — "unknown type"). 행만 바뀐다.
|
|
26
26
|
*/
|
|
27
27
|
it('★ 행만 바뀌고, 원본은 그대로 다시 열린다', () => {
|
|
@@ -97,7 +97,7 @@ describe('문이 이 규칙을 지난다', () => {
|
|
|
97
97
|
저장이 임시 이름을 스스로 지었다(`FIGURE_<32 hex>`). 형식의 `isPlaceholderType` 이 그것을 못 알아봐서
|
|
98
98
|
발행 판정이 기계 이름을 통과시켰다. 짓는 쪽과 알아보는 쪽이 한 규칙이어야 한다.
|
|
99
99
|
*/
|
|
100
|
-
/* 새
|
|
100
|
+
/* 새 피규어의 임시 이름이 판정이 알아보는 그 규칙인지는 문을 실제로 불러 본다 — figure-rename-db.test.ts 「이름 없이 부르면」. */
|
|
101
101
|
|
|
102
102
|
it('문이 type 을 받는 칸을 갖는다', () => {
|
|
103
103
|
const type = source('figure-type.ts')
|
|
@@ -6,8 +6,8 @@ import { TYPE_LENGTH, TYPE_PATTERN } from '@hatiolab/figure-model'
|
|
|
6
6
|
* ## 왜 발행 전에만인가
|
|
7
7
|
*
|
|
8
8
|
* `type` 은 도면에 적히는 이름이다. 도면은 `{ type, left, top, width, height }` 만 갖고 늘 최신
|
|
9
|
-
* 발행판을 그리므로, 발행된
|
|
10
|
-
* 된다. 한 번도 발행되지 않은
|
|
9
|
+
* 발행판을 그리므로, 발행된 피규어의 이름을 바꾸면 이미 배치된 도면이 아무 오류 없이 빈 자리가
|
|
10
|
+
* 된다. 한 번도 발행되지 않은 피규어는 그려질 자리가 없으니 가리키는 도면도 있을 수 없다.
|
|
11
11
|
*
|
|
12
12
|
* ## 왜 열어야 했나
|
|
13
13
|
*
|
|
@@ -28,10 +28,10 @@ export interface RenameFacts {
|
|
|
28
28
|
version?: number
|
|
29
29
|
/** 판본이 하나라도 있나. 되돌리기로 판 번호가 도로 낮아져도 이력은 남는다. */
|
|
30
30
|
hasHistory: boolean
|
|
31
|
-
/** 같은 도메인에서 그 이름을 이미 쓰는 **다른**
|
|
31
|
+
/** 같은 도메인에서 그 이름을 이미 쓰는 **다른** 피규어가 있나. */
|
|
32
32
|
takenByAnother: boolean
|
|
33
33
|
/**
|
|
34
|
-
* 같은 도메인에서 그 이름으로 발행한 뒤 지워진
|
|
34
|
+
* 같은 도메인에서 그 이름으로 발행한 뒤 지워진 피규어가 있나. 발행한 피규어를 지울 수 없게 되기(G4) 전에 지워진 것이다.
|
|
35
35
|
* 그 이름을 다시 쓰면 판 번호가 1부터 다시 매겨져 같은 `ns/type@n` 이 다른 내용을 가리킨다.
|
|
36
36
|
*/
|
|
37
37
|
releasedBefore?: boolean
|
|
@@ -57,7 +57,7 @@ export async function typeReleasedBefore(
|
|
|
57
57
|
|
|
58
58
|
/** Said when a type was released and deleted before: a new figure may not take it. */
|
|
59
59
|
export const releasedBeforeMessage = (type: string) =>
|
|
60
|
-
`type 「${type}」 는 이 도메인에서 발행한 뒤 지운
|
|
60
|
+
`type 「${type}」 는 이 도메인에서 발행한 뒤 지운 피규어의 이름이다 — 다시 쓰면 같은 판 번호가 다른 내용을 가리키므로 다른 이름을 고른다`
|
|
61
61
|
|
|
62
62
|
export class FigureRenameRefused extends Error {
|
|
63
63
|
constructor(
|
|
@@ -100,7 +100,7 @@ export function renameDraftType(
|
|
|
100
100
|
if ((facts.version ?? 0) > 0 || facts.hasHistory) {
|
|
101
101
|
throw new FigureRenameRefused(
|
|
102
102
|
'already-released',
|
|
103
|
-
'발행된
|
|
103
|
+
'발행된 피규어의 type 은 바꿀 수 없다 — 이미 배치된 도면이 이 이름을 가리킨다'
|
|
104
104
|
)
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const mockFigures: any[] = [
|
|
5
5
|
{ id: 'f1', domain: 'plant-a', name: '선풍기', type: 'system/fan' },
|
|
6
6
|
{ id: 'f2', domain: 'plant-a', name: '', type: 'system/agv-lift' },
|
|
7
|
-
{ id: 'f3', domain: 'plant-b', name: '남의
|
|
7
|
+
{ id: 'f3', domain: 'plant-b', name: '남의 피규어', type: 'other/one' }
|
|
8
8
|
]
|
|
9
9
|
const mockRegistered: any[] = []
|
|
10
10
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 나타나는 증상이 실제로 겪은 실패와 1:1 로 대응한다.
|
|
7
7
|
*
|
|
8
8
|
* · guidance → 도구 호출 끊기면 → 「후보를 만들었습니다」라고만 쓰고 화면에는 아무것도 없다
|
|
9
|
-
* · hostContext.figureId → 대상 끊기면 → 화면에
|
|
9
|
+
* · hostContext.figureId → 대상 끊기면 → 화면에 피규어가 떠 있는데 「어느 피규어입니까」를 되묻는다
|
|
10
10
|
* · 도메인 확인 → 대상 끊기면 → 모델이 넘긴 id 로 남의 테넌트를 읽는다
|
|
11
11
|
* · hostContext.context·revision → 제안 끊기면 → 모델러가 모델이 본 판이 아닌 판에 명령을 적용한다
|
|
12
12
|
*
|
|
@@ -301,7 +301,7 @@ describe('모델이 읽는 글은 영어다', () => {
|
|
|
301
301
|
})
|
|
302
302
|
|
|
303
303
|
describe('대화면 문맥에서 대상을 해소한다', () => {
|
|
304
|
-
it('id 를 안 주면 열려 있는
|
|
304
|
+
it('id 를 안 주면 열려 있는 피규어를 읽는다 — 사용자에게 되묻지 않는다', async () => {
|
|
305
305
|
const result = await toolNamed('getFigureSource').builder({}, ctx())
|
|
306
306
|
|
|
307
307
|
expect(result.id).toBe('fig-1')
|
|
@@ -316,7 +316,7 @@ describe('대화면 문맥에서 대상을 해소한다', () => {
|
|
|
316
316
|
expect(result.source).toBeUndefined()
|
|
317
317
|
})
|
|
318
318
|
|
|
319
|
-
it('목록에서 대상을 찾아 다시 호출하면 해당
|
|
319
|
+
it('목록에서 대상을 찾아 다시 호출하면 해당 피규어의 정본을 읽는다', async () => {
|
|
320
320
|
const catalog = await toolNamed('listFigures').builder({}, ctx({}))
|
|
321
321
|
const target = catalog.figures.find((figure: any) => figure.name === '믹서')
|
|
322
322
|
const result = await toolNamed('getFigureSource').builder({ figureId: target.id }, ctx({}))
|
|
@@ -324,7 +324,7 @@ describe('대화면 문맥에서 대상을 해소한다', () => {
|
|
|
324
324
|
expect(result.source.name).toBe('Mixer')
|
|
325
325
|
})
|
|
326
326
|
|
|
327
|
-
it('정본이 없는
|
|
327
|
+
it('정본이 없는 피규어는 없다고 말한다 — 지어내지 않는다', async () => {
|
|
328
328
|
const result = await toolNamed('getFigureSource').builder({ figureId: 'fig-blank' }, ctx())
|
|
329
329
|
|
|
330
330
|
expect(result.source).toBeNull()
|
|
@@ -344,7 +344,7 @@ describe('대화면 문맥에서 대상을 해소한다', () => {
|
|
|
344
344
|
있고, 그것을 그대로 믿으면 남의 테넌트를 읽는다.
|
|
345
345
|
*/
|
|
346
346
|
describe('모델이 넘긴 id 를 믿지 않는다', () => {
|
|
347
|
-
it('다른 도메인의
|
|
347
|
+
it('다른 도메인의 피규어는 못 찾았다고 한다', async () => {
|
|
348
348
|
await expect(toolNamed('getFigureSource').builder({ figureId: 'fig-other' }, ctx())).rejects.toThrow(
|
|
349
349
|
/not found in this tenant/
|
|
350
350
|
)
|
|
@@ -362,7 +362,7 @@ describe('모델이 넘긴 id 를 믿지 않는다', () => {
|
|
|
362
362
|
expect(result.figures.map((one: any) => one.id)).toEqual(['fig-1', 'fig-blank'])
|
|
363
363
|
})
|
|
364
364
|
|
|
365
|
-
it('도구 설명이 저장 전
|
|
365
|
+
it('도구 설명이 저장 전 피규어는 모델러에 열린 것만 읽는다고 말한다', () => {
|
|
366
366
|
const description = toolNamed('getFigureSource').schema.properties.figureId.description
|
|
367
367
|
expect(description).not.toMatch(/never saved is not open/)
|
|
368
368
|
expect(description).toMatch(/not saved yet/)
|
|
@@ -394,7 +394,7 @@ describe('목록이 잘릴 때', () => {
|
|
|
394
394
|
expect(result.note).toMatch(/search/)
|
|
395
395
|
})
|
|
396
396
|
|
|
397
|
-
it('이름이나 타입의 일부로 찾으면 잘린 뒤의
|
|
397
|
+
it('이름이나 타입의 일부로 찾으면 잘린 뒤의 피규어도 나온다 — 대소문자 없이', async () => {
|
|
398
398
|
const byName = await toolNamed('listFigures').builder({ search: 'roll PRESS' }, ctx({}))
|
|
399
399
|
const byType = await toolNamed('listFigures').builder({ search: 'old_press' }, ctx({}))
|
|
400
400
|
expect(byName.figures.map((one: any) => one.id)).toEqual(['fig-old'])
|
|
@@ -411,7 +411,7 @@ describe('목록이 잘릴 때', () => {
|
|
|
411
411
|
|
|
412
412
|
/*
|
|
413
413
|
제안은 모델러의 명령 목록이다(ADR-0094). 고치는 제안은 모델러가 모델이 본 판 위에 한 단계로 적용하므로, 고칠 수
|
|
414
|
-
있는 것은 모델러에 열린
|
|
414
|
+
있는 것은 모델러에 열린 피규어뿐이고 그 판(context · revision)을 문맥이 싣고 와야 한다.
|
|
415
415
|
*/
|
|
416
416
|
const figureDraft = () => {
|
|
417
417
|
const model = require('@hatiolab/figure-model')
|
|
@@ -422,7 +422,7 @@ const figureDraft = () => {
|
|
|
422
422
|
const modeller = (over: any = {}) => ({ figureId: 'fig-1', draftSource: figureDraft(), context: 'ctx-7', revision: 4, ...over })
|
|
423
423
|
|
|
424
424
|
describe('제안을 요청할 때', () => {
|
|
425
|
-
it('열린
|
|
425
|
+
it('열린 피규어를 고치면 그 초안이 제안기에 가고, 모델러가 적용할 판이 함께 돌아온다', async () => {
|
|
426
426
|
const out = await toolNamed('proposeFigureCandidate').builder({ prompt: '다리를 길게' }, ctx(modeller()))
|
|
427
427
|
expect(proposed[0].base.name).toBe('Probe')
|
|
428
428
|
expect(proposed[0].base.version).toBe('figure-asset-1')
|
|
@@ -438,19 +438,19 @@ describe('제안을 요청할 때', () => {
|
|
|
438
438
|
expect(proposed).toHaveLength(0)
|
|
439
439
|
})
|
|
440
440
|
|
|
441
|
-
it('모델러에 열리지 않은
|
|
441
|
+
it('모델러에 열리지 않은 피규어는 고치지 않고, 열라고 말한다', async () => {
|
|
442
442
|
const out = await toolNamed('proposeFigureCandidate').builder({ prompt: '고쳐줘', figureId: 'fig-blank' }, ctx(modeller()))
|
|
443
443
|
expect(out.error).toBe('FIGURE_NOT_OPEN')
|
|
444
444
|
expect(proposed).toHaveLength(0)
|
|
445
445
|
})
|
|
446
446
|
|
|
447
|
-
it('원본이 없는
|
|
447
|
+
it('원본이 없는 피규어는 고치지 않는다 — 빈 것을 제안기에 건네지 않는다', async () => {
|
|
448
448
|
const out = await toolNamed('proposeFigureCandidate').builder({ prompt: '고쳐줘' }, ctx({ figureId: 'fig-blank', context: 'c', revision: 1 }))
|
|
449
449
|
expect(out.error).toBe('FIGURE_NOT_OPEN')
|
|
450
450
|
expect(proposed).toHaveLength(0)
|
|
451
451
|
})
|
|
452
452
|
|
|
453
|
-
it('목록에서 새 제작은 열린
|
|
453
|
+
it('목록에서 새 제작은 열린 피규어를 요구하지 않는다', async () => {
|
|
454
454
|
const out = await toolNamed('proposeFigureCandidate').builder({ prompt: '믹서를 새로 만들어줘', create: true, name: 'Mixer' }, ctx({}))
|
|
455
455
|
expect(out.proposed).toBe(true)
|
|
456
456
|
expect(proposed[0].base).toBeUndefined()
|
|
@@ -464,7 +464,7 @@ describe('제안을 요청할 때', () => {
|
|
|
464
464
|
expect(proposed[0].base).toBeUndefined()
|
|
465
465
|
})
|
|
466
466
|
|
|
467
|
-
it('create 를 켜면 열린
|
|
467
|
+
it('create 를 켜면 열린 피규어가 있어도 새로 만든다 — 열린 것을 덮어쓰는 제안이 오지 않게', async () => {
|
|
468
468
|
const out = await toolNamed('proposeFigureCandidate').builder({ prompt: '새 컨베이어', create: true }, ctx(modeller()))
|
|
469
469
|
expect(proposed[0].base).toBeUndefined()
|
|
470
470
|
expect(out.revision).toBeUndefined()
|
|
@@ -474,7 +474,7 @@ describe('제안을 요청할 때', () => {
|
|
|
474
474
|
await expect(toolNamed('proposeFigureCandidate').builder({ prompt: ' ' }, ctx(modeller()))).rejects.toThrow(/prompt is required/)
|
|
475
475
|
})
|
|
476
476
|
|
|
477
|
-
it('남의
|
|
477
|
+
it('남의 피규어를 고치라고 하면 못 찾았다고 한다', async () => {
|
|
478
478
|
await expect(
|
|
479
479
|
toolNamed('proposeFigureCandidate').builder({ prompt: '고쳐줘', figureId: 'fig-other' }, ctx(modeller()))
|
|
480
480
|
).rejects.toThrow(/not found in this tenant/)
|
|
@@ -600,7 +600,7 @@ describe('사람이 붙인 그림', () => {
|
|
|
600
600
|
})
|
|
601
601
|
})
|
|
602
602
|
|
|
603
|
-
describe('
|
|
603
|
+
describe('피규어가 모델러에 열려 있을 때', () => {
|
|
604
604
|
it('저장 전 초안을 이 모델로 읽고, 셈으로 잰다', async () => {
|
|
605
605
|
const draft = figureDraft()
|
|
606
606
|
const got = await toolNamed('getFigureSource').builder({}, ctx({ figureId: 'fig-1', draftSource: draft, palette: ['palette.primary'] }))
|
|
@@ -639,7 +639,7 @@ describe('도형이 모델러에 열려 있을 때', () => {
|
|
|
639
639
|
expect(got.trying).toEqual({ clips: { open: 1 } })
|
|
640
640
|
})
|
|
641
641
|
|
|
642
|
-
it('저장된 정본을 읽을 때는 화면의 선택을 붙이지 않는다 — 다른
|
|
642
|
+
it('저장된 정본을 읽을 때는 화면의 선택을 붙이지 않는다 — 다른 피규어의 선택일 수 있다', async () => {
|
|
643
643
|
const got = await toolNamed('getFigureSource').builder({ figureId: 'fig-1' }, ctx({ selection: { part: 'body', context: 'c', revision: 1 }, context: 'c', revision: 1 }))
|
|
644
644
|
expect(got.draft).toBe(false)
|
|
645
645
|
expect(got.selection).toBeUndefined()
|
|
@@ -661,7 +661,7 @@ describe('도형이 모델러에 열려 있을 때', () => {
|
|
|
661
661
|
Until then the tools refused any draft over 100,000 characters: a figure that opened and saved got no read, no
|
|
662
662
|
inspection and no proposal, and saving again did not help. Now it is read as an outline and the parts asked for.
|
|
663
663
|
*/
|
|
664
|
-
describe('
|
|
664
|
+
describe('피규어가 한 번에 보일 만큼보다 클 때', () => {
|
|
665
665
|
const { FIGURE_CONTEXT_BUDGET } = require('./figure-context')
|
|
666
666
|
const model = require('@hatiolab/figure-model')
|
|
667
667
|
let under: any
|
|
@@ -770,7 +770,7 @@ describe('도형이 한 번에 보일 만큼보다 클 때', () => {
|
|
|
770
770
|
the screen sent, through the same door a save goes through. Only the figure open on that screen: an id the model passes
|
|
771
771
|
that has no row and is not the open one is still not found.
|
|
772
772
|
*/
|
|
773
|
-
describe('저장 전
|
|
773
|
+
describe('저장 전 피규어', () => {
|
|
774
774
|
const CREATION = '33333333-3333-4333-8333-333333333333'
|
|
775
775
|
const unsaved = (over: any = {}) => modeller({ figureId: CREATION, editorMode: 'new', ...over })
|
|
776
776
|
|
|
@@ -811,7 +811,7 @@ describe('저장 전 도형', () => {
|
|
|
811
811
|
await expect(toolNamed('getFigureSource').builder({}, ctx({ figureId: CREATION, editorMode: 'new' }))).rejects.toThrow(/not found in this tenant/)
|
|
812
812
|
})
|
|
813
813
|
|
|
814
|
-
it('생성 식별자 꼴이 아닌 이름은 저장 전
|
|
814
|
+
it('생성 식별자 꼴이 아닌 이름은 저장 전 피규어로 읽지 않는다', async () => {
|
|
815
815
|
await expect(toolNamed('getFigureSource').builder({}, ctx(unsaved({ figureId: 'fig-unknown' })))).rejects.toThrow(/not found in this tenant/)
|
|
816
816
|
})
|
|
817
817
|
})
|
|
@@ -100,7 +100,7 @@ function requireDomainId(state: any): string {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* 이
|
|
103
|
+
* 이 피규어가 호출자 도메인 것인가.
|
|
104
104
|
*
|
|
105
105
|
* 모델이 넘긴 id 를 그대로 쓰지 않는다. 없으면 못 찾았다고 말한다 — 「권한이 없다」와 구별해
|
|
106
106
|
* 알려 줄 것이 없다(다른 테넌트에 그 id 가 있다는 사실 자체가 알려 줄 것이 아니다).
|
|
@@ -211,8 +211,8 @@ function focusOf(host: any, source: any): { selection?: { part: string }; trying
|
|
|
211
211
|
/**
|
|
212
212
|
* 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.
|
|
213
213
|
*
|
|
214
|
-
* 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는
|
|
215
|
-
* 않는다 — 화면에 그
|
|
214
|
+
* 모델이 대상을 안 줬으면 **저작면이 지금 열고 있는 피규어**이다. 사용자에게 식별자를 되묻지
|
|
215
|
+
* 않는다 — 화면에 그 피규어가 떠 있는데 「어느 피규어입니까」를 묻는 것은 문맥을 안 읽은 것이다.
|
|
216
216
|
*/
|
|
217
217
|
function figureIdFrom(args: any, host: any): string | undefined {
|
|
218
218
|
const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''
|
|
@@ -303,7 +303,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
303
303
|
{
|
|
304
304
|
kind: 'read',
|
|
305
305
|
name: 'listFigures',
|
|
306
|
-
/* 이 도메인의
|
|
306
|
+
/* 이 도메인의 피규어 목록 — 저장소를 직접 읽지만 같은 일을 하는 문은 이것이다. */
|
|
307
307
|
doors: ['Query.figures'],
|
|
308
308
|
description:
|
|
309
309
|
'Lists the figures in this tenant with their type names, states and part counts. ' +
|
|
@@ -354,8 +354,8 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
354
354
|
{
|
|
355
355
|
kind: 'read',
|
|
356
356
|
name: 'getFigureSource',
|
|
357
|
-
/* 저장된
|
|
358
|
-
* 그 갈래에는 닿는 문이 없지만 `doors: []` 로 적지 않는다. 그러면 저장된
|
|
357
|
+
/* 저장된 피규어의 원본. **저장 전 초안**을 물으면 저장소에 갈 것이 없어 화면이 보낸 것을 그대로 돌려준다 —
|
|
358
|
+
* 그 갈래에는 닿는 문이 없지만 `doors: []` 로 적지 않는다. 그러면 저장된 피규어를 읽는 권한 검사가 선언에서 사라진다. */
|
|
359
359
|
doors: ['Query.figure'],
|
|
360
360
|
description:
|
|
361
361
|
'Returns the source (the figure asset) of one figure, with its measured cost ' +
|
|
@@ -467,12 +467,12 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
467
467
|
if (!prompt) throw new Error('prompt is required: there is nothing to ask for')
|
|
468
468
|
|
|
469
469
|
/*
|
|
470
|
-
고치는 것인지 만드는 것인지를 **문맥에서 정한다.** 열린
|
|
471
|
-
모델이 `create` 를 켜면 만드는 것이다. 이것을 모델의 짐작에 맡기면 열려 있는
|
|
470
|
+
고치는 것인지 만드는 것인지를 **문맥에서 정한다.** 열린 피규어가 있으면 고치는 것이 기본이고,
|
|
471
|
+
모델이 `create` 를 켜면 만드는 것이다. 이것을 모델의 짐작에 맡기면 열려 있는 피규어를 덮어쓰는
|
|
472
472
|
후보가 온다.
|
|
473
473
|
|
|
474
474
|
고치는 제안은 모델러가 **요청 당시의 판(context · revision)** 위에 한 단계로 적용한다(ADR-0094).
|
|
475
|
-
그래서 고칠 수 있는 것은 모델러에 열린
|
|
475
|
+
그래서 고칠 수 있는 것은 모델러에 열린 피규어뿐이고, 그 판을 문맥이 함께 실어 와야 한다.
|
|
476
476
|
*/
|
|
477
477
|
const host = ctx?.host
|
|
478
478
|
const create = args?.create === true || args?.figureId === null
|
|
@@ -48,8 +48,8 @@ export class NewFigure {
|
|
|
48
48
|
*
|
|
49
49
|
* `type` 은 **한 번도 발행되지 않은 초안에서만** 바뀐다(ADR-0063 결정 4). 저장되는 식별자라
|
|
50
50
|
* 발행된 뒤에 바꾸면 이미 배치된 보드가 타입을 못 찾아 오류 없이 빈 화면이 된다. 발행 전에는
|
|
51
|
-
* 그
|
|
52
|
-
*
|
|
51
|
+
* 그 피규어를 가리키는 도면이 있을 수 없다 — 도면은 최신 발행판을 그리므로 발행된 적 없는
|
|
52
|
+
* 피규어는 그려질 자리가 없다.
|
|
53
53
|
*
|
|
54
54
|
* 닫아 두었더니 저작자가 이름을 한 번도 정하지 못했다. 새 Figure 에는 기계가 지은 이름이
|
|
55
55
|
* 붙고(`FIGURE_MU6RJH47`) 칸에 자물쇠가 걸려서, 그 이름이 그대로 도면에 적히는 이름이 됐다.
|