@things-factory/figure-ui 10.1.64 → 10.1.65

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 (49) hide show
  1. package/client/figure-assistant.ts +26 -10
  2. package/client/graphql/index.ts +0 -58
  3. package/client/modeller/v3-ai-proposal.ts +111 -0
  4. package/client/modeller/v3-product-session.ts +10 -1
  5. package/client/pages/figure-v3-modeller-page.ts +35 -2
  6. package/client/types.ts +0 -39
  7. package/dist-client/figure-assistant.js +22 -10
  8. package/dist-client/figure-assistant.js.map +1 -1
  9. package/dist-client/graphql/index.d.ts +1 -19
  10. package/dist-client/graphql/index.js +0 -46
  11. package/dist-client/graphql/index.js.map +1 -1
  12. package/dist-client/modeller/figure-v3-capability.d.ts +1 -1
  13. package/dist-client/modeller/figure-v3-clips.d.ts +1 -1
  14. package/dist-client/modeller/figure-v3-controls.d.ts +1 -1
  15. package/dist-client/modeller/figure-v3-links.d.ts +1 -1
  16. package/dist-client/modeller/figure-v3-material.d.ts +1 -1
  17. package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
  18. package/dist-client/modeller/v3-ai-proposal.d.ts +51 -0
  19. package/dist-client/modeller/v3-ai-proposal.js +64 -0
  20. package/dist-client/modeller/v3-ai-proposal.js.map +1 -0
  21. package/dist-client/modeller/v3-clips.d.ts +1 -1
  22. package/dist-client/modeller/v3-product-session.d.ts +35 -1
  23. package/dist-client/modeller/v3-product-session.js +8 -0
  24. package/dist-client/modeller/v3-product-session.js.map +1 -1
  25. package/dist-client/pages/figure-v3-modeller-page.d.ts +7 -1
  26. package/dist-client/pages/figure-v3-modeller-page.js +41 -2
  27. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  28. package/dist-client/route.d.ts +1 -1
  29. package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
  30. package/dist-client/tsconfig.tsbuildinfo +1 -1
  31. package/dist-client/types.d.ts +0 -44
  32. package/dist-client/types.js.map +1 -1
  33. package/package.json +4 -4
  34. package/test/figure-assistant-surface.test.ts +9 -6
  35. package/test/v3-ai-proposal.test.ts +112 -0
  36. package/translations/en.json +6 -1
  37. package/translations/ja.json +6 -1
  38. package/translations/ko.json +6 -1
  39. package/translations/ms.json +6 -1
  40. package/translations/zh.json +6 -1
  41. package/client/modeller/proposal-session.ts +0 -98
  42. package/client/modeller/proposal.ts +0 -171
  43. package/dist-client/modeller/proposal-session.d.ts +0 -28
  44. package/dist-client/modeller/proposal-session.js +0 -66
  45. package/dist-client/modeller/proposal-session.js.map +0 -1
  46. package/dist-client/modeller/proposal.d.ts +0 -45
  47. package/dist-client/modeller/proposal.js +0 -114
  48. package/dist-client/modeller/proposal.js.map +0 -1
  49. package/test/ai-proposal-contract.test.ts +0 -563
@@ -1,20 +1,36 @@
1
1
  /*
2
- * What the AI dock does with a figure candidate - figure-ui declares it because figure-service declares the tool
2
+ * What the AI dock does with a figure proposal - figure-ui declares it because figure-service declares the tool
3
3
  * (`proposeFigureCandidate`, design/ai/shared-dock.md §4).
4
4
  *
5
- * Today a candidate cannot be taken anywhere. The proposer writes V2 figures and the modeller opens V3 only; the V2
6
- * modeller that reviewed candidates was removed with its listeners (`figure-ai-review`, `figure-ai-stage`), and the
7
- * server already refuses to propose for a V3 figure (`V3_PROPOSAL_UNAVAILABLE`). So the outcome is `unavailable`,
8
- * said to the person in words, not a silent pass - until a proposer writes V3 changes, which the modeller takes
9
- * through `figure-v3-apply`.
5
+ * A proposal is a list of the modeller's own commands (ADR-0094). Running it applies them in the modeller as one step
6
+ * of its history: a revision on the figure open there, at the version the model saw; a new figure on a new figure the
7
+ * modeller opens. The outcome is said in words on every branch (`v3-ai-proposal.ts`).
10
8
  */
11
9
  import { i18next } from '@operato/i18n'
