@snaptrude/plugin-core 0.6.0 → 0.7.1

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 (89) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/api-manifest.json +206 -704
  3. package/dist/api/core/index.d.ts +5 -0
  4. package/dist/api/core/index.d.ts.map +1 -1
  5. package/dist/api/core/proposals/index.d.ts +475 -0
  6. package/dist/api/core/proposals/index.d.ts.map +1 -0
  7. package/dist/api/design/create/index.d.ts +209 -0
  8. package/dist/api/design/create/index.d.ts.map +1 -1
  9. package/dist/api/design/doors/index.d.ts +159 -0
  10. package/dist/api/design/doors/index.d.ts.map +1 -1
  11. package/dist/api/design/furniture/index.d.ts +34 -5
  12. package/dist/api/design/furniture/index.d.ts.map +1 -1
  13. package/dist/api/design/index.d.ts +83 -0
  14. package/dist/api/design/index.d.ts.map +1 -1
  15. package/dist/api/design/lock.d.ts +26 -0
  16. package/dist/api/design/lock.d.ts.map +1 -1
  17. package/dist/api/design/materials/index.d.ts +159 -2
  18. package/dist/api/design/materials/index.d.ts.map +1 -1
  19. package/dist/api/design/query/index.d.ts +115 -5
  20. package/dist/api/design/query/index.d.ts.map +1 -1
  21. package/dist/api/design/selection/index.d.ts +2 -2
  22. package/dist/api/design/transform/index.d.ts +83 -2
  23. package/dist/api/design/transform/index.d.ts.map +1 -1
  24. package/dist/api/design/update/index.d.ts +168 -0
  25. package/dist/api/design/update/index.d.ts.map +1 -1
  26. package/dist/api/design/windows/index.d.ts +113 -2
  27. package/dist/api/design/windows/index.d.ts.map +1 -1
  28. package/dist/api/entity/story.d.ts +101 -7
  29. package/dist/api/entity/story.d.ts.map +1 -1
  30. package/dist/api/presentation/aiInspiration.d.ts +1967 -7
  31. package/dist/api/presentation/aiInspiration.d.ts.map +1 -1
  32. package/dist/api/presentation/import.d.ts +49 -4
  33. package/dist/api/presentation/import.d.ts.map +1 -1
  34. package/dist/api/presentation/index.d.ts +2 -3
  35. package/dist/api/presentation/index.d.ts.map +1 -1
  36. package/dist/api/presentation/views.d.ts +210 -5
  37. package/dist/api/presentation/views.d.ts.map +1 -1
  38. package/dist/api/program/areas.d.ts +42 -12
  39. package/dist/api/program/areas.d.ts.map +1 -1
  40. package/dist/api/program/index.d.ts +3 -11
  41. package/dist/api/program/index.d.ts.map +1 -1
  42. package/dist/api/program/layout.d.ts +186 -11
  43. package/dist/api/program/layout.d.ts.map +1 -1
  44. package/dist/errors/codes.d.ts +34 -0
  45. package/dist/errors/codes.d.ts.map +1 -0
  46. package/dist/errors/envelope.d.ts +56 -0
  47. package/dist/errors/envelope.d.ts.map +1 -0
  48. package/dist/errors/index.d.ts +6 -0
  49. package/dist/errors/index.d.ts.map +1 -0
  50. package/dist/errors/plugin-error.d.ts +69 -0
  51. package/dist/errors/plugin-error.d.ts.map +1 -0
  52. package/dist/host-utils.d.ts +4 -0
  53. package/dist/host-utils.d.ts.map +1 -1
  54. package/dist/index.cjs +1993 -1033
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1874 -1030
  59. package/dist/index.js.map +1 -1
  60. package/package.json +2 -1
  61. package/scripts/generate-manifest.test.mjs +77 -0
  62. package/src/api/core/index.ts +5 -0
  63. package/src/api/core/proposals/index.ts +509 -0
  64. package/src/api/design/create/index.ts +249 -0
  65. package/src/api/design/doors/index.ts +168 -0
  66. package/src/api/design/furniture/index.ts +36 -5
  67. package/src/api/design/index.ts +87 -0
  68. package/src/api/design/lock.ts +27 -0
  69. package/src/api/design/materials/index.ts +182 -2
  70. package/src/api/design/query/index.ts +81 -0
  71. package/src/api/design/transform/index.ts +74 -2
  72. package/src/api/design/update/index.ts +167 -0
  73. package/src/api/design/windows/index.ts +128 -2
  74. package/src/api/entity/story.ts +105 -7
  75. package/src/api/presentation/aiInspiration.ts +761 -7
  76. package/src/api/presentation/import.ts +45 -4
  77. package/src/api/presentation/index.ts +2 -3
  78. package/src/api/presentation/views.ts +221 -5
  79. package/src/api/program/areas.ts +34 -12
  80. package/src/api/program/index.ts +3 -11
  81. package/src/api/program/layout.ts +195 -11
  82. package/src/errors/codes.ts +136 -0
  83. package/src/errors/envelope.ts +75 -0
  84. package/src/errors/index.ts +21 -0
  85. package/src/errors/plugin-error.ts +134 -0
  86. package/src/host-utils.ts +4 -0
  87. package/src/index.ts +1 -0
  88. package/test/errors.test.mjs +184 -0
  89. package/api-manifest.full.json +0 -5600
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snaptrude/plugin-core",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -32,6 +32,7 @@
32
32
  "build": "tsup --clean",
