@things-factory/figure-ui 10.1.145 → 10.1.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/client/modeller/refusals.ts +16 -7
  2. package/dist-client/modeller/refusals.js +18 -8
  3. package/dist-client/modeller/refusals.js.map +1 -1
  4. package/dist-client/tsconfig.tsbuildinfo +1 -1
  5. package/package.json +4 -4
  6. package/test/align.test.ts +2 -2
  7. package/test/browser/modeller-page.js +3 -3
  8. package/test/browser/modeller.check.mjs +93 -51
  9. package/test/cavity-drawing.test.ts +1 -1
  10. package/test/clip-consent.test.ts +1 -1
  11. package/test/clips.test.ts +1 -1
  12. package/test/command-preview.test.ts +5 -5
  13. package/test/controls.test.ts +1 -1
  14. package/test/fasten-drawing.test.ts +1 -1
  15. package/test/fields.test.ts +1 -1
  16. package/test/figure-assistant-surface.test.ts +1 -1
  17. package/test/first-minute.test.ts +1 -1
  18. package/test/first-save-suggest.test.ts +1 -1
  19. package/test/gizmo.test.ts +1 -1
  20. package/test/layout.test.ts +2 -2
  21. package/test/links.test.ts +1 -1
  22. package/test/maturity.test.ts +1 -1
  23. package/test/parts-wiring.test.ts +3 -3
  24. package/test/properties.test.ts +1 -1
  25. package/test/proposal-record.test.ts +1 -1
  26. package/test/refusals.test.ts +40 -0
  27. package/test/release-entry.test.ts +1 -1
  28. package/test/revert-choice.test.ts +1 -1
  29. package/test/revert-keeping.test.ts +1 -1
  30. package/test/revision.test.ts +7 -4
  31. package/test/save-landed.test.ts +2 -2
  32. package/test/section-editor.test.ts +3 -3
  33. package/test/section-panel.test.ts +1 -1
  34. package/test/verdict.test.ts +1 -1
  35. package/translations/en.json +4 -1
  36. package/translations/es.json +991 -0
  37. package/translations/ja.json +3 -0
  38. package/translations/ko.json +3 -0
  39. package/translations/ms.json +3 -0
  40. package/translations/zh.json +3 -0
@@ -23,6 +23,46 @@ test('위쪽 끝도, 부품 이름이 없어도', () => {
23
23
  assert.ok(said.includes('Y 축을') && said.includes('15,000 mm'), said)
24
24
  })
25
25
 
26
+ /*
27
+ * 범위가 지금 크기를 담지 않는 거절도 숫자와 함께 (Figure 레인 인계, 2026-10-11: 400 mm 상자에 가장 작은 크기 500 을
28
+ * 넣으면 「이 편집은 할 수 없습니다. (INSTANCE_SIZE_RANGE)」만 떴다 — 이 문장은 below/above 꼴이 아니라 읽히지 않았다).
29
+ */
30
+ test('범위가 지금 크기를 담지 않으면 축 · 지금 크기 · 범위를 말한다', () => {
31
+ const said = sizeRangeRefusalOf(refusal('INSTANCE_SIZE_RANGE: size.x: the authored size 400 mm must lie within 500…4000 mm'), say)!
32
+ assert.ok(said.includes('X') && said.includes('400 mm') && said.includes('500') && said.includes('4,000'), said)
33
+ assert.ok(!/authored|lie within/.test(said), said)
34
+ })
35
+
36
+ test('가장 작은 크기가 0 이하이면 그렇게 말한다', () => {
37
+ const said = sizeRangeRefusalOf(refusal('INSTANCE_SIZE_RANGE: size.z: the smallest size must be above zero; got 0'), say)!
38
+ assert.ok(said.includes('Z') && said.includes('0 보다'), said)
39
+ assert.ok(!/smallest|above zero/.test(said), said)
40
+ })
41
+
42
+ test('지금 크기에서 이미 길이가 없는 부품이 있으면 그렇게 말한다', () => {
43
+ const said = sizeRangeRefusalOf(refusal('INSTANCE_SIZE_RANGE: size.y: the figure is authored at 120 mm, where a length is already gone; change the link or the size'), say)!
44
+ assert.ok(said.includes('Y') && said.includes('120 mm'), said)
45
+ assert.ok(!/authored|already gone/.test(said), said)
46
+ })
47
+
48
+ test('INSTANCE_SIZE_RANGE 의 문장 넷이 모두 사람의 말이 된다 — 커널이 쓰는 꼴 그대로', async () => {
49
+ const { addFigureBox, applyFigureAuthoring, createFigureAsset } = await import('@hatiolab/figure-model')
50
+ const box = addFigureBox(createFigureAsset('Box'), { id: 'box', size: { x: 400, y: 300, z: 200 }, position: { x: 0, y: 150, z: 0 } })
51
+ const refused = (action: unknown) => {
52
+ try {
53
+ applyFigureAuthoring(box, action as never)
54
+ } catch (e) {
55
+ return e
56
+ }
57
+ assert.fail('the kernel took it')
58
+ }
59
+ const range = (x: { min: number; max: number }) => ({ kind: 'declare-instance-size', range: { x, y: { min: 100, max: 3000 }, z: { min: 100, max: 3000 } } })
60
+ for (const error of [refused(range({ min: 500, max: 4000 })), refused(range({ min: 0, max: 4000 }))]) {
61
+ assert.equal((error as { code?: string }).code, 'INSTANCE_SIZE_RANGE', String(error))
62
+ assert.ok(sizeRangeRefusalOf(error, say), `not read: ${(error as Error).message}`)
63
+ }
64
+ })
65
+
26
66
  test('다른 거절은 건드리지 않는다', () => {
27
67
  assert.equal(sizeRangeRefusalOf(Object.assign(new Error('x'), { code: 'EDIT_TARGET' }), say), null)
28
68
  })