10
+ import { navigate } from '@operato/shell'
12
11
  import { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js'
13
12
 
13
+ import { modellerPathOf } from './modeller/modeller-route.js'
14
+ import { pendNewFigure, runFigureProposal, type FigureApplyDetail } from './modeller/v3-ai-proposal.js'
15
+
14
16
  export const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate'
15
17
 
16
- registerProposalHandler(FIGURE_CANDIDATE_TOOL, async (_proposal, context) => {
17
- const note = i18next.t('figure.text.candidate-cannot-open')
18
- context.notify('info', note)
19
- return { verdict: 'unavailable', errorCode: 'v3-review-missing', note }
18
+ registerProposalHandler(FIGURE_CANDIDATE_TOOL, async (proposal, context) => {
19
+ const outcome = await runFigureProposal(
20
+ proposal,
21
+ {
22
+ offer(detail: FigureApplyDetail) {
23
+ window.dispatchEvent(new CustomEvent('figure-v3-apply', { detail }))
24
+ return detail
25
+ },
26
+ openNew(steps) {
27
+ const opened = pendNewFigure(steps)
28
+ navigate(modellerPathOf())
29
+ return opened
30
+ }
31
+ },
32
+ (key, params) => i18next.t(key, params)
33
+ )
34
+ if (outcome.note) context.notify(outcome.verdict === 'accepted' ? 'info' : 'error', outcome.note)
35
+ return outcome
20
36
  })
@@ -8,8 +8,6 @@ import type {
8
8
  FigureInspection,
9
9
  FigureListResult,
10
10
  FigurePatch,
11
- FigureProposal,
12
- ProposalFeedback,
13
11
  FigureVersion,
14
12
  NewFigure
15
13
  } from '../types.js'
@@ -187,62 +185,6 @@ export async function deleteFigure(id: string): Promise<boolean> {
187
185
  return unwrap<boolean>(response, 'deleteFigure')
188
186
  }
189
187
 
190
- /**
191
- * 저작 보조에게 후보를 시킨다.
192
- *
193
- * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고
194
- * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을
195
- * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.
196
- *
197
- * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.
198
- */
199
- export async function proposeFigure(request: {
200
- prompt: string
201
- base?: string
202
- type?: string
203
- palette: string[]
204
- refine?: boolean
205
- feedback?: ProposalFeedback[]
206
- image?: File
207
- }): Promise<FigureProposal> {
208
- const response = await client.mutate({
209
- mutation: gql`
210
- mutation ProposeFigure($request: ProposeRequest!) {
211
- proposeFigure(request: $request) {
212
- source
213
- score
214
- grade
215
- triangles
216
- groups
217
- quality {
218
- status
219
- summary
220
- findings {
221
- dimension
222
- code
223
- message
224
- }
225
- visualRegions
226
- visualCoverage
227
- visualReadability
228
- }
229
- attempts
230
- violations {
231
- code
232
- message
233
- }
234
- }
235
- }
236
- `,
237
- variables: { request },
238
- // @operato/graphql 는 이 신호가 있을 때만 Upload가 포함된 변수를 multipart로 인코딩한다.
239
- // 없으면 브라우저 File은 JSON에서 {}가 되어 서버 Upload scalar가 요청 자체를 거절한다.
240
- context: { hasUpload: !!request.image }
241
- })
242
-
243
- return unwrap<FigureProposal>(response, 'proposeFigure')
244
- }
245
-
246
188
  /**
247
189
  * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.
248
190
  *
@@ -0,0 +1,111 @@
1
+ /*
2
+ * An AI proposal reaching the modeller (ADR-0094). The dock's card carries the modeller's own commands; running it
3
+ * applies them as one step of the modeller's history, undone whole, on the version the model saw. Nothing half made
4
+ * is applied, and every outcome is said: applied, refused with the reason, or not possible here.
5
+ *
6
+ * Kept free of Lit, i18n and the shell so node tests read it directly; the handler and the page pass in what they own.
7
+ */
8
+ import type { V3ProposalStep } from '@hatiolab/figure-model'
9
+
10
+ /** What the card carries, as `proposeFigureCandidate` returned it. */
11
+ export interface FigureProposalCard {
12
+ steps?: V3ProposalStep[]
13
+ create?: boolean
14
+ figureId?: string
15
+ context?: string
16
+ revision?: number
17
+ }
18
+
19
+ /** The `figure-v3-apply` event's detail. The page that takes it sets `applied`, and `error` when refused. */
20
+ export interface FigureApplyDetail {
21
+ steps: V3ProposalStep[]
22
+ create: boolean
23
+ context?: string
24
+ revision?: number
25
+ applied?: boolean
26
+ error?: string
27
+ code?: string
28
+ }
29
+
30
+ export type Say = (key: string, params?: Record<string, string>) => string
31
+
32
+ export interface FigureProposalTarget {
33
+ /** Offers the proposal to a modeller on screen. It comes back untouched when none took it. */
34
+ offer(detail: FigureApplyDetail): FigureApplyDetail
35
+ /** Opens a new figure in the modeller and applies the steps there; resolves once the page has, or has not. */
36
+ openNew(steps: V3ProposalStep[]): Promise<{ applied: boolean; error?: string; code?: string }>
37
+ }
38
+
39
+ export interface FigureProposalOutcome {
40
+ verdict: 'accepted' | 'refused' | 'unavailable'
41
+ errorCode?: string
42
+ applied?: number
43
+ note?: string
44
+ }
45
+
46
+ /** The workspace refuses a request made on another version; said as what happened, not as the kernel's code. */
47
+ const STALE = new Set(['REVISION_CONFLICT', 'CONTEXT_CONFLICT'])
48
+
49
+ function refused(code: string | undefined, error: string | undefined, say: Say): FigureProposalOutcome {
50
+ const note =
51
+ code && STALE.has(code)
52
+ ? say('figure.text.proposal-stale')
53
+ : code === 'NEW_FIGURE_UNSAVED'
54
+ ? (error ?? '')
55
+ : say('figure.text.proposal-refused', { reason: error ?? '' })
56
+ return { verdict: 'refused', errorCode: code ?? 'refused', note }
57
+ }
58
+
59
+ export async function runFigureProposal(card: FigureProposalCard, target: FigureProposalTarget, say: Say): Promise<FigureProposalOutcome> {
60
+ const steps = card?.steps
61
+ if (!Array.isArray(steps) || !steps.length) return { verdict: 'unavailable', errorCode: 'no-steps' }
62
+ const create = card.create === true
63
+ const accepted = (): FigureProposalOutcome => ({
64
+ verdict: 'accepted',
65
+ applied: steps.length,
66
+ note: say('figure.text.proposal-applied', { n: String(steps.length) })
67
+ })
68
+
69
+ const detail = target.offer({ steps, create, context: card.context, revision: card.revision })
70
+ if (detail.applied) return accepted()
71
+ if (detail.applied === false) return refused(detail.code, detail.error, say)
72
+
73
+ /* A revision belongs to the figure the model saw; with that modeller gone there is nowhere true to apply it. */
74
+ if (!create) return { verdict: 'unavailable', errorCode: 'modeller-not-open', note: say('figure.text.proposal-needs-modeller') }
75
+
76
+ const opened = await target.openNew(steps)
77
+ if (opened.applied) return accepted()
78
+ return opened.code === 'not-opened'
79
+ ? { verdict: 'unavailable', errorCode: 'not-opened', note: say('figure.text.proposal-not-opened') }
80
+ : refused(opened.code, opened.error, say)
81
+ }
82
+
83
+ /*
84
+ A new figure's proposal handed to the modeller page across a navigation: the handler leaves it here and opens the
85
+ modeller on a new figure; the page takes it once it has started that figure. Left untaken, it lapses -- a later new
86
+ figure must not receive steps nobody is waiting for.
87
+ */
88
+ let pending: { steps: V3ProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined
89
+
90
+ export function pendNewFigure(steps: V3ProposalStep[], ms = 10_000): Promise<{ applied: boolean; error?: string; code?: string }> {
91
+ pending?.settle({ applied: false, code: 'not-opened' })
92
+ return new Promise(resolve => {
93
+ const mine = {
94
+ steps,
95
+ settle: (result: { applied: boolean; error?: string; code?: string }) => {
96
+ clearTimeout(timer)
97
+ if (pending === mine) pending = undefined
98
+ resolve(result)
99
+ }
100
+ }
101
+ const timer = setTimeout(() => mine.settle({ applied: false, code: 'not-opened' }), ms)
102
+ pending = mine
103
+ })
104
+ }
105
+
106
+ /** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */
107
+ export function takePendingNewFigure(): { steps: V3ProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined {
108
+ const taken = pending
109
+ pending = undefined
110
+ return taken
111
+ }
@@ -6,7 +6,8 @@ import {
6
6
  type V3InputChange,
7
7
  type V3PrimitiveCreation,
8
8
  type V3PartEdit,
9
- type V3AuthoringAction
9
+ type V3AuthoringAction,
10
+ type V3ProposalStep
10
11
  } from '@hatiolab/figure-model'
11
12
 
12
13
  export interface V3ProductRecord {
@@ -111,6 +112,14 @@ export function createV3ProductSession(store: V3ProductStore) {
111
112
  changes
112
113
  })
113
114
  },
115
+ /**
116
+ * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.
117
+ * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.
118
+ */
119
+ applyProposal(steps: readonly V3ProposalStep[], expectedRevision: number, context: string) {
120
+ writable()
121
+ return workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps })
122
+ },
114
123
  add(part: V3PrimitiveCreation) {
115
124
  return workspace.addPrimitive({ ...writable(), part })
116
125
  },
@@ -25,6 +25,7 @@ import {
25
25
  } from '@things-factory/ai-assistant/dist-client/index.js'
26
26
  import type { FirstSave } from '../modeller/v3-first-save.js'
27
27
  import { placementRefusalOf } from '../modeller/v3-refusals.js'
28
+ import { takePendingNewFigure } from '../modeller/v3-ai-proposal.js'
28
29
 
29
30
  /**
30
31
  * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
@@ -158,6 +159,9 @@ export class FigureV3ModellerPage extends PageView {
158
159
  editorMode: 'existing',
159
160
  figureId: id,
160
161
  draftSource: JSON.parse(this.session.source()),
162
+ /* The version the model sees; a proposal is applied on it, or refused if the figure moved on (ADR-0094). */
163
+ context: this.session.read().context,
164
+ revision: this.session.read().revision,
161
165
  palette: Object.keys(activePalette())
162
166
  })