33
33
  "dev": "tsup --watch",
34
34
  "clean-dist": "rm -rf dist",
35
+ "test": "node --test test/*.test.mjs",
35
36
  "generate:manifest": "node scripts/generate-manifest.mjs",
36
37
  "generate:manifest:check": "node scripts/generate-manifest.mjs --check",
37
38
  "generate:manifest:all": "node scripts/generate-manifest.mjs --all"
@@ -131,6 +131,12 @@ test("promoted methods are present in the manifest", () => {
131
131
  "analysis.heatmaps.renderGrid",
132
132
  "analysis.heatmaps.reset",
133
133
  "analysis.heatmaps.isActive",
134
+ // 2026-07-09 promotion — program.layout became an async-job family (was
135
+ // author-ahead optional `arrange`/`pack`; now required + getState/cancel):
136
+ "program.layout.arrange",
137
+ "program.layout.pack",
138
+ "program.layout.getState",
139
+ "program.layout.cancel",
134
140
  ]
135
141
  for (const p of promoted) {
136
142
  assert.ok(
@@ -213,3 +219,74 @@ test("v2 family writes are IN the discovery manifest (hosts landed, promoted)",
213
219
  )
214
220
  }
215
221
  })
222
+
223
+ test("0.7.0 batch — new families & methods present with intent + prompts", () => {
224
+ // The 0.7.0 wave: core.proposals, the program.layout async-job family,
225
+ // door/window catalogs + placement, furniture categories, smart layout,
226
+ // align, per-face materials, staircase read/write, footprint-area lock,
227
+ // storey duplicate, SVG import, and view display settings. Every entry is a
228
+ // required `abstract` member (react hosts landed), so each must be in the
229
+ // discovery manifest with a summary, >= 3 example prompts, and a result type.
230
+ const batch = [
231
+ // core.proposals — design-option infrastructure
232
+ "core.proposals.list",
233
+ "core.proposals.get",
234
+ "core.proposals.getActive",
235
+ "core.proposals.listForComponent",
236
+ "core.proposals.isActive",
237
+ "core.proposals.create",
238
+ "core.proposals.rename",
239
+ "core.proposals.setActive",
240
+ "core.proposals.delete",
241
+ // program.layout — async space-solver job family
242
+ "program.layout.arrange",
243
+ "program.layout.pack",
244
+ "program.layout.getState",
245
+ "program.layout.cancel",
246
+ // door / window catalogs (shared DTOs) + placement creators
247
+ "design.doors.listCatalogGroups",
248
+ "design.doors.listCatalog",
249
+ "design.doors.getCatalogItem",
250
+ "design.doors.exists",
251
+ "design.windows.listCatalogGroups",
252
+ "design.windows.listCatalog",
253
+ "design.windows.getCatalogItem",
254
+ "design.windows.exists",
255
+ "design.create.door",
256
+ "design.create.window",
257
+ // furniture categories + smart layout
258
+ "design.furniture.listCategories",
259
+ "design.create.smartLayout",
260
+ // align
261
+ "design.transform.align",
262
+ // per-face materials
263
+ "design.materials.applyToFaces",
264
+ "design.materials.resetFaces",
265
+ "design.materials.getByFace",
266
+ "design.materials.listByFace",
267
+ // staircase read/write
268
+ "design.query.getStaircaseParams",
269
+ "design.update.staircase",
270
+ // footprint-area lock
271
+ "design.lockArea",
272
+ "design.unlockArea",
273
+ "design.isAreaLocked",
274
+ "design.listAreaLocked",
275
+ // storey duplicate
276
+ "entity.story.duplicate",
277
+ // present-canvas SVG import + view display settings
278
+ "presentation.import.svg",
279
+ "presentation.views.getSettings",
280
+ "presentation.views.updateSettings",
281
+ ]
282
+ for (const p of batch) {
283
+ const m = byPath.get(p)
284
+ assert.ok(m, `missing 0.7.0 batch method: ${p}`)
285
+ assert.ok(m.summary.length > 0, `${p} has no intent summary`)
286
+ assert.ok(m.examplePrompts.length >= 3, `${p} has < 3 example prompts`)
287
+ assert.ok(
288
+ m.resultType && m.resultType !== "void",
289
+ `${p} has no result type`,
290
+ )
291
+ }
292
+ })
@@ -10,6 +10,7 @@ import { PluginCoreBuildingsApi } from "./buildings"
10
10
  import { PluginCoreLayersApi } from "./layers"