@@ -21,7 +21,7 @@ test('beside the save: the release state, and a way to the release section that
21
21
  assert.ok(page.includes('.releaseState=${this.row}'), 'as the server gave it')
22
22
  const ko = JSON.parse(read('../translations/ko.json'))
23
23
  assert.equal(ko['figure.text.release-state-draft-over'].replace('{version}', '3'), '초안 · 보드는 v3 사용')
24
- for (const lang of ['en', 'ja', 'ko', 'ms', 'zh']) {
24
+ for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) {
25
25
  const t = JSON.parse(read(`../translations/${lang}.json`))
26
26
  for (const key of ['figure.text.release-state-released', 'figure.text.release-state-draft-over', 'figure.text.release-state-draft-new', 'figure.button.release']) assert.ok(t[key], `${lang} ${key}`)
27
27
  }
@@ -32,7 +32,7 @@ test('saved first, and known to be -- or nothing is reverted', () => {
32
32
  const save = page.slice(page.indexOf(' private saveBeforeRevert'), page.indexOf(' private reverted'))
33
33
  assert.ok(save.includes("return this.session.read().dirty ? { ok: false } : { ok: true }"), 'ok only when the session says saved')
34
34
  assert.ok(save.includes("code === 'SAVE_UNREACHABLE' ? i18next.t('figure.text.save-unreachable')"), 'not known is said as not known')
35
- for (const lang of ['en', 'ja', 'ko', 'ms', 'zh']) {
35
+ for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) {
36
36
  const t = JSON.parse(read(`../translations/${lang}.json`))
37
37
  for (const key of ['figure.text.revert-with-unsaved', 'figure.button.revert-saving', 'figure.button.revert-discarding', 'figure.text.revert-held-unsaved']) assert.ok(t[key], `${lang} ${key}`)
38
38
  }
@@ -81,7 +81,7 @@ test('the screen: reverting is on, sends the confirmed source and a request id,
81
81
  assert.ok(panel.includes('<section kept>'), 'kept drafts in their own section, not among the releases')
82
82
  assert.ok(panel.includes('${newest && !kept.restoredAt'), 'only the newest, not yet brought back, is offered')
83
83
  assert.ok(read('../client/pages/figure-modeller-page.ts').includes('.serverSource=${() => this.session.confirmedSource()}'))
84
- for (const lang of ['en', 'ja', 'ko', 'ms', 'zh']) {
84
+ for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) {
85
85
  const t = JSON.parse(read(`../translations/${lang}.json`))
86
86
  for (const key of [
87
87
  'figure.label.kept-drafts',
@@ -118,8 +118,11 @@ test('the ends of a motion evaluate the figure at its two ends, and nothing is c
118
118
  assert.deepEqual([work.compiles, work.evaluations], [0, 2])
119
119
  })
120
120
 
121
- /* A compile also evaluates the figure once (its stored values must run): three compiles, and the read's evaluation. */
122
- test('a value changed, read again as the next version, is three compiles at most: the edit, its read and its asset', async () => {
121
+ /*
122
+ * A compile also evaluates the figure once (its stored values must run): two compiles, and the read's evaluation. The
123
+ * version's asset is the one the edit installed (ADR-0119 decision 6), not compiled again.
124
+ */
125
+ test('a value changed, read again as the next version, is two compiles at most: the edit and its read', async () => {
123
126
  const session = await opened(arm)
124
127
  const field = session.revisionNow()!.fields.find(f => f.role !== 'state')!
125
128
  const parts = session.revisionNow()!.geometry.map(g => g.id).filter(id => !id.startsWith('['))
@@ -129,8 +132,8 @@ test('a value changed, read again as the next version, is three compiles at most
129
132
  for (const part of parts) selectionQuestions(session.revisionNow()!, part)
130
133
  session.source()
131
134
  })
132
- assert.ok(work.compiles <= 3, `compiles ${work.compiles}`)
133
- assert.ok(work.evaluations <= 4, `evaluations ${work.evaluations}`)
135
+ assert.ok(work.compiles <= 2, `compiles ${work.compiles}`)
136
+ assert.ok(work.evaluations <= 3, `evaluations ${work.evaluations}`)
134
137
  })
135
138
 
136
139
  /*
@@ -105,7 +105,7 @@ test('not reached is said as not known, not as not saved', () => {
105
105
  assert.ok(write.includes("return new GraphQLError(message, { extensions: { code: 'FIGURE_SOURCE_CONFLICT' } })"), 'the server says a conflict is one')
106
106
  const editor = read('../client/modeller/editor.ts')
107
107
  assert.ok(editor.includes("=== 'SAVE_UNREACHABLE' ? i18next.t('figure.text.save-unreachable')"))
108
- for (const lang of ['en', 'ja', 'ko', 'ms', 'zh']) assert.ok(JSON.parse(read(`../translations/${lang}.json`))['figure.text.save-unreachable'], lang)
108
+ for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) assert.ok(JSON.parse(read(`../translations/${lang}.json`))['figure.text.save-unreachable'], lang)
109
109
  assert.match(JSON.parse(read('../translations/ko.json'))['figure.text.save-unreachable'], /저장됐는지 확인하지 못했습니다/)
110
110
  })
111
111
 
@@ -116,7 +116,7 @@ test('a released figure changed back to a draft is told, whichever change did it
116
116
  assert.ok(page.includes('if (this.session.read().id === id) this.toldDemoted(this.row.state, figure.state)'), 'after a save, read back')
117
117
  assert.ok(page.includes('this.toldDemoted(this.row.state, result.figure.state)'), 'after category or tags')
118
118
  assert.ok(page.includes("...(result.figure.state ? { state: result.figure.state as 'draft' | 'released' } : {})"), 'and the badge follows')
119
- for (const lang of ['en', 'ja', 'ko', 'ms', 'zh']) assert.ok(JSON.parse(read(`../translations/${lang}.json`))['figure.text.released-now-draft'], lang)
119
+ for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) assert.ok(JSON.parse(read(`../translations/${lang}.json`))['figure.text.released-now-draft'], lang)
120
120
  })
121
121
 
122
122
  /* Designer 2026-09-27: read back, every field the save writes must match; a refusal on grounds is never a save. */
@@ -148,7 +148,7 @@ test('편집 창의 말은 다섯 말 모두에 있다', () => {
148
148
  const drawing = read('../client/modeller/section-drawing.ts')
149
149
  keys.push(...new Set([...drawing.matchAll(/'(figure\.text\.section-issue-[\w-]+)'/g)].map(m => m[1]!)))
150
150
  assert.ok(keys.length >= 20, String(keys.length))
151
- for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
151
+ for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
152
152
  const words = JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>
153
153
  for (const key of keys) assert.ok(words[key], `${lang} has ${key}`)
154
154
  }
@@ -296,7 +296,7 @@ test('ADR-0100 A5-1: 팔레트의 뿔은 삼각 단면과 꼭짓점 하나 —
296
296
  assert.deepEqual(s.loft!.apex, { x: 0, z: 0 })
297
297
  assert.equal(startingShapeKindOf(node as never), 'pyramid')
298
298
  assert.equal(startingShapeKindOfBody(s as never), 'pyramid')
299
- for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
299
+ for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
300
300
  const t = JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>
301
301
  for (const key of ['figure.label.primitive-pyramid', 'figure.label.section-editor-apex', 'figure.label.section-editor-apex-at', 'figure.label.section-editor-apex-mark', 'figure.text.section-editor-apex-blocked', 'figure.label.section-editor-height'])
302
302
  assert.ok(t[key], `${lang} ${key}`)
@@ -349,7 +349,7 @@ test('닫기 전에 묻는다 (사용자 10-01): 바꾼 것이 있으면 적용
349
349
  has(choice, /\$\{this\.held\s*\? nothing/)
350
350
  /* Picking another part while changes wait keeps the part selected until the editor closed. */
351
351
  has(editor, /this\.selected = editing\s*editor\.requestClose\(\)\.then\(closed => \{\s*if \(closed && this\.selected === editing\) this\.selected = next/)
352
- for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
352
+ for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
353
353
  const t = JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>
354
354
  for (const key of ['figure.text.section-editor-close-changed', 'figure.text.section-editor-close-invalid', 'figure.button.section-editor-apply-and-close', 'figure.button.section-editor-discard', 'figure.button.keep-editing'])
355
355
  assert.ok(t[key], `${lang} ${key}`)
@@ -93,7 +93,7 @@ test('편집기는 단면 칸의 명령을 세션의 이력 하나로 돌리고,
93
93
  has(editor, /<figure-section-panel[\s\S]*?@command=\$\{\(e: CustomEvent\) => this\.linkCommand\(e\.detail\.action\)\}/)
94
94
  has(editor, /@command-live=\$\{\(e: CustomEvent\) => this\.holdAction\(e\.detail\.action\)\}/)
95
95
  has(editor, /private linkCommand\(action: FigureAuthoringAction, from: 'section-editor' \| 'panel' = 'panel'\): boolean \{\s*\/\*[^*]*\*\/\s*this\.holdAction\(null\)/, 'a command sent lets go of the one in hand')
96
- for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
96
+ for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
97
97
  const words = JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>
98
98
  const used = [...new Set([...(panel + drawing).matchAll(/'(figure\.(?:label|text)\.section-[\w-]+|figure\.label\.group-section)'/g)].map(m => m[1]!))]
99
99
  /* The keys the panel builds: each result, and the wall's and the ends' over-limit words. */
@@ -74,7 +74,7 @@ test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그
74
74
  const maturity = read('../client/modeller/maturity.ts')
75
75
  const keys = [...(verdict + maturity).matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
76
76
  assert.ok(keys.includes('figure.text.verdict-asking'), '전제: 삼항 안의 키도 센다')
77
- for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
77
+ for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
78
78
  const table = JSON.parse(read(`../translations/${lang}.json`))
79
79
  for (const key of keys) assert.ok(table[key], `${lang}: ${key}`)
80
80
  }
@@ -71,6 +71,9 @@
71
71
  "figure.text.instance-range-can-widen": "measured again with the parts as they are: {ranges} mm.",
72
72
  "figure.text.size-range-too-small": "below {mm} mm on {axis} the {part} part has no length, yet the figure is declared to be placed smaller, so this change cannot be made. use \"measure again\" under the sizes it can be placed at.",
73
73
  "figure.text.size-range-too-large": "above {mm} mm on {axis} the {part} part cannot be drawn, yet the figure is declared to be placed larger, so this change cannot be made. use \"measure again\" under the sizes it can be placed at.",
74
+ "figure.text.size-range-leaves-out-size": "the {axis} range of the sizes it can be placed at, {min}–{max} mm, does not hold its size now, {at} mm. change the range so that it does.",
75
+ "figure.text.size-range-from-zero": "the smallest {axis} size it can be placed at must be above 0.",
76
+ "figure.text.size-range-part-gone": "at its size now, {at} mm on {axis}, a part already has no length. change the link or the size.",
74
77
  "figure.text.input-range-size": "figure size {axis} can only be set between {min} and {max} mm. value given: {value} mm",
75
78
  "figure.text.input-range": "this value can only be set between {min} and {max}. value given: {value}",
76
79
  "figure.text.mounting-face-needs-space": "declare the space first to choose one.",
@@ -958,7 +961,7 @@
958
961
  "figure.text.summary-size-part-share-plus": "Its {axes} is {pct}% of {other}'s {measureWord} plus {plus} mm.",
959
962
  "figure.text.summary-size-input": "Its {axes} follows {field}.",
960
963
  "figure.text.summary-size-worked-out": "Its {axes} is worked out from other measures.",
961
- "figure.text.held-by-fastening": "It is fastened: its place and turn are set in 붙이기.",
964
+ "figure.text.held-by-fastening": "It is fastened: its place and turn are set in Fastening.",
962
965
  "figure.text.held-worked-out": "{group} are worked out from other values and cannot be typed here.",
963
966
  "figure.text.face-mapping-lost": "Faces this takes away: {faces}. Choose where what stands on them goes.",
964
967
  "figure.label.face-mapping-to": "Where {part} goes",