163
167
  })
@@ -297,16 +301,32 @@ export class FigureV3ModellerPage extends PageView {
297
301
  this.changed()
298
302
  this.readRow(id)
299
303
  }
300
- /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */
304
+ /**
305
+ * The dock's way in (`figure-assistant.ts`): input changes, or a proposal's commands as one step (ADR-0094). A
306
+ * revision is applied on the version the model saw; a new figure's proposal only on a new figure not yet touched. A
307
+ * page on a saved figure leaves a new figure's proposal untaken, and the handler opens a new figure for it.
308
+ */
301
309
  private applyAI = (event: Event) => {
302
310
  const detail = (event as CustomEvent).detail
303
311
  if (!this.active || !detail) return
312
+ const view = this.session.read()
313
+ if (detail.create && this.routeId) return
314
+ if (detail.create && view.dirty) {
315
+ /* A new figure already being made is the person's; a proposal does not go on top of it or replace it. */
316
+ detail.applied = false
317
+ detail.code = 'NEW_FIGURE_UNSAVED'
318
+ detail.error = i18next.t('figure.text.proposal-new-figure-unsaved')
319
+ return
320
+ }
304
321
  try {
305
- this.session.apply(detail.changes, 'ai', detail.revision, detail.context)
322
+ if (detail.create) this.session.applyProposal(detail.steps, view.revision!, view.context!)
323
+ else if (detail.steps) this.session.applyProposal(detail.steps, detail.revision, detail.context)
324
+ else this.session.apply(detail.changes, 'ai', detail.revision, detail.context)
306
325
  detail.applied = true
307
326
  this.changed()
308
327
  } catch (error) {
309
328
  detail.applied = false
329
+ detail.code = (error as { code?: string })?.code
310
330
  detail.error = error instanceof Error ? error.message : String(error)
311
331
  }
312
332
  }
@@ -361,6 +381,7 @@ export class FigureV3ModellerPage extends PageView {
361
381
  this.draft = undefined
362
382
  this.row = { version: 0 }
363
383
  this.typeError = ''
384
+ this.takeNewFigureProposal()
364
385
  }
365
386
  this.routeId = id
366
387
  this.error = ''
@@ -369,6 +390,18 @@ export class FigureV3ModellerPage extends PageView {
369
390
  this.error = error instanceof Error ? error.message : String(error)
370
391
  }