11
11
  import { PluginCoreTagsApi } from "./tags"
12
12
  import { PluginCoreIoApi } from "./io"
13
+ import { PluginCoreProposalsApi } from "./proposals"
13
14
 
14
15
  /**
15
16
  * Core primitives and infrastructure used across the plugin API.
@@ -26,6 +27,7 @@ import { PluginCoreIoApi } from "./io"
26
27
  * - {@linkcode PluginCoreApi.layers} — Read & toggle storey drawing/reference layers
27
28
  * - {@linkcode PluginCoreApi.tags} — Tag catalog + assignment
28
29
  * - {@linkcode PluginCoreApi.io} — Import files (image/pdf/cad/model/terrain), poll jobs, manage underlays
30
+ * - {@linkcode PluginCoreApi.proposals} — Design options: read, create, rename, switch, delete
29
31
  */
30
32
  export abstract class PluginCoreApi {
31
33
  /** Vector and quaternion math utilities. See {@linkcode PluginMathApi}. */
@@ -52,6 +54,8 @@ export abstract class PluginCoreApi {
52
54
  public abstract tags: PluginCoreTagsApi
53
55
  /** I/O — file import, import jobs, and underlay management. See {@linkcode PluginCoreIoApi}. */
54
56
  public abstract io: PluginCoreIoApi
57
+ /** Proposals — design options: read, create, rename, switch, delete. See {@linkcode PluginCoreProposalsApi}. */
58
+ public abstract proposals: PluginCoreProposalsApi
55
59
 
56
60
  constructor() {}
57
61
  }
@@ -68,3 +72,4 @@ export * from "./buildings"
68
72
  export * from "./layers"
69
73
  export * from "./tags"
70
74
  export * from "./io"
75
+ export * from "./proposals"
@@ -0,0 +1,509 @@
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../../types"
3
+ import { ComponentHandle } from "../../../handles"
4
+
5
+ /**
6
+ * Proposals — read and edit the project's design options ("proposals").
7
+ *
8
+ * A **proposal** is a design option: one of several variants of the same
9
+ * project. Each proposal owns a set of buckets (a bucket == a drawing layer),
10
+ * and switching the active proposal shows/hides the buckets it owns. Proposals
11
+ * are **infrastructure**, not design — they own the buckets/layers geometry
12
+ * lives in — so they sit under `core`, not `design` (constitution §2A).
13
+ *
14
+ * Reads return plain records and never throw for a missing proposal (`get` and
15
+ * `getActive` return `null`, `list` / `listForComponent` return an empty list,
16
+ * `isActive` returns `false`). Ids are stable public strings.
17
+ *
18
+ * On legacy / no-proposal projects the scoping predicates degrade gracefully:
19
+ * `getActive` returns `null` and the reads return empty.
20
+ *
21
+ * Proposal writes are **not undoable** — create / rename / setActive / delete
22
+ * persist directly and bypass the undo stack; a `core.history.undo` will not
23
+ * reverse them. Each proposal keeps its own isolated undo stack for the
24
+ * geometry edits made inside it.
25
+ *
26
+ * Accessed via `snaptrude.core.proposals`.
27
+ */
28
+ export abstract class PluginCoreProposalsApi {
29
+ constructor() {}
30
+
31
+ /**
32
+ * List the design options (proposals) in the active project.
33
+ *
34
+ * @returns A {@linkcode PluginCoreProposalsListResult} with a `proposals`
35
+ * array (empty on a legacy / no-proposal project).
36
+ *
37
+ * @examplePrompt List all the design options in this project
38
+ * @examplePrompt How many proposals does this project have?
39
+ * @examplePrompt Show every design variant with its name
40
+ *
41
+ * # Example
42
+ * ```ts
43
+ * const { proposals } = await snaptrude.core.proposals.list()
44
+ * for (const p of proposals) console.log(p.id, p.name, p.isActive)
45
+ * ```
46
+ */
47
+ public abstract list(): PluginApiReturn<PluginCoreProposalsListResult>
48
+
49
+ /**
50
+ * Get a single proposal by id.
51
+ *
52
+ * @param proposalId - The id of the proposal to read.
53
+ * @returns The matching {@linkcode PluginCoreProposalRef}, or `null` if no
54
+ * proposal has that id.
55
+ *
56
+ * @examplePrompt Get the proposal with id prop_1
57
+ * @examplePrompt Look up a single design option by its id
58
+ * @examplePrompt Which buckets belong to proposal prop_2?
59
+ *
60
+ * # Example
61
+ * ```ts
62
+ * const proposal = await snaptrude.core.proposals.get("prop_1")
63
+ * if (proposal) console.log(proposal.name, proposal.bucketIds)
64
+ * ```
65
+ */
66
+ public abstract get(
67
+ proposalId: string,
68
+ ): PluginApiReturn<PluginCoreProposalsGetResult>
69
+
70
+ /**
71
+ * Get the active proposal.
72
+ *
73
+ * The active proposal is the design option currently shown and edited.
74
+ * Honors a present-mode capture override: while a present-mode capture is in
75
+ * flight this returns the captured proposal, not the redux-active one. Paired
76
+ * with {@linkcode PluginCoreProposalsApi.setActive}.
77
+ *
78
+ * @returns The active {@linkcode PluginCoreProposalRef}, or `null` on a legacy
79
+ * / no-proposal project where nothing is active.
80
+ *
81
+ * @examplePrompt Which design option is active?
82
+ * @examplePrompt Get the current proposal
83
+ * @examplePrompt What proposal am I working in?
84
+ *
85
+ * # Example
86
+ * ```ts
87
+ * const active = await snaptrude.core.proposals.getActive()
88
+ * if (active) console.log(active.id, active.name)
89
+ * ```
90
+ */
91
+ public abstract getActive(): PluginApiReturn<PluginCoreProposalsGetActiveResult>
92
+
93
+ /**
94
+ * List the proposals a component belongs to.
95
+ *
96
+ * Returns **every** proposal whose buckets include the component — a bucket
97
+ * can be shared across proposals, so a component may appear in more than one.
98
+ *
99
+ * @param component - The component to look up.
100
+ * @returns A {@linkcode PluginCoreProposalsListForComponentResult} with a
101
+ * `proposals` array (empty when the component is out of scope or the project
102
+ * has no proposals).
103
+ *
104
+ * @examplePrompt Which design options is this component part of?
105
+ * @examplePrompt List the proposals that contain this wall
106
+ * @examplePrompt Is this element shared across multiple design variants?
107
+ *
108
+ * # Example
109
+ * ```ts
110
+ * const { proposals } = await snaptrude.core.proposals.listForComponent(wall)
111
+ * console.log(`shared across ${proposals.length} proposals`)
112
+ * ```
113
+ */
114
+ public abstract listForComponent(
115
+ component: ComponentHandle,
116
+ ): PluginApiReturn<PluginCoreProposalsListForComponentResult>
117
+
118
+ /**
119
+ * Test whether a proposal is the active one.
120
+ *
121
+ * @param proposalId - The id of the proposal to test.
122
+ * @returns `true` if it is the active proposal, otherwise `false` (including
123
+ * when no proposal has that id).
124
+ *
125
+ * @examplePrompt Is proposal prop_2 the active one?
126
+ * @examplePrompt Check whether this design option is currently shown
127
+ * @examplePrompt Am I working in proposal prop_1?
128
+ *
129
+ * # Example
130
+ * ```ts
131
+ * if (await snaptrude.core.proposals.isActive("prop_1")) {
132
+ * console.log("prop_1 is active")
133
+ * }
134
+ * ```
135
+ */
136
+ public abstract isActive(proposalId: string): PluginApiReturn<boolean>
137
+
138
+ /**
139
+ * Create a new design option (proposal).
140
+ *
141
+ * **Pro-gated** (creating proposals requires a Pro plan, matching the UI) and
142
+ * **not undoable**. Creates a bare proposal with a single empty default
143
+ * bucket — it does **not** carry forward the current proposal's layers, views,
144
+ * or targets. The name is capped at 24 characters.
145
+ *
146
+ * @param name - Display name of the new proposal (capped at 24 chars).
147
+ * @returns The new proposal's id (read the full record back with
148
+ * {@linkcode PluginCoreProposalsApi.get}).
149
+ * @throws If the caller is not on a Pro plan, or the proposal could not be
150
+ * created.
151
+ *
152
+ * @examplePrompt Create a design option called Scheme B
153
+ * @examplePrompt Add a new proposal named Option 2
154
+ * @examplePrompt Make a fresh design variant to explore
155
+ *
156
+ * # Example
157
+ * ```ts
158
+ * const { id } = await snaptrude.core.proposals.create("Scheme B")
159
+ * ```
160
+ */
161
+ public abstract create(
162
+ name: string,
163
+ ): PluginApiReturn<PluginCoreProposalsCreateResult>
164
+
165
+ /**
166
+ * Rename a proposal.
167
+ *
168
+ * **Not undoable.** The name is capped at 24 characters.
169
+ *
170
+ * @param proposalId - The id of the proposal to rename.
171
+ * @param name - The new display name (capped at 24 chars).
172
+ * @returns The updated {@linkcode PluginCoreProposalRef}.
173
+ * @throws If no proposal has the given id, or the name is empty / duplicates
174
+ * another proposal.
175
+ *
176
+ * @examplePrompt Rename proposal prop_1 to Final Scheme
177
+ * @examplePrompt Change the name of this design option
178
+ * @examplePrompt Call proposal prop_2 "Option A"
179
+ *
180
+ * # Example
181
+ * ```ts
182
+ * const proposal = await snaptrude.core.proposals.rename("prop_1", "Final Scheme")
183
+ * ```
184
+ */
185
+ public abstract rename(
186
+ proposalId: string,
187
+ name: string,
188
+ ): PluginApiReturn<PluginCoreProposalsRenameResult>
189
+
190
+ /**
191
+ * Switch the active proposal.
192
+ *
193
+ * A **heavy scene switch** and **not undoable**. Switching clears the current
194
+ * selection, resets AI / streaming state, cancels proposal-scoped operations,
195
+ * retoggles bucket visibility to the new proposal's owned set, restores its
196
+ * per-proposal site/FSI settings, and realigns the views panel. After the
197
+ * switch, **writes** are guarded to the new active proposal (edits targeting
198
+ * components outside it are rejected), but **list reads are NOT
199
+ * proposal-filtered** — `design.query.list*` and similar reads stay
200
+ * project-wide; filter their results via
201
+ * {@linkcode PluginCoreProposalsApi.listForComponent} if you need
202
+ * proposal-scoped sets.
203
+ *
204
+ * **Blocked while an in-canvas agent run is in flight** — throws
205
+ * `PROPOSAL_SWITCH_BLOCKED` if a canvas-agent run is active (matching the UI's
206
+ * hard block), because a switch would cancel the in-flight run.
207
+ *
208
+ * @param proposalId - The id of the proposal to activate.
209
+ * @returns The id of the now-active proposal.
210
+ * @throws `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is in flight; or
211
+ * if no proposal has the given id.
212
+ *
213
+ * @examplePrompt Switch to proposal prop_2
214
+ * @examplePrompt Make Scheme B the active design option
215
+ * @examplePrompt Show me the other proposal
216
+ *
217
+ * # Example
218
+ * ```ts
219
+ * await snaptrude.core.proposals.setActive("prop_2")
220
+ * ```
221
+ */
222
+ public abstract setActive(
223
+ proposalId: string,
224
+ ): PluginApiReturn<PluginCoreProposalsSetActiveResult>
225
+
226
+ /**
227
+ * Delete a proposal.
228
+ *
229
+ * **DANGEROUS AND IRREVERSIBLE — not undoable.** You must choose **exactly
230
+ * one** explicit disposition (mirroring the product's delete dialog):
231
+ *
232
+ * - `{ deleteObjects: true }` — delete the proposal AND **permanently
233
+ * destroy the geometry** in its exclusive buckets. Buckets shared with
234
+ * other proposals are never destroyed.
235
+ * - `{ targetProposalId }` — transfer **all** of this proposal's buckets to
236
+ * another proposal first (keeping the geometry alive), then delete the
237
+ * emptied proposal — "merge and remove". The target must be a
238
+ * **different** proposal.
239
+ *
240
+ * A bare `delete(proposalId)` is **rejected**: there is no safe default —
241
+ * without a disposition the engine would drop the proposal's bucket records
242
+ * while leaving their components orphaned. Passing both dispositions at once
243
+ * is also rejected as ambiguous.
244
+ *
245
+ * It is **server-validated**: the backend refuses the delete (throws) if a
246
+ * collaborator is active in that proposal, and the check times out after
247
+ * ~10s. The project's **last remaining proposal cannot be deleted**. If the
248
+ * proposal is active, the host switches away first.
249
+ *
250
+ * @param proposalId - The id of the proposal to delete.
251
+ * @param options - Exactly one disposition: `{ deleteObjects: true }`
252
+ * (destroy exclusive-bucket geometry) or `{ targetProposalId }` (transfer
253
+ * buckets to that proposal, then delete).
254
+ * @returns Confirmation that the proposal was deleted.
255
+ * @throws If no disposition (or both) is given, `targetProposalId` equals
256
+ * `proposalId`, no proposal has the given id, it is the only proposal, a
257
+ * collaborator lock refuses the delete, or the server validation times out.
258
+ *
259
+ * @examplePrompt Delete proposal prop_2 and everything in it
260
+ * @examplePrompt Remove this design option and destroy its geometry
261
+ * @examplePrompt Merge prop_2's layers into prop_1 and delete prop_2
262
+ *
263
+ * # Example
264
+ * ```ts
265
+ * // destroy the proposal and its exclusive geometry
266
+ * await snaptrude.core.proposals.delete("prop_2", { deleteObjects: true })
267
+ * // …or keep the geometry by transferring its buckets to prop_1 first
268
+ * await snaptrude.core.proposals.delete("prop_2", { targetProposalId: "prop_1" })
269
+ * ```
270
+ */
271
+ public abstract delete(
272
+ proposalId: string,
273
+ options:
274
+ | { deleteObjects: true; targetProposalId?: never }
275
+ | { deleteObjects?: false; targetProposalId: string },
276
+ ): PluginApiReturn<PluginCoreProposalsDeleteResult>
277
+ }
278
+
279
+ /**
280
+ * A design option (proposal) in the active project.
281
+ *
282
+ * | Property | Type | Description |
283
+ * |---|---|---|
284
+ * | `id` | `string` | Stable public proposal id (e.g. `"prop_1"`) |
285
+ * | `name` | `string` | Display name (≤ 24 chars) |
286
+ * | `isActive` | `boolean` | Whether this is the active proposal |
287
+ * | `defaultBucketId` | `string` | Id of the proposal's default bucket (cannot be unshared) |
288
+ * | `bucketIds` | `string[]` | Ids of every bucket (layer) the proposal owns |
289
+ * | `createdAt` | `string` | ISO creation timestamp |
290
+ * | `createdBy` | `string` | User id of the creator |
291
+ */
292
+ export const PluginCoreProposalRef = z.object({
293
+ id: z.string(),
294
+ name: z.string(),
295
+ isActive: z.boolean(),
296
+ defaultBucketId: z.string(),
297
+ bucketIds: z.array(z.string()),
298
+ createdAt: z.string(),
299
+ createdBy: z.string(),
300
+ })
301
+ export type PluginCoreProposalRef = z.infer<typeof PluginCoreProposalRef>
302
+
303
+ /**
304
+ * Result of {@linkcode PluginCoreProposalsApi.list}.
305
+ *
306
+ * | Property | Type | Description |
307
+ * |---|---|---|
308
+ * | `proposals` | {@linkcode PluginCoreProposalRef}`[]` | Every proposal in the active project |
309
+ */
310
+ export const PluginCoreProposalsListResult = z.object({
311
+ proposals: z.array(PluginCoreProposalRef),
312
+ })
313
+ export type PluginCoreProposalsListResult = z.infer<
314
+ typeof PluginCoreProposalsListResult
315
+ >
316
+
317
+ /**
318
+ * Arguments for {@linkcode PluginCoreProposalsApi.get} /
319
+ * {@linkcode PluginCoreProposalsApi.isActive}.
320
+ *
321
+ * | Property | Type | Description |
322
+ * |---|---|---|
323
+ * | `proposalId` | `string` | The id of the proposal |
324
+ */
325
+ export const PluginCoreProposalsGetArgs = z.object({
326
+ proposalId: z.string(),
327
+ })
328
+ export type PluginCoreProposalsGetArgs = z.infer<
329
+ typeof PluginCoreProposalsGetArgs
330
+ >
331
+
332
+ /**
333
+ * Result of {@linkcode PluginCoreProposalsApi.get} — the proposal, or `null`
334
+ * when no proposal has the given id.
335
+ */
336
+ export const PluginCoreProposalsGetResult = PluginCoreProposalRef.nullable()
337
+ export type PluginCoreProposalsGetResult = z.infer<
338
+ typeof PluginCoreProposalsGetResult
339
+ >
340
+
341
+ /**
342
+ * Result of {@linkcode PluginCoreProposalsApi.getActive} — the active proposal,
343
+ * or `null` when nothing is active (legacy / no-proposal project).
344
+ */
345
+ export const PluginCoreProposalsGetActiveResult =
346
+ PluginCoreProposalRef.nullable()
347
+ export type PluginCoreProposalsGetActiveResult = z.infer<
348
+ typeof PluginCoreProposalsGetActiveResult
349
+ >
350
+
351
+ /**
352
+ * Arguments for {@linkcode PluginCoreProposalsApi.listForComponent}.
353
+ *
354
+ * | Property | Type | Description |
355
+ * |---|---|---|
356
+ * | `component` | {@linkcode ComponentHandle} | The component to look up |
357
+ */
358
+ export const PluginCoreProposalsListForComponentArgs = z.object({
359
+ component: ComponentHandle,
360
+ })
361
+ export type PluginCoreProposalsListForComponentArgs = z.infer<
362
+ typeof PluginCoreProposalsListForComponentArgs
363
+ >
364
+
365
+ /**
366
+ * Result of {@linkcode PluginCoreProposalsApi.listForComponent} — the proposals
367
+ * the component belongs to (empty when out of scope or none). Same shape as
368
+ * {@linkcode PluginCoreProposalsListResult}.
369
+ */
370
+ export const PluginCoreProposalsListForComponentResult =
371
+ PluginCoreProposalsListResult
372
+ export type PluginCoreProposalsListForComponentResult = z.infer<
373
+ typeof PluginCoreProposalsListForComponentResult
374
+ >
375
+
376
+ /**
377
+ * Arguments for {@linkcode PluginCoreProposalsApi.isActive}. Alias of
378
+ * {@linkcode PluginCoreProposalsGetArgs}.
379
+ */
380
+ export const PluginCoreProposalsIsActiveArgs = PluginCoreProposalsGetArgs
381
+ export type PluginCoreProposalsIsActiveArgs = z.infer<
382
+ typeof PluginCoreProposalsIsActiveArgs
383
+ >
384
+
385
+ /**
386
+ * Arguments for {@linkcode PluginCoreProposalsApi.create}.
387
+ *
388
+ * | Property | Type | Description |
389
+ * |---|---|---|
390
+ * | `name` | `string` | Display name of the new proposal (capped at 24 chars) |
391
+ */
392
+ export const PluginCoreProposalsCreateArgs = z.object({
393
+ name: z.string(),
394
+ })
395
+ export type PluginCoreProposalsCreateArgs = z.infer<
396
+ typeof PluginCoreProposalsCreateArgs
397
+ >
398
+
399
+ /**
400
+ * Result of {@linkcode PluginCoreProposalsApi.create} — the id of the new
401
+ * proposal.
402
+ *
403
+ * | Property | Type | Description |
404
+ * |---|---|---|
405
+ * | `id` | `string` | The id of the newly created proposal |
406
+ */
407
+ export const PluginCoreProposalsCreateResult = z.object({
408
+ id: z.string(),
409
+ })
410
+ export type PluginCoreProposalsCreateResult = z.infer<
411
+ typeof PluginCoreProposalsCreateResult
412
+ >
413
+
414
+ /**
415
+ * Arguments for {@linkcode PluginCoreProposalsApi.rename}.
416
+ *
417
+ * | Property | Type | Description |
418
+ * |---|---|---|
419
+ * | `proposalId` | `string` | The id of the proposal to rename |
420
+ * | `name` | `string` | The new display name (capped at 24 chars) |
421
+ */
422
+ export const PluginCoreProposalsRenameArgs = z.object({
423
+ proposalId: z.string(),
424
+ name: z.string(),
425
+ })
426
+ export type PluginCoreProposalsRenameArgs = z.infer<
427
+ typeof PluginCoreProposalsRenameArgs
428
+ >
429
+
430
+ /** Result of {@linkcode PluginCoreProposalsApi.rename} — the updated record. */
431
+ export const PluginCoreProposalsRenameResult = PluginCoreProposalRef
432
+ export type PluginCoreProposalsRenameResult = z.infer<
433
+ typeof PluginCoreProposalsRenameResult
434
+ >
435
+
436
+ /**
437
+ * Arguments for {@linkcode PluginCoreProposalsApi.setActive}.
438
+ *
439
+ * | Property | Type | Description |
440
+ * |---|---|---|
441
+ * | `proposalId` | `string` | The id of the proposal to activate |
442
+ */
443
+ export const PluginCoreProposalsSetActiveArgs = z.object({
444
+ proposalId: z.string(),
445
+ })
446
+ export type PluginCoreProposalsSetActiveArgs = z.infer<
447
+ typeof PluginCoreProposalsSetActiveArgs
448
+ >
449
+
450
+ /**
451
+ * Result of {@linkcode PluginCoreProposalsApi.setActive} — the now-active
452
+ * proposal's id.
453
+ *
454
+ * | Property | Type | Description |
455
+ * |---|---|---|
456
+ * | `id` | `string` | The id of the now-active proposal |
457
+ */
458
+ export const PluginCoreProposalsSetActiveResult = z.object({
459
+ id: z.string(),
460
+ })
461
+ export type PluginCoreProposalsSetActiveResult = z.infer<
462
+ typeof PluginCoreProposalsSetActiveResult
463
+ >
464
+
465
+ /**
466
+ * Arguments for {@linkcode PluginCoreProposalsApi.delete}. Exactly one
467
+ * disposition is required: `deleteObjects: true` XOR `targetProposalId` (a
468
+ * **different** proposal). A bare delete with neither is rejected.
469
+ *
470
+ * | Property | Type | Description |
471
+ * |---|---|---|
472
+ * | `proposalId` | `string` | The id of the proposal to delete |
473
+ * | `deleteObjects` | `true \| undefined` | Destroy the geometry in exclusive buckets (mutually exclusive with `targetProposalId`) |
474
+ * | `targetProposalId` | `string \| undefined` | Transfer this proposal's buckets here before deleting (must differ from `proposalId`) |
475
+ */
476
+ export const PluginCoreProposalsDeleteArgs = z
477
+ .object({
478
+ proposalId: z.string(),
479
+ deleteObjects: z.boolean().optional(),
480
+ targetProposalId: z.string().optional(),
481
+ })
482
+ .refine((v) => (v.deleteObjects === true) !== (v.targetProposalId != null), {
483
+ message:
484
+ "Provide exactly one disposition: deleteObjects: true (destroy exclusive-bucket geometry) or targetProposalId (transfer buckets, then delete)",
485
+ })
486
+ .refine(
487
+ (v) => v.targetProposalId == null || v.targetProposalId !== v.proposalId,
488
+ {
489
+ message: "targetProposalId must be a different proposal than proposalId",
490
+ },
491
+ )
492
+ export type PluginCoreProposalsDeleteArgs = z.infer<
493
+ typeof PluginCoreProposalsDeleteArgs
494
+ >
495
+
496
+ /**
497
+ * Result of {@linkcode PluginCoreProposalsApi.delete} — confirmation that the
498
+ * proposal was deleted.
499
+ *
500
+ * | Property | Type | Description |
501
+ * |---|---|---|
502
+ * | `deleted` | `true` | Always `true` on success |
503
+ */
504
+ export const PluginCoreProposalsDeleteResult = z.object({
505
+ deleted: z.literal(true),
506
+ })
507
+ export type PluginCoreProposalsDeleteResult = z.infer<
508
+ typeof PluginCoreProposalsDeleteResult
509
+ >