371
392
  }
393
+ /** A new figure the dock opened for a proposal: its steps, applied as the new figure's first step. */
394
+ private takeNewFigureProposal() {
395
+ const waiting = takePendingNewFigure()
396
+ if (!waiting) return
397
+ const view = this.session.read()
398
+ try {
399
+ this.session.applyProposal(waiting.steps, view.revision!, view.context!)
400
+ waiting.settle({ applied: true })
401
+ } catch (error) {
402
+ waiting.settle({ applied: false, code: (error as { code?: string })?.code, error: error instanceof Error ? error.message : String(error) })
403
+ }
404
+ }
372
405
  /** The first save's question, over the names this domain already uses (V2 asked the same). */
373
406
  private askFirstSave = async (name: string): Promise<FirstSave | undefined> => {
374
407
  const taken = await fetchFigureTypeNames().catch(() => [] as string[])
package/client/types.ts CHANGED
@@ -102,45 +102,6 @@ export interface FigureFinding {
102
102
  at?: string
103
103
  }
104
104
 
105
- /**
106
- * 저작 보조가 낸 후보.
107
- *
108
- * **정본이 아니다.** 저작자가 보고 받아야 정본이 된다 — 서버는 저장하지 않고
109
- * 돌려주기만 한다.
110
- */
111
- export interface FigureProposal {
112
- /** 후보 정본. JSON 문자열이다 — 화면이 파싱한다. */
113
- source: string
114
- score: number
115
- grade: string
116
- triangles: number
117
- /** 재질 묶음 = 인스턴스 하나가 무는 draw call. */
118
- groups: number
119
- /** Rule-based review of measurable design concerns; it is not an aesthetic verdict. */
120
- quality: {
121
- status: 'ready' | 'review'
122
- summary: string
123
- findings: Array<{ dimension: string; code: string; message: string }>
124
- visualRegions: number
125
- visualCoverage: number
126
- visualReadability: number
127
- }
128
- /** 형식은 맞으나 정책을 넘은 것. 막지 않는다 — 저작자가 정한다. */
129
- violations: FigureFinding[]
130
- /** 몇 번 만에 됐나. 진단용이다. */
131
- attempts: number
132
- }
133
-
134
- /** 사람이 후보를 수락하거나 버릴 때 직접 남긴 현재 저작 세션의 참고 신호. */
135
- export interface ProposalFeedback {
136
- outcome: 'accepted' | 'discarded'
137
- selectedChanges: number
138
- totalChanges: number
139
- note?: string
140
- grade?: string
141
- quality?: { status?: 'ready' | 'review'; findingCodes?: string[] }
142
- }
143
-
144
105
  /**
145
106
  * 발행 판정 한 줄.
146
107
  *
@@ -1,19 +1,31 @@
1
1
  /*
2
- * What the AI dock does with a figure candidate - figure-ui declares it because figure-service declares the tool
2
+ * What the AI dock does with a figure proposal - figure-ui declares it because figure-service declares the tool
3
3
  * (`proposeFigureCandidate`, design/ai/shared-dock.md §4).
4
4
  *
5
- * Today a candidate cannot be taken anywhere. The proposer writes V2 figures and the modeller opens V3 only; the V2
6
- * modeller that reviewed candidates was removed with its listeners (`figure-ai-review`, `figure-ai-stage`), and the
7
- * server already refuses to propose for a V3 figure (`V3_PROPOSAL_UNAVAILABLE`). So the outcome is `unavailable`,
8
- * said to the person in words, not a silent pass - until a proposer writes V3 changes, which the modeller takes
9
- * through `figure-v3-apply`.
5
+ * A proposal is a list of the modeller's own commands (ADR-0094). Running it applies them in the modeller as one step
6
+ * of its history: a revision on the figure open there, at the version the model saw; a new figure on a new figure the
7
+ * modeller opens. The outcome is said in words on every branch (`v3-ai-proposal.ts`).
10
8
  */
11
9
  import { i18next } from '@operato/i18n';
10
+ import { navigate } from '@operato/shell';
12
11
  import { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js';
12
+ import { modellerPathOf } from './modeller/modeller-route.js';
13
+ import { pendNewFigure, runFigureProposal } from './modeller/v3-ai-proposal.js';
13
14
  export const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate';
14
- registerProposalHandler(FIGURE_CANDIDATE_TOOL, async (_proposal, context) => {
15
- const note = i18next.t('figure.text.candidate-cannot-open');
16
- context.notify('info', note);
17
- return { verdict: 'unavailable', errorCode: 'v3-review-missing', note };
15
+ registerProposalHandler(FIGURE_CANDIDATE_TOOL, async (proposal, context) => {
16
+ const outcome = await runFigureProposal(proposal, {
17
+ offer(detail) {
18
+ window.dispatchEvent(new CustomEvent('figure-v3-apply', { detail }));
19
+ return detail;
20
+ },
21
+ openNew(steps) {
22
+ const opened = pendNewFigure(steps);
23
+ navigate(modellerPathOf());
24
+ return opened;
25
+ }
26
+ }, (key, params) => i18next.t(key, params));
27
+ if (outcome.note)
28
+ context.notify(outcome.verdict === 'accepted' ? 'info' : 'error', outcome.note);
29
+ return outcome;
18
30
  });
19
31
  //# sourceMappingURL=figure-assistant.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"figure-assistant.js","sourceRoot":"","sources":["../client/figure-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAA;AAE3F,MAAM,CAAC,MAAM,qBAAqB,GAAG,wBAAwB,CAAA;AAE7D,uBAAuB,CAAC,qBAAqB,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;IAC1E,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAA;IAC3D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC5B,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAA;AACzE,CAAC,CAAC,CAAA","sourcesContent":["/*\n * What the AI dock does with a figure candidate - figure-ui declares it because figure-service declares the tool\n * (`proposeFigureCandidate`, design/ai/shared-dock.md §4).\n *\n * Today a candidate cannot be taken anywhere. The proposer writes V2 figures and the modeller opens V3 only; the V2\n * modeller that reviewed candidates was removed with its listeners (`figure-ai-review`, `figure-ai-stage`), and the\n * server already refuses to propose for a V3 figure (`V3_PROPOSAL_UNAVAILABLE`). So the outcome is `unavailable`,\n * said to the person in words, not a silent pass - until a proposer writes V3 changes, which the modeller takes\n * through `figure-v3-apply`.\n */\nimport { i18next } from '@operato/i18n'\nimport { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js'\n\nexport const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate'\n\nregisterProposalHandler(FIGURE_CANDIDATE_TOOL, async (_proposal, context) => {\n const note = i18next.t('figure.text.candidate-cannot-open')\n context.notify('info', note)\n return { verdict: 'unavailable', errorCode: 'v3-review-missing', note }\n})\n"]}
1
+ {"version":3,"file":"figure-assistant.js","sourceRoot":"","sources":["../client/figure-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA0B,MAAM,8BAA8B,CAAA;AAEvG,MAAM,CAAC,MAAM,qBAAqB,GAAG,wBAAwB,CAAA;AAE7D,uBAAuB,CAAC,qBAAqB,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IACzE,MAAM,OAAO,GAAG,MAAM,iBAAiB,CACrC,QAAQ,EACR;QACE,KAAK,CAAC,MAAyB;YAC7B,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;YACpE,OAAO,MAAM,CAAA;QACf,CAAC;QACD,OAAO,CAAC,KAAK;YACX,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;YACnC,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAA;YAC1B,OAAO,MAAM,CAAA;QACf,CAAC;KACF,EACD,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CACxC,CAAA;IACD,IAAI,OAAO,CAAC,IAAI;QAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACjG,OAAO,OAAO,CAAA;AAChB,CAAC,CAAC,CAAA","sourcesContent":["/*\n * What the AI dock does with a figure proposal - figure-ui declares it because figure-service declares the tool\n * (`proposeFigureCandidate`, design/ai/shared-dock.md §4).\n *\n * A proposal is a list of the modeller's own commands (ADR-0094). Running it applies them in the modeller as one step\n * of its history: a revision on the figure open there, at the version the model saw; a new figure on a new figure the\n * modeller opens. The outcome is said in words on every branch (`v3-ai-proposal.ts`).\n */\nimport { i18next } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\nimport { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js'\n\nimport { modellerPathOf } from './modeller/modeller-route.js'\nimport { pendNewFigure, runFigureProposal, type FigureApplyDetail } from './modeller/v3-ai-proposal.js'\n\nexport const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate'\n\nregisterProposalHandler(FIGURE_CANDIDATE_TOOL, async (proposal, context) => {\n const outcome = await runFigureProposal(\n proposal,\n {\n offer(detail: FigureApplyDetail) {\n window.dispatchEvent(new CustomEvent('figure-v3-apply', { detail }))\n return detail\n },\n openNew(steps) {\n const opened = pendNewFigure(steps)\n navigate(modellerPathOf())\n return opened\n }\n },\n (key, params) => i18next.t(key, params)\n )\n if (outcome.note) context.notify(outcome.verdict === 'accepted' ? 'info' : 'error', outcome.note)\n return outcome\n})\n"]}
@@ -1,4 +1,4 @@
1
- import type { Figure, FigureFinding, FigureInspection, FigureListResult, FigurePatch, FigureProposal, ProposalFeedback, FigureVersion, NewFigure } from '../types.js';
1
+ import type { Figure, FigureFinding, FigureInspection, FigureListResult, FigurePatch, FigureVersion, NewFigure } from '../types.js';
2
2
  /**
3
3
  * 목록을 가져온다.
4
4
  *
@@ -32,24 +32,6 @@ export interface SaveResult {
32
32
  export declare function createFigure(figure: NewFigure): Promise<SaveResult>;
33
33
  export declare function updateFigure(id: string, patch: FigurePatch): Promise<SaveResult>;
34
34
  export declare function deleteFigure(id: string): Promise<boolean>;
35
- /**
36
- * 저작 보조에게 후보를 시킨다.
37
- *
38
- * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고
39
- * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을
40
- * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.
41
- *
42
- * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.
43
- */
44
- export declare function proposeFigure(request: {
45
- prompt: string;
46
- base?: string;
47
- type?: string;
48
- palette: string[];
49
- refine?: boolean;
50
- feedback?: ProposalFeedback[];
51
- image?: File;
52
- }): Promise<FigureProposal>;
53
35
  /**
54
36
  * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.
55
37
  *
@@ -147,52 +147,6 @@ export async function deleteFigure(id) {
147
147
  });
148
148
  return unwrap(response, 'deleteFigure');
149
149
  }
150
- /**
151
- * 저작 보조에게 후보를 시킨다.
152
- *
153
- * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고
154
- * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을
155
- * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.
156
- *
157
- * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.
158
- */
159
- export async function proposeFigure(request) {
160
- const response = await client.mutate({
161
- mutation: gql `
162
- mutation ProposeFigure($request: ProposeRequest!) {
163
- proposeFigure(request: $request) {
164
- source
165
- score
166
- grade
167
- triangles
168
- groups
169
- quality {
170
- status
171
- summary
172
- findings {
173
- dimension
174
- code
175
- message
176
- }
177
- visualRegions
178
- visualCoverage
179
- visualReadability
180
- }
181
- attempts
182
- violations {
183
- code
184
- message
185
- }
186
- }
187
- }
188
- `,
189
- variables: { request },
190
- // @operato/graphql 는 이 신호가 있을 때만 Upload가 포함된 변수를 multipart로 인코딩한다.
191
- // 없으면 브라우저 File은 JSON에서 {}가 되어 서버 Upload scalar가 요청 자체를 거절한다.
192
- context: { hasUpload: !!request.image }
193
- });
194
- return unwrap(response, 'proposeFigure');
195
- }
196
150
  /**
197
151
  * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.
198
152
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../client/graphql/index.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { i18next } from '@operato/i18n'\nimport { client } from '@operato/graphql'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureProposal,\n ProposalFeedback,\n FigureVersion,\n NewFigure\n} from '../types.js'\n\n/**\n * 응답에서 값을 꺼낸다. **서버가 말한 사유를 그대로 올린다.**\n *\n * 전에는 `response.data.figures` 처럼 바로 꺼냈다. 서버가 오류를 돌려주면 `data` 가\n * 없으므로 `Cannot read properties of undefined (reading 'figures')` 가 났고, 화면에는\n * 그 문장이 그대로 떴다. 사용자는 자기가 무엇을 잘못했는지 알 수 없다 — 실제로 AI\n * 요청에서 그렇게 났다.\n *\n * 값이 `null` 인 것은 오류가 아니다. 지운 것을 물었을 때처럼 없는 것이 답인 경우가 있다.\n * 여기서 막는 것은 **`data` 자체가 없는 것**이다.\n */\nfunction unwrap<T>(response: { data?: Record<string, unknown>; errors?: readonly { message: string }[] }, field: string): T {\n const said = response.errors?.[0]?.message\n if (said) throw new Error(said)\n\n if (!response.data) {\n /* The field name stays off the screen because the user cannot act on it; it goes to the console. */\n console.error(`[figure-ui] the server returned no data for ${field}`)\n throw new Error(i18next.t('figure.text.server-returned-no-data'))\n }\n\n return response.data[field] as T\n}\n\n\n/**\n * 목록에 필요한 필드만 가져온다 — `source` 는 무겁고 목록에서 쓰지 않는다.\n *\n * **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림\n * 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로\n * 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를\n * 그것으로 알고, 판을 가리는 값으로도 쓴다.\n */\nconst FIGURE_LIST_FIELDS = `\n id\n type\n name\n description\n category\n tags\n state\n version\n score\n triangles\n groups\n format\n thumbnailUpdatedAt\n updatedAt\n updater { id name }\n`\n\n/**\n * 목록을 가져온다.\n *\n * 걸러 보기·정렬·쪽 나누기를 **그대로 넘긴다.** 화면이 `search`·`state` 같은 이름을 따로 만들어\n * filter 로 옮기던 것을 그만두었다 — 그 이름들은 격자(ox-grist)가 이미 컬럼 설정에서 만들어 주고,\n * 중간에 한 벌 더 두면 격자가 아는 조건과 서버에 가는 조건이 어긋난다.\n */\nexport async function fetchFigureList(params: {\n page?: number\n limit?: number\n filters?: unknown[]\n sortings?: { name: string; desc?: boolean }[]\n}): Promise<FigureListResult> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n figures(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} }\n total\n }\n }\n `,\n variables: {\n filters: params.filters ?? [],\n pagination: { page: params.page ?? 1, limit: params.limit ?? 30 },\n sortings: params.sortings?.length ? params.sortings : [{ name: 'updatedAt', desc: true }]\n },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureListResult>(response, 'figures')\n}\n\n/** 저작면에 필요한 전부 — `source` 를 포함한다. */\nexport async function fetchFigure(id: string): Promise<Figure> {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n figure(id: $id) {\n ${FIGURE_LIST_FIELDS}\n source\n properties\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<Figure>(response, 'figure')\n}\n\n/**\n * 이미 쓰이고 있는 타입 이름.\n *\n * `type` 은 만든 뒤에 고칠 수 없으므로 **만들기 전에** 알려 줘야 한다. 저장을 눌러\n * 거절당하고 나서 아는 것은 늦다.\n */\nexport async function fetchFigureTypeNames(): Promise<string[]> {\n const response = await client.query({\n query: gql`\n query {\n figureTypeNames\n }\n `,\n fetchPolicy: 'network-only'\n })\n\n return unwrap<string[] | null>(response, 'figureTypeNames') ?? []\n}\n\nexport interface SaveResult {\n figure: Figure\n /** 막지 않는 발견. 저장은 됐지만 화면이 보여 줘야 한다. */\n violations: FigureFinding[]\n}\n\nexport async function createFigure(figure: NewFigure): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($figure: NewFigure!) {\n createFigure(figure: $figure) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { figure }\n })\n\n return unwrap<SaveResult>(response, 'createFigure')\n}\n\nexport async function updateFigure(id: string, patch: FigurePatch): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: FigurePatch!) {\n updateFigure(id: $id, patch: $patch) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, patch }\n })\n\n return unwrap<SaveResult>(response, 'updateFigure')\n}\n\nexport async function deleteFigure(id: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteFigure(id: $id)\n }\n `,\n variables: { id }\n })\n\n return unwrap<boolean>(response, 'deleteFigure')\n}\n\n/**\n * 저작 보조에게 후보를 시킨다.\n *\n * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고\n * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을\n * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.\n *\n * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.\n */\nexport async function proposeFigure(request: {\n prompt: string\n base?: string\n type?: string\n palette: string[]\n refine?: boolean\n feedback?: ProposalFeedback[]\n image?: File\n}): Promise<FigureProposal> {\n const response = await client.mutate({\n mutation: gql`\n mutation ProposeFigure($request: ProposeRequest!) {\n proposeFigure(request: $request) {\n source\n score\n grade\n triangles\n groups\n quality {\n status\n summary\n findings {\n dimension\n code\n message\n }\n visualRegions\n visualCoverage\n visualReadability\n }\n attempts\n violations {\n code\n message\n }\n }\n }\n `,\n variables: { request },\n // @operato/graphql 는 이 신호가 있을 때만 Upload가 포함된 변수를 multipart로 인코딩한다.\n // 없으면 브라우저 File은 JSON에서 {}가 되어 서버 Upload scalar가 요청 자체를 거절한다.\n context: { hasUpload: !!request.image }\n })\n\n return unwrap<FigureProposal>(response, 'proposeFigure')\n}\n\n/**\n * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.\n *\n * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\n * 발행을 푼다(초안으로 돌아온다).\n */\nexport async function releaseFigure(id: string, comment?: string): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ReleaseFigure($id: String!, $comment: String) {\n releaseFigure(id: $id, comment: $comment) {\n id\n version\n state\n updatedAt\n }\n }\n `,\n variables: { id, comment }\n })\n\n return unwrap<Figure>(response, 'releaseFigure')\n}\n\n/** 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. */\nexport async function revertFigureVersion(id: string, version: number): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!) {\n revertFigureVersion(id: $id, version: $version) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, version }\n })\n\n return unwrap<SaveResult>(response, 'revertFigureVersion')\n}\n\n/**\n * 발행해도 되나 — **누르기 전에** 묻는다.\n *\n * 막는 것은 서버의 `releaseFigure` 이고 이것은 이유를 미리 보여 주기 위한 것이다. 허가증이\n * 아니므로 이 답이 통과라 해도 발행은 다시 판정을 받는다.\n */\nexport async function inspectFigure(id: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigure($id: String!) {\n inspectFigure(id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\n blocking\n }\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigure')\n}\n\n/**\n * 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.\n *\n * 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는\n * 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —\n * 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행판을 읽는다.\n */\nexport async function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigureDraft($source: String!, $id: String) {\n inspectFigureDraft(source: $source, id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\n blocking\n }\n }\n }\n `,\n variables: { source, id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigureDraft')\n}\n\n/** 발행된 판들 — 최근 것부터 열 개. */\n/**\n * 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.\n *\n * 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장\n * 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.\n */\nexport async function fetchReleasedSource(id: string): Promise<string | undefined> {\n const response = await client.query({\n query: gql`\n query FigureReleased($id: String!) {\n figureReleased(id: $id) {\n version\n source\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<{ source?: string }>(response, 'figureReleased')?.source ?? undefined\n}\n\nexport async function fetchFigureVersions(id: string): Promise<FigureVersion[]> {\n const response = await client.query({\n query: gql`\n query FigureVersions($id: String!) {\n figureVersions(id: $id) {\n version\n comment\n state\n updatedAt\n updater { id name }\n score\n triangles\n groups\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureVersion[]>(response, 'figureVersions') ?? []\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../client/graphql/index.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { i18next } from '@operato/i18n'\nimport { client } from '@operato/graphql'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureVersion,\n NewFigure\n} from '../types.js'\n\n/**\n * 응답에서 값을 꺼낸다. **서버가 말한 사유를 그대로 올린다.**\n *\n * 전에는 `response.data.figures` 처럼 바로 꺼냈다. 서버가 오류를 돌려주면 `data` 가\n * 없으므로 `Cannot read properties of undefined (reading 'figures')` 가 났고, 화면에는\n * 그 문장이 그대로 떴다. 사용자는 자기가 무엇을 잘못했는지 알 수 없다 — 실제로 AI\n * 요청에서 그렇게 났다.\n *\n * 값이 `null` 인 것은 오류가 아니다. 지운 것을 물었을 때처럼 없는 것이 답인 경우가 있다.\n * 여기서 막는 것은 **`data` 자체가 없는 것**이다.\n */\nfunction unwrap<T>(response: { data?: Record<string, unknown>; errors?: readonly { message: string }[] }, field: string): T {\n const said = response.errors?.[0]?.message\n if (said) throw new Error(said)\n\n if (!response.data) {\n /* The field name stays off the screen because the user cannot act on it; it goes to the console. */\n console.error(`[figure-ui] the server returned no data for ${field}`)\n throw new Error(i18next.t('figure.text.server-returned-no-data'))\n }\n\n return response.data[field] as T\n}\n\n\n/**\n * 목록에 필요한 필드만 가져온다 — `source` 는 무겁고 목록에서 쓰지 않는다.\n *\n * **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림\n * 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로\n * 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를\n * 그것으로 알고, 판을 가리는 값으로도 쓴다.\n */\nconst FIGURE_LIST_FIELDS = `\n id\n type\n name\n description\n category\n tags\n state\n version\n score\n triangles\n groups\n format\n thumbnailUpdatedAt\n updatedAt\n updater { id name }\n`\n\n/**\n * 목록을 가져온다.\n *\n * 걸러 보기·정렬·쪽 나누기를 **그대로 넘긴다.** 화면이 `search`·`state` 같은 이름을 따로 만들어\n * filter 로 옮기던 것을 그만두었다 — 그 이름들은 격자(ox-grist)가 이미 컬럼 설정에서 만들어 주고,\n * 중간에 한 벌 더 두면 격자가 아는 조건과 서버에 가는 조건이 어긋난다.\n */\nexport async function fetchFigureList(params: {\n page?: number\n limit?: number\n filters?: unknown[]\n sortings?: { name: string; desc?: boolean }[]\n}): Promise<FigureListResult> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n figures(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} }\n total\n }\n }\n `,\n variables: {\n filters: params.filters ?? [],\n pagination: { page: params.page ?? 1, limit: params.limit ?? 30 },\n sortings: params.sortings?.length ? params.sortings : [{ name: 'updatedAt', desc: true }]\n },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureListResult>(response, 'figures')\n}\n\n/** 저작면에 필요한 전부 — `source` 를 포함한다. */\nexport async function fetchFigure(id: string): Promise<Figure> {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n figure(id: $id) {\n ${FIGURE_LIST_FIELDS}\n source\n properties\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<Figure>(response, 'figure')\n}\n\n/**\n * 이미 쓰이고 있는 타입 이름.\n *\n * `type` 은 만든 뒤에 고칠 수 없으므로 **만들기 전에** 알려 줘야 한다. 저장을 눌러\n * 거절당하고 나서 아는 것은 늦다.\n */\nexport async function fetchFigureTypeNames(): Promise<string[]> {\n const response = await client.query({\n query: gql`\n query {\n figureTypeNames\n }\n `,\n fetchPolicy: 'network-only'\n })\n\n return unwrap<string[] | null>(response, 'figureTypeNames') ?? []\n}\n\nexport interface SaveResult {\n figure: Figure\n /** 막지 않는 발견. 저장은 됐지만 화면이 보여 줘야 한다. */\n violations: FigureFinding[]\n}\n\nexport async function createFigure(figure: NewFigure): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($figure: NewFigure!) {\n createFigure(figure: $figure) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { figure }\n })\n\n return unwrap<SaveResult>(response, 'createFigure')\n}\n\nexport async function updateFigure(id: string, patch: FigurePatch): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: FigurePatch!) {\n updateFigure(id: $id, patch: $patch) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, patch }\n })\n\n return unwrap<SaveResult>(response, 'updateFigure')\n}\n\nexport async function deleteFigure(id: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteFigure(id: $id)\n }\n `,\n variables: { id }\n })\n\n return unwrap<boolean>(response, 'deleteFigure')\n}\n\n/**\n * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.\n *\n * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\n * 발행을 푼다(초안으로 돌아온다).\n */\nexport async function releaseFigure(id: string, comment?: string): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ReleaseFigure($id: String!, $comment: String) {\n releaseFigure(id: $id, comment: $comment) {\n id\n version\n state\n updatedAt\n }\n }\n `,\n variables: { id, comment }\n })\n\n return unwrap<Figure>(response, 'releaseFigure')\n}\n\n/** 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. */\nexport async function revertFigureVersion(id: string, version: number): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!) {\n revertFigureVersion(id: $id, version: $version) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, version }\n })\n\n return unwrap<SaveResult>(response, 'revertFigureVersion')\n}\n\n/**\n * 발행해도 되나 — **누르기 전에** 묻는다.\n *\n * 막는 것은 서버의 `releaseFigure` 이고 이것은 이유를 미리 보여 주기 위한 것이다. 허가증이\n * 아니므로 이 답이 통과라 해도 발행은 다시 판정을 받는다.\n */\nexport async function inspectFigure(id: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigure($id: String!) {\n inspectFigure(id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\n blocking\n }\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigure')\n}\n\n/**\n * 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.\n *\n * 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는\n * 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —\n * 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행판을 읽는다.\n */\nexport async function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigureDraft($source: String!, $id: String) {\n inspectFigureDraft(source: $source, id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\n blocking\n }\n }\n }\n `,\n variables: { source, id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigureDraft')\n}\n\n/** 발행된 판들 — 최근 것부터 열 개. */\n/**\n * 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.\n *\n * 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장\n * 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.\n */\nexport async function fetchReleasedSource(id: string): Promise<string | undefined> {\n const response = await client.query({\n query: gql`\n query FigureReleased($id: String!) {\n figureReleased(id: $id) {\n version\n source\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<{ source?: string }>(response, 'figureReleased')?.source ?? undefined\n}\n\nexport async function fetchFigureVersions(id: string): Promise<FigureVersion[]> {\n const response = await client.query({\n query: gql`\n query FigureVersions($id: String!) {\n figureVersions(id: $id) {\n version\n comment\n state\n updatedAt\n updater { id name }\n score\n triangles\n groups\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureVersion[]>(response, 'figureVersions') ?? []\n}\n"]}
@@ -15,7 +15,7 @@ export declare class FigureV3Capability extends FigureV3Capability_base {
15
15
  /** The selected part, whose places are marked. */
16
16
  selected: string;
17
17
  locked: boolean;
18
- render(): typeof nothing | import("lit-html").TemplateResult<1>;
18
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
19
19
  private setRung;
20
20
  private pick;
21
21
  }
@@ -18,7 +18,7 @@ export declare class FigureV3Clips extends FigureV3Clips_base {
18
18
  /** Where each clip is scrubbed to, in seconds. */
19
19
  private at;
20
20
  private adding;
21
- render(): typeof nothing | import("lit-html").TemplateResult<1>;
21
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
22
22
  private renderClip;
23
23
  private renderTrack;
24
24
  private renderAdd;
@@ -22,7 +22,7 @@ export declare class FigureV3Controls extends FigureV3Controls_base {
22
22
  private open;
23
23
  /** Values in hand while a slider is dragged: the dial and the number follow them before the release commits. */
24
24
  private live;
25
- render(): typeof nothing | import("lit-html").TemplateResult<1>;
25
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
26
26
  private renderControl;
27
27
  private renderDial;
28
28
  private renderSettings;
@@ -34,7 +34,7 @@ export declare class FigureV3Links extends FigureV3Links_base {
34
34
  /** The motion form is open over the part's own motion, to change it where it is. */
35
35
  private editingMotion;
36
36
  willUpdate(changed: Map<string, unknown>): void;
37
- render(): typeof nothing | import("lit-html").TemplateResult<1>;
37
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
38
38
  /** Where things are held on this part: a seat things rest on, or a grip things are held in (parity row ⑤). */
39
39
  private renderPlaces;
40
40
  /** The figure holds things: `Capacity` and `Holdable`, the ladder's middle rung. One part edit, one undo step. */