@snaptrude/plugin-core 0.9.16 → 0.9.18-edgesetbacks-20261021.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 (221) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -402
  3. package/CLAUDE.md +11 -11
  4. package/api-manifest.json +1510 -701
  5. package/dist/api/analysis/daylight.d.ts +5 -5
  6. package/dist/api/analysis/solar.d.ts +2 -2
  7. package/dist/api/core/camera/index.d.ts +29 -0
  8. package/dist/api/core/camera/index.d.ts.map +1 -1
  9. package/dist/api/core/geom/create/index.d.ts +7 -1
  10. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/update/contour.d.ts +8 -2
  12. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  13. package/dist/api/core/storeys/index.d.ts +5 -2
  14. package/dist/api/core/storeys/index.d.ts.map +1 -1
  15. package/dist/api/core/zoom/index.d.ts +13 -0
  16. package/dist/api/core/zoom/index.d.ts.map +1 -1
  17. package/dist/api/design/bim.d.ts +299 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/boolean/index.d.ts +2 -0
  20. package/dist/api/design/boolean/index.d.ts.map +1 -1
  21. package/dist/api/design/catalog.d.ts +320 -0
  22. package/dist/api/design/catalog.d.ts.map +1 -0
  23. package/dist/api/design/create/bulk-items.d.ts +65 -25
  24. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  25. package/dist/api/design/create/index.d.ts +299 -100
  26. package/dist/api/design/create/index.d.ts.map +1 -1
  27. package/dist/api/design/create/opening-fields.d.ts +13 -12
  28. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  29. package/dist/api/design/delete/index.d.ts +1 -0
  30. package/dist/api/design/delete/index.d.ts.map +1 -1
  31. package/dist/api/design/doors/index.d.ts +18 -24
  32. package/dist/api/design/doors/index.d.ts.map +1 -1
  33. package/dist/api/design/family.d.ts +813 -35
  34. package/dist/api/design/family.d.ts.map +1 -1
  35. package/dist/api/design/index.d.ts +16 -0
  36. package/dist/api/design/index.d.ts.map +1 -1
  37. package/dist/api/design/networks.d.ts +1703 -0
  38. package/dist/api/design/networks.d.ts.map +1 -0
  39. package/dist/api/design/query/grids.d.ts +2 -2
  40. package/dist/api/design/query/index.d.ts +42 -32
  41. package/dist/api/design/query/index.d.ts.map +1 -1
  42. package/dist/api/design/selection/index.d.ts +10 -10
  43. package/dist/api/design/types/index.d.ts +40 -24
  44. package/dist/api/design/types/index.d.ts.map +1 -1
  45. package/dist/api/design/update/index.d.ts +111 -6
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/design/windows/index.d.ts +8 -8
  48. package/dist/api/design/windows/index.d.ts.map +1 -1
  49. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  50. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  51. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  52. package/dist/api/presentation/annotate.d.ts +38 -22
  53. package/dist/api/presentation/annotate.d.ts.map +1 -1
  54. package/dist/api/presentation/drawingContract.d.ts +32 -0
  55. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  56. package/dist/api/presentation/drawings.d.ts +3523 -0
  57. package/dist/api/presentation/drawings.d.ts.map +1 -0
  58. package/dist/api/presentation/export.d.ts +23 -5
  59. package/dist/api/presentation/export.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +41 -12
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +106 -6
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  64. package/dist/api/presentation/sections.d.ts +461 -0
  65. package/dist/api/presentation/sections.d.ts.map +1 -0
  66. package/dist/api/presentation/shapes.d.ts +2 -2
  67. package/dist/api/presentation/sheets.d.ts +175 -19
  68. package/dist/api/presentation/sheets.d.ts.map +1 -1
  69. package/dist/api/presentation/viewPresets.d.ts +222 -0
  70. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  71. package/dist/api/presentation/views.d.ts +141 -8
  72. package/dist/api/presentation/views.d.ts.map +1 -1
  73. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  74. package/dist/errors/codes.d.ts +3 -1
  75. package/dist/errors/codes.d.ts.map +1 -1
  76. package/dist/index.cjs +3883 -2385
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.js +3744 -2385
  79. package/dist/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/scripts/check-dist-fresh.mjs +22 -22
  82. package/scripts/generate-manifest.mjs +243 -238
  83. package/scripts/generate-manifest.test.mjs +303 -303
  84. package/src/api/analysis/daylight.ts +470 -470
  85. package/src/api/analysis/heatmaps.ts +683 -683
  86. package/src/api/analysis/illuminance.ts +155 -155
  87. package/src/api/analysis/index.ts +61 -61
  88. package/src/api/analysis/shadows.ts +183 -183
  89. package/src/api/analysis/solar.ts +237 -237
  90. package/src/api/analysis/sunlightHours.ts +211 -211
  91. package/src/api/analysis/sunpath.ts +83 -83
  92. package/src/api/analysis/weather.ts +179 -179
  93. package/src/api/core/buildings.ts +485 -485
  94. package/src/api/core/camera/index.ts +696 -667
  95. package/src/api/core/comment/index.ts +376 -376
  96. package/src/api/core/geom/create/index.ts +2139 -2133
  97. package/src/api/core/geom/delete/index.ts +84 -84
  98. package/src/api/core/geom/index.ts +35 -35
  99. package/src/api/core/geom/query/arc.ts +169 -169
  100. package/src/api/core/geom/query/brep.ts +621 -621
  101. package/src/api/core/geom/query/circle.ts +404 -404
  102. package/src/api/core/geom/query/contour.ts +540 -540
  103. package/src/api/core/geom/query/curve.ts +1102 -1102
  104. package/src/api/core/geom/query/edge.ts +130 -130
  105. package/src/api/core/geom/query/face.ts +508 -508
  106. package/src/api/core/geom/query/halfedge.ts +210 -210
  107. package/src/api/core/geom/query/index.ts +63 -63
  108. package/src/api/core/geom/query/profile.ts +473 -473
  109. package/src/api/core/geom/query/vertex.ts +205 -205
  110. package/src/api/core/geom/update/contour.ts +323 -317
  111. package/src/api/core/geom/update/curve.ts +209 -209
  112. package/src/api/core/geom/update/index.ts +29 -29
  113. package/src/api/core/geom/update/profile.ts +379 -379
  114. package/src/api/core/groups.ts +521 -521
  115. package/src/api/core/handles/index.ts +234 -234
  116. package/src/api/core/history/index.ts +84 -84
  117. package/src/api/core/index.ts +99 -99
  118. package/src/api/core/io/export/index.ts +126 -126
  119. package/src/api/core/io/import/index.ts +504 -504
  120. package/src/api/core/io/index.ts +42 -42
  121. package/src/api/core/io/job/index.ts +140 -140
  122. package/src/api/core/io/query/index.ts +71 -71
  123. package/src/api/core/io/terrain/index.ts +360 -360
  124. package/src/api/core/io/underlay/index.ts +705 -705
  125. package/src/api/core/layers.ts +540 -540
  126. package/src/api/core/math/index.ts +20 -20
  127. package/src/api/core/math/quat.ts +172 -172
  128. package/src/api/core/math/vec3.ts +179 -179
  129. package/src/api/core/mode/index.ts +108 -108
  130. package/src/api/core/project/index.ts +479 -479
  131. package/src/api/core/proposals/index.ts +570 -570
  132. package/src/api/core/storeys/index.ts +311 -308
  133. package/src/api/core/storeys/layers.ts +339 -339
  134. package/src/api/core/tags.ts +911 -911
  135. package/src/api/core/units/index.ts +164 -164
  136. package/src/api/core/user.ts +46 -46
  137. package/src/api/core/zoom/index.ts +64 -51
  138. package/src/api/design/bim.ts +210 -0
  139. package/src/api/design/boolean/index.ts +181 -179
  140. package/src/api/design/catalog.ts +203 -0
  141. package/src/api/design/create/bulk-items.ts +294 -254
  142. package/src/api/design/create/grids.ts +137 -137
  143. package/src/api/design/create/index.ts +2178 -1980
  144. package/src/api/design/create/opening-fields.ts +40 -37
  145. package/src/api/design/delete/index.ts +75 -74
  146. package/src/api/design/dimensions.ts +453 -453
  147. package/src/api/design/doors/index.ts +516 -500
  148. package/src/api/design/edit/index.ts +54 -54
  149. package/src/api/design/erase/index.ts +151 -151
  150. package/src/api/design/family.ts +1342 -704
  151. package/src/api/design/furniture/index.ts +319 -319
  152. package/src/api/design/grids.ts +237 -237
  153. package/src/api/design/index.ts +304 -274
  154. package/src/api/design/lock.ts +69 -69
  155. package/src/api/design/materials/index.ts +608 -608
  156. package/src/api/design/networks.ts +607 -0
  157. package/src/api/design/query/geometry/index.ts +319 -319
  158. package/src/api/design/query/grids.ts +197 -197
  159. package/src/api/design/query/index.ts +1236 -1226
  160. package/src/api/design/query/referenceLines.ts +52 -52
  161. package/src/api/design/query/spaces.ts +275 -275
  162. package/src/api/design/selection/index.ts +273 -273
  163. package/src/api/design/shared.ts +40 -40
  164. package/src/api/design/transform/index.ts +254 -254
  165. package/src/api/design/types/index.ts +163 -156
  166. package/src/api/design/update/index.ts +1096 -983
  167. package/src/api/design/visibility.ts +143 -143
  168. package/src/api/design/windows/index.ts +351 -333
  169. package/src/api/entity/buildableEnvelope.ts +397 -322
  170. package/src/api/entity/index.ts +30 -30
  171. package/src/api/entity/referenceLine.ts +235 -235
  172. package/src/api/entity/space.ts +612 -612
  173. package/src/api/entity/story.ts +517 -517
  174. package/src/api/index.ts +47 -47
  175. package/src/api/presentation/aiInspiration.ts +1116 -1116
  176. package/src/api/presentation/annotate.ts +417 -385
  177. package/src/api/presentation/camera.ts +289 -289
  178. package/src/api/presentation/diagrams.ts +194 -194
  179. package/src/api/presentation/drawingContract.ts +70 -0
  180. package/src/api/presentation/drawings.ts +1179 -0
  181. package/src/api/presentation/export.ts +134 -108
  182. package/src/api/presentation/import.ts +159 -159
  183. package/src/api/presentation/index.ts +160 -126
  184. package/src/api/presentation/placedViews.ts +1211 -1120
  185. package/src/api/presentation/sections.ts +420 -0
  186. package/src/api/presentation/shapes.ts +274 -274
  187. package/src/api/presentation/sheets.ts +746 -585
  188. package/src/api/presentation/slideshow.ts +134 -134
  189. package/src/api/presentation/tables.ts +84 -84
  190. package/src/api/presentation/viewPresets.ts +241 -0
  191. package/src/api/presentation/views.ts +742 -673
  192. package/src/api/presentation/walkthroughs.ts +1201 -1201
  193. package/src/api/program/adjacency.ts +164 -164
  194. package/src/api/program/areas.ts +408 -408
  195. package/src/api/program/classification.ts +138 -138
  196. package/src/api/program/cores.ts +83 -83
  197. package/src/api/program/departments.ts +517 -517
  198. package/src/api/program/index.ts +73 -73
  199. package/src/api/program/labels.ts +332 -332
  200. package/src/api/program/layout.ts +422 -422
  201. package/src/api/program/metadata.ts +364 -364
  202. package/src/api/program/metrics.ts +120 -120
  203. package/src/api/program/site.ts +748 -748
  204. package/src/api/program/spreadsheet.ts +2698 -2698
  205. package/src/api/workspace/index.ts +612 -612
  206. package/src/errors/codes.ts +143 -136
  207. package/src/errors/envelope.ts +75 -75
  208. package/src/errors/index.ts +21 -21
  209. package/src/errors/plugin-error.ts +134 -134
  210. package/src/handles.ts +297 -297
  211. package/src/host-utils.ts +67 -67
  212. package/src/index.ts +7 -7
  213. package/src/massParameters.ops.ts +246 -246
  214. package/src/massParameters.ts +427 -427
  215. package/src/types.ts +16 -16
  216. package/test/drawingContract.test.mjs +288 -0
  217. package/test/errors.test.mjs +185 -184
  218. package/test/massParameters.test.mjs +952 -952
  219. package/test/presentationViewSettings.test.mjs +85 -0
  220. package/tsconfig.json +22 -22
  221. package/tsup.config.ts +12 -12
@@ -1,570 +1,570 @@
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 is a layer in the
10
- * Layers panel; read them with `core.layers.get` / {@linkcode PluginCoreLayersApi}),
11
- * and switching the active proposal shows/hides the buckets it owns. Proposals
12
- * are **infrastructure**, not design — they own the buckets/layers geometry
13
- * lives in — so they sit under `core`, not `design` (constitution §2A).
14
- *
15
- * Reads return plain records and never throw for a missing proposal (`get` and
16
- * `getActive` return `null`, `list` / `listForComponent` return an empty list,
17
- * `isActive` returns `false`). Ids are stable public strings.
18
- *
19
- * On legacy / no-proposal projects the scoping predicates degrade gracefully:
20
- * `getActive` returns `null` and the reads return empty.
21
- *
22
- * Proposal writes are **not undoable** — create / rename / setActive / delete
23
- * persist directly and bypass the undo stack; a `core.history.undo` will not
24
- * reverse them. Each proposal keeps its own isolated undo stack for the
25
- * geometry edits made inside it.
26
- *
27
- * Accessed via `snaptrude.core.proposals`.
28
- */
29
- export abstract class PluginCoreProposalsApi {
30
- constructor() {}
31
-
32
- /**
33
- * List the design options (proposals) in the active project.
34
- *
35
- * @returns A {@linkcode PluginCoreProposalsListResult} with a `proposals`
36
- * array (empty on a legacy / no-proposal project).
37
- *
38
- * @examplePrompt List all the design options in this project
39
- * @examplePrompt How many proposals does this project have?
40
- * @examplePrompt Show every design variant with its name
41
- *
42
- * # Example
43
- * ```ts
44
- * const { proposals } = await snaptrude.core.proposals.list()
45
- * for (const p of proposals) console.log(p.id, p.name, p.isActive)
46
- * ```
47
- */
48
- public abstract list(): PluginApiReturn<PluginCoreProposalsListResult>
49
-
50
- /**
51
- * Get a single proposal by id.
52
- *
53
- * @param proposalId - The id of the proposal to read.
54
- * @returns The matching {@linkcode PluginCoreProposalRef}, or `null` if no
55
- * proposal has that id.
56
- *
57
- * @examplePrompt Get the proposal with id prop_1
58
- * @examplePrompt Look up a single design option by its id
59
- * @examplePrompt Which buckets belong to proposal prop_2?
60
- *
61
- * # Example
62
- * ```ts
63
- * const proposal = await snaptrude.core.proposals.get("prop_1")
64
- * if (proposal) console.log(proposal.name, proposal.bucketIds)
65
- * ```
66
- */
67
- public abstract get(
68
- proposalId: string,
69
- ): PluginApiReturn<PluginCoreProposalsGetResult>
70
-
71
- /**
72
- * Get the active proposal.
73
- *
74
- * The active proposal is the design option currently shown and edited.
75
- * Honors a present-mode capture override: while a present-mode capture is in
76
- * flight this returns the captured proposal, not the redux-active one. Paired
77
- * with {@linkcode PluginCoreProposalsApi.setActive}.
78
- *
79
- * @returns The active {@linkcode PluginCoreProposalRef}, or `null` on a legacy
80
- * / no-proposal project where nothing is active.
81
- *
82
- * @examplePrompt Which design option is active?
83
- * @examplePrompt Get the current proposal
84
- * @examplePrompt What proposal am I working in?
85
- *
86
- * # Example
87
- * ```ts
88
- * const active = await snaptrude.core.proposals.getActive()
89
- * if (active) console.log(active.id, active.name)
90
- * ```
91
- */
92
- public abstract getActive(): PluginApiReturn<PluginCoreProposalsGetActiveResult>
93
-
94
- /**
95
- * List the proposals a component belongs to.
96
- *
97
- * Returns **every** proposal whose buckets include the component — a bucket
98
- * can be shared across proposals, so a component may appear in more than one.
99
- *
100
- * @param component - The component to look up.
101
- * @returns A {@linkcode PluginCoreProposalsListForComponentResult} with a
102
- * `proposals` array (empty when the component is out of scope or the project
103
- * has no proposals).
104
- *
105
- * @examplePrompt Which design options is this component part of?
106
- * @examplePrompt List the proposals that contain this wall
107
- * @examplePrompt Is this element shared across multiple design variants?
108
- *
109
- * # Example
110
- * ```ts
111
- * const { proposals } = await snaptrude.core.proposals.listForComponent(wall)
112
- * console.log(`shared across ${proposals.length} proposals`)
113
- * ```
114
- */
115
- public abstract listForComponent(
116
- component: ComponentHandle,
117
- ): PluginApiReturn<PluginCoreProposalsListForComponentResult>
118
-
119
- /**
120
- * Test whether a proposal is the active one.
121
- *
122
- * @param proposalId - The id of the proposal to test.
123
- * @returns `true` if it is the active proposal, otherwise `false` (including
124
- * when no proposal has that id).
125
- *
126
- * @examplePrompt Is proposal prop_2 the active one?
127
- * @examplePrompt Check whether this design option is currently shown
128
- * @examplePrompt Am I working in proposal prop_1?
129
- *
130
- * # Example
131
- * ```ts
132
- * if (await snaptrude.core.proposals.isActive("prop_1")) {
133
- * console.log("prop_1 is active")
134
- * }
135
- * ```
136
- */
137
- public abstract isActive(proposalId: string): PluginApiReturn<boolean>
138
-
139
- /**
140
- * Create a new design option (proposal) — blank, or configured from a base
141
- * proposal (the New Proposal modal's two paths).
142
- *
143
- * **Pro-gated** (creating proposals requires a Pro plan, matching the UI) and
144
- * **not undoable**. The name is capped at 24 characters.
145
- *
146
- * **Blank** (no `options`) — creates a bare proposal with a single empty
147
- * default bucket; it does **not** carry forward the current proposal's
148
- * layers, views, or targets, and the active proposal does **not** change.
149
- *
150
- * **Configured** (`options.baseProposalId`) — the modal's Configure path:
151
- * carries the base proposal's buckets (layers) forward per `options.layers`,
152
- * where each entry names a bucket of the base proposal and an action —
153
- * `"share"` (the new proposal references the same live bucket) or
154
- * `"duplicate"` (an independent copy of the bucket and its contents).
155
- * Buckets not listed are left behind. Omitting `layers` carries **every**
156
- * base bucket forward as a `"duplicate"` (the modal's default). The base
157
- * proposal's views and area targets are copied either way, and the new
158
- * proposal becomes the **active** one (implicit switch — blocked with
159
- * `PROPOSAL_SWITCH_BLOCKED` while an in-canvas agent run is in flight, like
160
- * {@linkcode PluginCoreProposalsApi.setActive}).
161
- *
162
- * @param name - Display name of the new proposal (capped at 24 chars).
163
- * @param options - Optional configure path: `baseProposalId` (the proposal to
164
- * carry forward from) and `layers` (per-bucket `"share"` / `"duplicate"`
165
- * actions; omitted buckets are excluded, omitted `layers` duplicates all).
166
- * @returns The new proposal's id (read the full record back with
167
- * {@linkcode PluginCoreProposalsApi.get}).
168
- * @throws If the caller is not on a Pro plan, the name is empty/duplicate,
169
- * `layers` is passed without `baseProposalId`, the base proposal or a
170
- * listed bucket does not exist / does not belong to the base proposal, or
171
- * (configured only) `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is
172
- * in flight.
173
- *
174
- * @examplePrompt Create a design option called Scheme B
175
- * @examplePrompt Add a new proposal named Option 2
176
- * @examplePrompt Duplicate the current proposal as Scheme C
177
- * @examplePrompt Create a design option based on Proposal 1 that shares its site layer
178
- *
179
- * # Example
180
- * ```ts
181
- * // blank design option
182
- * const { id } = await snaptrude.core.proposals.create("Scheme B")
183
- *
184
- * // configured from the active proposal: duplicate every layer
185
- * const base = await snaptrude.core.proposals.getActive()
186
- * if (base) {
187
- * const { id: dup } = await snaptrude.core.proposals.create("Scheme C", {
188
- * baseProposalId: base.id,
189
- * })
190
- * // …or share one layer and leave the rest behind
191
- * const { id: shared } = await snaptrude.core.proposals.create("Scheme D", {
192
- * baseProposalId: base.id,
193
- * layers: [{ bucketId: base.defaultBucketId, action: "share" }],
194
- * })
195
- * }
196
- * ```
197
- */
198
- public abstract create(
199
- name: string,
200
- options?: {
201
- baseProposalId: string
202
- layers?: { bucketId: string; action: "share" | "duplicate" }[]
203
- },
204
- ): PluginApiReturn<PluginCoreProposalsCreateResult>
205
-
206
- /**
207
- * Rename a proposal.
208
- *
209
- * **Not undoable.** The name is capped at 24 characters.
210
- *
211
- * @param proposalId - The id of the proposal to rename.
212
- * @param name - The new display name (capped at 24 chars).
213
- * @returns The updated {@linkcode PluginCoreProposalRef}.
214
- * @throws If no proposal has the given id, or the name is empty / duplicates
215
- * another proposal.
216
- *
217
- * @examplePrompt Rename proposal prop_1 to Final Scheme
218
- * @examplePrompt Change the name of this design option
219
- * @examplePrompt Call proposal prop_2 "Option A"
220
- *
221
- * # Example
222
- * ```ts
223
- * const proposal = await snaptrude.core.proposals.rename("prop_1", "Final Scheme")
224
- * ```
225
- */
226
- public abstract rename(
227
- proposalId: string,
228
- name: string,
229
- ): PluginApiReturn<PluginCoreProposalsRenameResult>
230
-
231
- /**
232
- * Switch the active proposal.
233
- *
234
- * A **heavy scene switch** and **not undoable**. Switching clears the current
235
- * selection, resets AI / streaming state, cancels proposal-scoped operations,
236
- * retoggles bucket visibility to the new proposal's owned set, restores its
237
- * per-proposal site/FSI settings, and realigns the views panel. After the
238
- * switch, **writes** are guarded to the new active proposal (edits targeting
239
- * components outside it are rejected), but **list reads are NOT
240
- * proposal-filtered** — `design.query.list*` and similar reads stay
241
- * project-wide; filter their results via
242
- * {@linkcode PluginCoreProposalsApi.listForComponent} if you need
243
- * proposal-scoped sets.
244
- *
245
- * **Blocked while an in-canvas agent run is in flight** — throws
246
- * `PROPOSAL_SWITCH_BLOCKED` if a canvas-agent run is active (matching the UI's
247
- * hard block), because a switch would cancel the in-flight run.
248
- *
249
- * @param proposalId - The id of the proposal to activate.
250
- * @returns The id of the now-active proposal.
251
- * @throws `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is in flight; or
252
- * if no proposal has the given id.
253
- *
254
- * @examplePrompt Switch to proposal prop_2
255
- * @examplePrompt Make Scheme B the active design option
256
- * @examplePrompt Show me the other proposal
257
- *
258
- * # Example
259
- * ```ts
260
- * await snaptrude.core.proposals.setActive("prop_2")
261
- * ```
262
- */
263
- public abstract setActive(
264
- proposalId: string,
265
- ): PluginApiReturn<PluginCoreProposalsSetActiveResult>
266
-
267
- /**
268
- * Delete a proposal.
269
- *
270
- * **DANGEROUS AND IRREVERSIBLE — not undoable.** You must choose **exactly
271
- * one** explicit disposition (mirroring the product's delete dialog):
272
- *
273
- * - `{ deleteObjects: true }` — delete the proposal AND **permanently
274
- * destroy the geometry** in its exclusive buckets. Buckets shared with
275
- * other proposals are never destroyed.
276
- * - `{ targetProposalId }` — transfer **all** of this proposal's buckets to
277
- * another proposal first (keeping the geometry alive), then delete the
278
- * emptied proposal — "merge and remove". The target must be a
279
- * **different** proposal.
280
- *
281
- * A bare `delete(proposalId)` is **rejected**: there is no safe default —
282
- * without a disposition the engine would drop the proposal's bucket records
283
- * while leaving their components orphaned. Passing both dispositions at once
284
- * is also rejected as ambiguous.
285
- *
286
- * It is **server-validated**: the backend refuses the delete (throws) if a
287
- * collaborator is active in that proposal, and the check times out after
288
- * ~10s. The project's **last remaining proposal cannot be deleted**. If the
289
- * proposal is active, the host switches away first.
290
- *
291
- * @param proposalId - The id of the proposal to delete.
292
- * @param options - Exactly one disposition: `{ deleteObjects: true }`
293
- * (destroy exclusive-bucket geometry) or `{ targetProposalId }` (transfer
294
- * buckets to that proposal, then delete).
295
- * @returns Confirmation that the proposal was deleted.
296
- * @throws If no disposition (or both) is given, `targetProposalId` equals
297
- * `proposalId`, no proposal has the given id, it is the only proposal, a
298
- * collaborator lock refuses the delete, or the server validation times out.
299
- *
300
- * @examplePrompt Delete proposal prop_2 and everything in it
301
- * @examplePrompt Remove this design option and destroy its geometry
302
- * @examplePrompt Merge prop_2's layers into prop_1 and delete prop_2
303
- *
304
- * # Example
305
- * ```ts
306
- * // destroy the proposal and its exclusive geometry
307
- * await snaptrude.core.proposals.delete("prop_2", { deleteObjects: true })
308
- * // …or keep the geometry by transferring its buckets to prop_1 first
309
- * await snaptrude.core.proposals.delete("prop_2", { targetProposalId: "prop_1" })
310
- * ```
311
- */
312
- public abstract delete(
313
- proposalId: string,
314
- options:
315
- | { deleteObjects: true; targetProposalId?: never }
316
- | { deleteObjects?: false; targetProposalId: string },
317
- ): PluginApiReturn<PluginCoreProposalsDeleteResult>
318
- }
319
-
320
- /**
321
- * A design option (proposal) in the active project.
322
- *
323
- * | Property | Type | Description |
324
- * |---|---|---|
325
- * | `id` | `string` | Stable public proposal id (e.g. `"prop_1"`) |
326
- * | `name` | `string` | Display name (≤ 24 chars) |
327
- * | `isActive` | `boolean` | Whether this is the active proposal |
328
- * | `defaultBucketId` | `string` | Id of the proposal's default layer (cannot be unshared); read it with `core.layers.get` ({@linkcode PluginCoreLayersApi}) |
329
- * | `bucketIds` | `string[]` | Ids of every layer in the Layers panel the proposal owns; read them with `core.layers.get` ({@linkcode PluginCoreLayersApi}) |
330
- * | `createdAt` | `string` | ISO creation timestamp |
331
- * | `createdBy` | `string` | User id of the creator |
332
- */
333
- export const PluginCoreProposalRef = z.object({
334
- id: z.string(),
335
- name: z.string(),
336
- isActive: z.boolean(),
337
- defaultBucketId: z.string(),
338
- bucketIds: z.array(z.string()),
339
- createdAt: z.string(),
340
- createdBy: z.string(),
341
- })
342
- export type PluginCoreProposalRef = z.infer<typeof PluginCoreProposalRef>
343
-
344
- /**
345
- * Result of {@linkcode PluginCoreProposalsApi.list}.
346
- *
347
- * | Property | Type | Description |
348
- * |---|---|---|
349
- * | `proposals` | {@linkcode PluginCoreProposalRef}`[]` | Every proposal in the active project |
350
- */
351
- export const PluginCoreProposalsListResult = z.object({
352
- proposals: z.array(PluginCoreProposalRef),
353
- })
354
- export type PluginCoreProposalsListResult = z.infer<
355
- typeof PluginCoreProposalsListResult
356
- >
357
-
358
- /**
359
- * Arguments for {@linkcode PluginCoreProposalsApi.get} /
360
- * {@linkcode PluginCoreProposalsApi.isActive}.
361
- *
362
- * | Property | Type | Description |
363
- * |---|---|---|
364
- * | `proposalId` | `string` | The id of the proposal |
365
- */
366
- export const PluginCoreProposalsGetArgs = z.object({
367
- proposalId: z.string(),
368
- })
369
- export type PluginCoreProposalsGetArgs = z.infer<
370
- typeof PluginCoreProposalsGetArgs
371
- >
372
-
373
- /**
374
- * Result of {@linkcode PluginCoreProposalsApi.get} — the proposal, or `null`
375
- * when no proposal has the given id.
376
- */
377
- export const PluginCoreProposalsGetResult = PluginCoreProposalRef.nullable()
378
- export type PluginCoreProposalsGetResult = z.infer<
379
- typeof PluginCoreProposalsGetResult
380
- >
381
-
382
- /**
383
- * Result of {@linkcode PluginCoreProposalsApi.getActive} — the active proposal,
384
- * or `null` when nothing is active (legacy / no-proposal project).
385
- */
386
- export const PluginCoreProposalsGetActiveResult =
387
- PluginCoreProposalRef.nullable()
388
- export type PluginCoreProposalsGetActiveResult = z.infer<
389
- typeof PluginCoreProposalsGetActiveResult
390
- >
391
-
392
- /**
393
- * Arguments for {@linkcode PluginCoreProposalsApi.listForComponent}.
394
- *
395
- * | Property | Type | Description |
396
- * |---|---|---|
397
- * | `component` | {@linkcode ComponentHandle} | The component to look up |
398
- */
399
- export const PluginCoreProposalsListForComponentArgs = z.object({
400
- component: ComponentHandle,
401
- })
402
- export type PluginCoreProposalsListForComponentArgs = z.infer<
403
- typeof PluginCoreProposalsListForComponentArgs
404
- >
405
-
406
- /**
407
- * Result of {@linkcode PluginCoreProposalsApi.listForComponent} — the proposals
408
- * the component belongs to (empty when out of scope or none). Same shape as
409
- * {@linkcode PluginCoreProposalsListResult}.
410
- */
411
- export const PluginCoreProposalsListForComponentResult =
412
- PluginCoreProposalsListResult
413
- export type PluginCoreProposalsListForComponentResult = z.infer<
414
- typeof PluginCoreProposalsListForComponentResult
415
- >
416
-
417
- /**
418
- * Arguments for {@linkcode PluginCoreProposalsApi.isActive}. Alias of
419
- * {@linkcode PluginCoreProposalsGetArgs}.
420
- */
421
- export const PluginCoreProposalsIsActiveArgs = PluginCoreProposalsGetArgs
422
- export type PluginCoreProposalsIsActiveArgs = z.infer<
423
- typeof PluginCoreProposalsIsActiveArgs
424
- >
425
-
426
- /**
427
- * Arguments for {@linkcode PluginCoreProposalsApi.create}. A bare `name` makes
428
- * a blank proposal; `baseProposalId` switches to the configured (carry-forward)
429
- * path. `layers` requires `baseProposalId` — each entry names a bucket of the
430
- * base proposal; omitted buckets are excluded, and omitting `layers` entirely
431
- * duplicates every base bucket.
432
- *
433
- * | Property | Type | Description |
434
- * |---|---|---|
435
- * | `name` | `string` | Display name of the new proposal (capped at 24 chars) |
436
- * | `baseProposalId` | `string \| undefined` | Proposal to carry forward from (configured path) |
437
- * | `layers` | `{ bucketId: string; action: "share" \| "duplicate" }[] \| undefined` | Per-bucket carry-forward actions (requires `baseProposalId`) |
438
- */
439
- export const PluginCoreProposalsCreateArgs = z
440
- .object({
441
- name: z.string(),
442
- baseProposalId: z.string().optional(),
443
- layers: z
444
- .array(
445
- z.object({
446
- bucketId: z.string(),
447
- action: z.enum(["share", "duplicate"]),
448
- }),
449
- )
450
- .optional(),
451
- })
452
- .refine((v) => v.layers === undefined || v.baseProposalId !== undefined, {
453
- message:
454
- "layers requires baseProposalId — the layers describe how to carry the base proposal's buckets forward",
455
- })
456
- export type PluginCoreProposalsCreateArgs = z.infer<
457
- typeof PluginCoreProposalsCreateArgs
458
- >
459
-
460
- /**
461
- * Result of {@linkcode PluginCoreProposalsApi.create} — the id of the new
462
- * proposal.
463
- *
464
- * | Property | Type | Description |
465
- * |---|---|---|
466
- * | `id` | `string` | The id of the newly created proposal |
467
- */
468
- export const PluginCoreProposalsCreateResult = z.object({
469
- id: z.string(),
470
- })
471
- export type PluginCoreProposalsCreateResult = z.infer<
472
- typeof PluginCoreProposalsCreateResult
473
- >
474
-
475
- /**
476
- * Arguments for {@linkcode PluginCoreProposalsApi.rename}.
477
- *
478
- * | Property | Type | Description |
479
- * |---|---|---|
480
- * | `proposalId` | `string` | The id of the proposal to rename |
481
- * | `name` | `string` | The new display name (capped at 24 chars) |
482
- */
483
- export const PluginCoreProposalsRenameArgs = z.object({
484
- proposalId: z.string(),
485
- name: z.string(),
486
- })
487
- export type PluginCoreProposalsRenameArgs = z.infer<
488
- typeof PluginCoreProposalsRenameArgs
489
- >
490
-
491
- /** Result of {@linkcode PluginCoreProposalsApi.rename} — the updated record. */
492
- export const PluginCoreProposalsRenameResult = PluginCoreProposalRef
493
- export type PluginCoreProposalsRenameResult = z.infer<
494
- typeof PluginCoreProposalsRenameResult
495
- >
496
-
497
- /**
498
- * Arguments for {@linkcode PluginCoreProposalsApi.setActive}.
499
- *
500
- * | Property | Type | Description |
501
- * |---|---|---|
502
- * | `proposalId` | `string` | The id of the proposal to activate |
503
- */
504
- export const PluginCoreProposalsSetActiveArgs = z.object({
505
- proposalId: z.string(),
506
- })
507
- export type PluginCoreProposalsSetActiveArgs = z.infer<
508
- typeof PluginCoreProposalsSetActiveArgs
509
- >
510
-
511
- /**
512
- * Result of {@linkcode PluginCoreProposalsApi.setActive} — the now-active
513
- * proposal's id.
514
- *
515
- * | Property | Type | Description |
516
- * |---|---|---|
517
- * | `id` | `string` | The id of the now-active proposal |
518
- */
519
- export const PluginCoreProposalsSetActiveResult = z.object({
520
- id: z.string(),
521
- })
522
- export type PluginCoreProposalsSetActiveResult = z.infer<
523
- typeof PluginCoreProposalsSetActiveResult
524
- >
525
-
526
- /**
527
- * Arguments for {@linkcode PluginCoreProposalsApi.delete}. Exactly one
528
- * disposition is required: `deleteObjects: true` XOR `targetProposalId` (a
529
- * **different** proposal). A bare delete with neither is rejected.
530
- *
531
- * | Property | Type | Description |
532
- * |---|---|---|
533
- * | `proposalId` | `string` | The id of the proposal to delete |
534
- * | `deleteObjects` | `true \| undefined` | Destroy the geometry in exclusive buckets (mutually exclusive with `targetProposalId`) |
535
- * | `targetProposalId` | `string \| undefined` | Transfer this proposal's buckets here before deleting (must differ from `proposalId`) |
536
- */
537
- export const PluginCoreProposalsDeleteArgs = z
538
- .object({
539
- proposalId: z.string(),
540
- deleteObjects: z.boolean().optional(),
541
- targetProposalId: z.string().optional(),
542
- })
543
- .refine((v) => (v.deleteObjects === true) !== (v.targetProposalId != null), {
544
- message:
545
- "Provide exactly one disposition: deleteObjects: true (destroy exclusive-bucket geometry) or targetProposalId (transfer buckets, then delete)",
546
- })
547
- .refine(
548
- (v) => v.targetProposalId == null || v.targetProposalId !== v.proposalId,
549
- {
550
- message: "targetProposalId must be a different proposal than proposalId",
551
- },
552
- )
553
- export type PluginCoreProposalsDeleteArgs = z.infer<
554
- typeof PluginCoreProposalsDeleteArgs
555
- >
556
-
557
- /**
558
- * Result of {@linkcode PluginCoreProposalsApi.delete} — confirmation that the
559
- * proposal was deleted.
560
- *
561
- * | Property | Type | Description |
562
- * |---|---|---|
563
- * | `deleted` | `true` | Always `true` on success |
564
- */
565
- export const PluginCoreProposalsDeleteResult = z.object({
566
- deleted: z.literal(true),
567
- })
568
- export type PluginCoreProposalsDeleteResult = z.infer<
569
- typeof PluginCoreProposalsDeleteResult
570
- >
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 is a layer in the
10
+ * Layers panel; read them with `core.layers.get` / {@linkcode PluginCoreLayersApi}),
11
+ * and switching the active proposal shows/hides the buckets it owns. Proposals
12
+ * are **infrastructure**, not design — they own the buckets/layers geometry
13
+ * lives in — so they sit under `core`, not `design` (constitution §2A).
14
+ *
15
+ * Reads return plain records and never throw for a missing proposal (`get` and
16
+ * `getActive` return `null`, `list` / `listForComponent` return an empty list,
17
+ * `isActive` returns `false`). Ids are stable public strings.
18
+ *
19
+ * On legacy / no-proposal projects the scoping predicates degrade gracefully:
20
+ * `getActive` returns `null` and the reads return empty.
21
+ *
22
+ * Proposal writes are **not undoable** — create / rename / setActive / delete
23
+ * persist directly and bypass the undo stack; a `core.history.undo` will not
24
+ * reverse them. Each proposal keeps its own isolated undo stack for the
25
+ * geometry edits made inside it.
26
+ *
27
+ * Accessed via `snaptrude.core.proposals`.
28
+ */
29
+ export abstract class PluginCoreProposalsApi {
30
+ constructor() {}
31
+
32
+ /**
33
+ * List the design options (proposals) in the active project.
34
+ *
35
+ * @returns A {@linkcode PluginCoreProposalsListResult} with a `proposals`
36
+ * array (empty on a legacy / no-proposal project).
37
+ *
38
+ * @examplePrompt List all the design options in this project
39
+ * @examplePrompt How many proposals does this project have?
40
+ * @examplePrompt Show every design variant with its name
41
+ *
42
+ * # Example
43
+ * ```ts
44
+ * const { proposals } = await snaptrude.core.proposals.list()
45
+ * for (const p of proposals) console.log(p.id, p.name, p.isActive)
46
+ * ```
47
+ */
48
+ public abstract list(): PluginApiReturn<PluginCoreProposalsListResult>
49
+
50
+ /**
51
+ * Get a single proposal by id.
52
+ *
53
+ * @param proposalId - The id of the proposal to read.
54
+ * @returns The matching {@linkcode PluginCoreProposalRef}, or `null` if no
55
+ * proposal has that id.
56
+ *
57
+ * @examplePrompt Get the proposal with id prop_1
58
+ * @examplePrompt Look up a single design option by its id
59
+ * @examplePrompt Which buckets belong to proposal prop_2?
60
+ *
61
+ * # Example
62
+ * ```ts
63
+ * const proposal = await snaptrude.core.proposals.get("prop_1")
64
+ * if (proposal) console.log(proposal.name, proposal.bucketIds)
65
+ * ```
66
+ */
67
+ public abstract get(
68
+ proposalId: string,
69
+ ): PluginApiReturn<PluginCoreProposalsGetResult>
70
+
71
+ /**
72
+ * Get the active proposal.
73
+ *
74
+ * The active proposal is the design option currently shown and edited.
75
+ * Honors a present-mode capture override: while a present-mode capture is in
76
+ * flight this returns the captured proposal, not the redux-active one. Paired
77
+ * with {@linkcode PluginCoreProposalsApi.setActive}.
78
+ *
79
+ * @returns The active {@linkcode PluginCoreProposalRef}, or `null` on a legacy
80
+ * / no-proposal project where nothing is active.
81
+ *
82
+ * @examplePrompt Which design option is active?
83
+ * @examplePrompt Get the current proposal
84
+ * @examplePrompt What proposal am I working in?
85
+ *
86
+ * # Example
87
+ * ```ts
88
+ * const active = await snaptrude.core.proposals.getActive()
89
+ * if (active) console.log(active.id, active.name)
90
+ * ```
91
+ */
92
+ public abstract getActive(): PluginApiReturn<PluginCoreProposalsGetActiveResult>
93
+
94
+ /**
95
+ * List the proposals a component belongs to.
96
+ *
97
+ * Returns **every** proposal whose buckets include the component — a bucket
98
+ * can be shared across proposals, so a component may appear in more than one.
99
+ *
100
+ * @param component - The component to look up.
101
+ * @returns A {@linkcode PluginCoreProposalsListForComponentResult} with a
102
+ * `proposals` array (empty when the component is out of scope or the project
103
+ * has no proposals).
104
+ *
105
+ * @examplePrompt Which design options is this component part of?
106
+ * @examplePrompt List the proposals that contain this wall
107
+ * @examplePrompt Is this element shared across multiple design variants?
108
+ *
109
+ * # Example
110
+ * ```ts
111
+ * const { proposals } = await snaptrude.core.proposals.listForComponent(wall)
112
+ * console.log(`shared across ${proposals.length} proposals`)
113
+ * ```
114
+ */
115
+ public abstract listForComponent(
116
+ component: ComponentHandle,
117
+ ): PluginApiReturn<PluginCoreProposalsListForComponentResult>
118
+
119
+ /**
120
+ * Test whether a proposal is the active one.
121
+ *
122
+ * @param proposalId - The id of the proposal to test.
123
+ * @returns `true` if it is the active proposal, otherwise `false` (including
124
+ * when no proposal has that id).
125
+ *
126
+ * @examplePrompt Is proposal prop_2 the active one?
127
+ * @examplePrompt Check whether this design option is currently shown
128
+ * @examplePrompt Am I working in proposal prop_1?
129
+ *
130
+ * # Example
131
+ * ```ts
132
+ * if (await snaptrude.core.proposals.isActive("prop_1")) {
133
+ * console.log("prop_1 is active")
134
+ * }
135
+ * ```
136
+ */
137
+ public abstract isActive(proposalId: string): PluginApiReturn<boolean>
138
+
139
+ /**
140
+ * Create a new design option (proposal) — blank, or configured from a base
141
+ * proposal (the New Proposal modal's two paths).
142
+ *
143
+ * **Pro-gated** (creating proposals requires a Pro plan, matching the UI) and
144
+ * **not undoable**. The name is capped at 24 characters.
145
+ *
146
+ * **Blank** (no `options`) — creates a bare proposal with a single empty
147
+ * default bucket; it does **not** carry forward the current proposal's
148
+ * layers, views, or targets, and the active proposal does **not** change.
149
+ *
150
+ * **Configured** (`options.baseProposalId`) — the modal's Configure path:
151
+ * carries the base proposal's buckets (layers) forward per `options.layers`,
152
+ * where each entry names a bucket of the base proposal and an action —
153
+ * `"share"` (the new proposal references the same live bucket) or
154
+ * `"duplicate"` (an independent copy of the bucket and its contents).
155
+ * Buckets not listed are left behind. Omitting `layers` carries **every**
156
+ * base bucket forward as a `"duplicate"` (the modal's default). The base
157
+ * proposal's views and area targets are copied either way, and the new
158
+ * proposal becomes the **active** one (implicit switch — blocked with
159
+ * `PROPOSAL_SWITCH_BLOCKED` while an in-canvas agent run is in flight, like
160
+ * {@linkcode PluginCoreProposalsApi.setActive}).
161
+ *
162
+ * @param name - Display name of the new proposal (capped at 24 chars).
163
+ * @param options - Optional configure path: `baseProposalId` (the proposal to
164
+ * carry forward from) and `layers` (per-bucket `"share"` / `"duplicate"`
165
+ * actions; omitted buckets are excluded, omitted `layers` duplicates all).
166
+ * @returns The new proposal's id (read the full record back with
167
+ * {@linkcode PluginCoreProposalsApi.get}).
168
+ * @throws If the caller is not on a Pro plan, the name is empty/duplicate,
169
+ * `layers` is passed without `baseProposalId`, the base proposal or a
170
+ * listed bucket does not exist / does not belong to the base proposal, or
171
+ * (configured only) `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is
172
+ * in flight.
173
+ *
174
+ * @examplePrompt Create a design option called Scheme B
175
+ * @examplePrompt Add a new proposal named Option 2
176
+ * @examplePrompt Duplicate the current proposal as Scheme C
177
+ * @examplePrompt Create a design option based on Proposal 1 that shares its site layer
178
+ *
179
+ * # Example
180
+ * ```ts
181
+ * // blank design option
182
+ * const { id } = await snaptrude.core.proposals.create("Scheme B")
183
+ *
184
+ * // configured from the active proposal: duplicate every layer
185
+ * const base = await snaptrude.core.proposals.getActive()
186
+ * if (base) {
187
+ * const { id: dup } = await snaptrude.core.proposals.create("Scheme C", {
188
+ * baseProposalId: base.id,
189
+ * })
190
+ * // …or share one layer and leave the rest behind
191
+ * const { id: shared } = await snaptrude.core.proposals.create("Scheme D", {
192
+ * baseProposalId: base.id,
193
+ * layers: [{ bucketId: base.defaultBucketId, action: "share" }],
194
+ * })
195
+ * }
196
+ * ```
197
+ */
198
+ public abstract create(
199
+ name: string,
200
+ options?: {
201
+ baseProposalId: string
202
+ layers?: { bucketId: string; action: "share" | "duplicate" }[]
203
+ },
204
+ ): PluginApiReturn<PluginCoreProposalsCreateResult>
205
+
206
+ /**
207
+ * Rename a proposal.
208
+ *
209
+ * **Not undoable.** The name is capped at 24 characters.
210
+ *
211
+ * @param proposalId - The id of the proposal to rename.
212
+ * @param name - The new display name (capped at 24 chars).
213
+ * @returns The updated {@linkcode PluginCoreProposalRef}.
214
+ * @throws If no proposal has the given id, or the name is empty / duplicates
215
+ * another proposal.
216
+ *
217
+ * @examplePrompt Rename proposal prop_1 to Final Scheme
218
+ * @examplePrompt Change the name of this design option
219
+ * @examplePrompt Call proposal prop_2 "Option A"
220
+ *
221
+ * # Example
222
+ * ```ts
223
+ * const proposal = await snaptrude.core.proposals.rename("prop_1", "Final Scheme")
224
+ * ```
225
+ */
226
+ public abstract rename(
227
+ proposalId: string,
228
+ name: string,
229
+ ): PluginApiReturn<PluginCoreProposalsRenameResult>
230
+
231
+ /**
232
+ * Switch the active proposal.
233
+ *
234
+ * A **heavy scene switch** and **not undoable**. Switching clears the current
235
+ * selection, resets AI / streaming state, cancels proposal-scoped operations,
236
+ * retoggles bucket visibility to the new proposal's owned set, restores its
237
+ * per-proposal site/FSI settings, and realigns the views panel. After the
238
+ * switch, **writes** are guarded to the new active proposal (edits targeting
239
+ * components outside it are rejected), but **list reads are NOT
240
+ * proposal-filtered** — `design.query.list*` and similar reads stay
241
+ * project-wide; filter their results via
242
+ * {@linkcode PluginCoreProposalsApi.listForComponent} if you need
243
+ * proposal-scoped sets.
244
+ *
245
+ * **Blocked while an in-canvas agent run is in flight** — throws
246
+ * `PROPOSAL_SWITCH_BLOCKED` if a canvas-agent run is active (matching the UI's
247
+ * hard block), because a switch would cancel the in-flight run.
248
+ *
249
+ * @param proposalId - The id of the proposal to activate.
250
+ * @returns The id of the now-active proposal.
251
+ * @throws `PROPOSAL_SWITCH_BLOCKED` while a canvas-agent run is in flight; or
252
+ * if no proposal has the given id.
253
+ *
254
+ * @examplePrompt Switch to proposal prop_2
255
+ * @examplePrompt Make Scheme B the active design option
256
+ * @examplePrompt Show me the other proposal
257
+ *
258
+ * # Example
259
+ * ```ts
260
+ * await snaptrude.core.proposals.setActive("prop_2")
261
+ * ```
262
+ */
263
+ public abstract setActive(
264
+ proposalId: string,
265
+ ): PluginApiReturn<PluginCoreProposalsSetActiveResult>
266
+
267
+ /**
268
+ * Delete a proposal.
269
+ *
270
+ * **DANGEROUS AND IRREVERSIBLE — not undoable.** You must choose **exactly
271
+ * one** explicit disposition (mirroring the product's delete dialog):
272
+ *
273
+ * - `{ deleteObjects: true }` — delete the proposal AND **permanently
274
+ * destroy the geometry** in its exclusive buckets. Buckets shared with
275
+ * other proposals are never destroyed.
276
+ * - `{ targetProposalId }` — transfer **all** of this proposal's buckets to
277
+ * another proposal first (keeping the geometry alive), then delete the
278
+ * emptied proposal — "merge and remove". The target must be a
279
+ * **different** proposal.
280
+ *
281
+ * A bare `delete(proposalId)` is **rejected**: there is no safe default —
282
+ * without a disposition the engine would drop the proposal's bucket records
283
+ * while leaving their components orphaned. Passing both dispositions at once
284
+ * is also rejected as ambiguous.
285
+ *
286
+ * It is **server-validated**: the backend refuses the delete (throws) if a
287
+ * collaborator is active in that proposal, and the check times out after
288
+ * ~10s. The project's **last remaining proposal cannot be deleted**. If the
289
+ * proposal is active, the host switches away first.
290
+ *
291
+ * @param proposalId - The id of the proposal to delete.
292
+ * @param options - Exactly one disposition: `{ deleteObjects: true }`
293
+ * (destroy exclusive-bucket geometry) or `{ targetProposalId }` (transfer
294
+ * buckets to that proposal, then delete).
295
+ * @returns Confirmation that the proposal was deleted.
296
+ * @throws If no disposition (or both) is given, `targetProposalId` equals
297
+ * `proposalId`, no proposal has the given id, it is the only proposal, a
298
+ * collaborator lock refuses the delete, or the server validation times out.
299
+ *
300
+ * @examplePrompt Delete proposal prop_2 and everything in it
301
+ * @examplePrompt Remove this design option and destroy its geometry
302
+ * @examplePrompt Merge prop_2's layers into prop_1 and delete prop_2
303
+ *
304
+ * # Example
305
+ * ```ts
306
+ * // destroy the proposal and its exclusive geometry
307
+ * await snaptrude.core.proposals.delete("prop_2", { deleteObjects: true })
308
+ * // …or keep the geometry by transferring its buckets to prop_1 first
309
+ * await snaptrude.core.proposals.delete("prop_2", { targetProposalId: "prop_1" })
310
+ * ```
311
+ */
312
+ public abstract delete(
313
+ proposalId: string,
314
+ options:
315
+ | { deleteObjects: true; targetProposalId?: never }
316
+ | { deleteObjects?: false; targetProposalId: string },
317
+ ): PluginApiReturn<PluginCoreProposalsDeleteResult>
318
+ }
319
+
320
+ /**
321
+ * A design option (proposal) in the active project.
322
+ *
323
+ * | Property | Type | Description |
324
+ * |---|---|---|
325
+ * | `id` | `string` | Stable public proposal id (e.g. `"prop_1"`) |
326
+ * | `name` | `string` | Display name (≤ 24 chars) |
327
+ * | `isActive` | `boolean` | Whether this is the active proposal |
328
+ * | `defaultBucketId` | `string` | Id of the proposal's default layer (cannot be unshared); read it with `core.layers.get` ({@linkcode PluginCoreLayersApi}) |
329
+ * | `bucketIds` | `string[]` | Ids of every layer in the Layers panel the proposal owns; read them with `core.layers.get` ({@linkcode PluginCoreLayersApi}) |
330
+ * | `createdAt` | `string` | ISO creation timestamp |
331
+ * | `createdBy` | `string` | User id of the creator |
332
+ */
333
+ export const PluginCoreProposalRef = z.object({
334
+ id: z.string(),
335
+ name: z.string(),
336
+ isActive: z.boolean(),
337
+ defaultBucketId: z.string(),
338
+ bucketIds: z.array(z.string()),
339
+ createdAt: z.string(),
340
+ createdBy: z.string(),
341
+ })
342
+ export type PluginCoreProposalRef = z.infer<typeof PluginCoreProposalRef>
343
+
344
+ /**
345
+ * Result of {@linkcode PluginCoreProposalsApi.list}.
346
+ *
347
+ * | Property | Type | Description |
348
+ * |---|---|---|
349
+ * | `proposals` | {@linkcode PluginCoreProposalRef}`[]` | Every proposal in the active project |
350
+ */
351
+ export const PluginCoreProposalsListResult = z.object({
352
+ proposals: z.array(PluginCoreProposalRef),
353
+ })
354
+ export type PluginCoreProposalsListResult = z.infer<
355
+ typeof PluginCoreProposalsListResult
356
+ >
357
+
358
+ /**
359
+ * Arguments for {@linkcode PluginCoreProposalsApi.get} /
360
+ * {@linkcode PluginCoreProposalsApi.isActive}.
361
+ *
362
+ * | Property | Type | Description |
363
+ * |---|---|---|
364
+ * | `proposalId` | `string` | The id of the proposal |
365
+ */
366
+ export const PluginCoreProposalsGetArgs = z.object({
367
+ proposalId: z.string(),
368
+ })
369
+ export type PluginCoreProposalsGetArgs = z.infer<
370
+ typeof PluginCoreProposalsGetArgs
371
+ >
372
+
373
+ /**
374
+ * Result of {@linkcode PluginCoreProposalsApi.get} — the proposal, or `null`
375
+ * when no proposal has the given id.
376
+ */
377
+ export const PluginCoreProposalsGetResult = PluginCoreProposalRef.nullable()
378
+ export type PluginCoreProposalsGetResult = z.infer<
379
+ typeof PluginCoreProposalsGetResult
380
+ >
381
+
382
+ /**
383
+ * Result of {@linkcode PluginCoreProposalsApi.getActive} — the active proposal,
384
+ * or `null` when nothing is active (legacy / no-proposal project).
385
+ */
386
+ export const PluginCoreProposalsGetActiveResult =
387
+ PluginCoreProposalRef.nullable()
388
+ export type PluginCoreProposalsGetActiveResult = z.infer<
389
+ typeof PluginCoreProposalsGetActiveResult
390
+ >
391
+
392
+ /**
393
+ * Arguments for {@linkcode PluginCoreProposalsApi.listForComponent}.
394
+ *
395
+ * | Property | Type | Description |
396
+ * |---|---|---|
397
+ * | `component` | {@linkcode ComponentHandle} | The component to look up |
398
+ */
399
+ export const PluginCoreProposalsListForComponentArgs = z.object({
400
+ component: ComponentHandle,
401
+ })
402
+ export type PluginCoreProposalsListForComponentArgs = z.infer<
403
+ typeof PluginCoreProposalsListForComponentArgs
404
+ >
405
+
406
+ /**
407
+ * Result of {@linkcode PluginCoreProposalsApi.listForComponent} — the proposals
408
+ * the component belongs to (empty when out of scope or none). Same shape as
409
+ * {@linkcode PluginCoreProposalsListResult}.
410
+ */
411
+ export const PluginCoreProposalsListForComponentResult =
412
+ PluginCoreProposalsListResult
413
+ export type PluginCoreProposalsListForComponentResult = z.infer<
414
+ typeof PluginCoreProposalsListForComponentResult
415
+ >
416
+
417
+ /**
418
+ * Arguments for {@linkcode PluginCoreProposalsApi.isActive}. Alias of
419
+ * {@linkcode PluginCoreProposalsGetArgs}.
420
+ */
421
+ export const PluginCoreProposalsIsActiveArgs = PluginCoreProposalsGetArgs
422
+ export type PluginCoreProposalsIsActiveArgs = z.infer<
423
+ typeof PluginCoreProposalsIsActiveArgs
424
+ >
425
+
426
+ /**
427
+ * Arguments for {@linkcode PluginCoreProposalsApi.create}. A bare `name` makes
428
+ * a blank proposal; `baseProposalId` switches to the configured (carry-forward)
429
+ * path. `layers` requires `baseProposalId` — each entry names a bucket of the
430
+ * base proposal; omitted buckets are excluded, and omitting `layers` entirely
431
+ * duplicates every base bucket.
432
+ *
433
+ * | Property | Type | Description |
434
+ * |---|---|---|
435
+ * | `name` | `string` | Display name of the new proposal (capped at 24 chars) |
436
+ * | `baseProposalId` | `string \| undefined` | Proposal to carry forward from (configured path) |
437
+ * | `layers` | `{ bucketId: string; action: "share" \| "duplicate" }[] \| undefined` | Per-bucket carry-forward actions (requires `baseProposalId`) |
438
+ */
439
+ export const PluginCoreProposalsCreateArgs = z
440
+ .object({
441
+ name: z.string(),
442
+ baseProposalId: z.string().optional(),
443
+ layers: z
444
+ .array(
445
+ z.object({
446
+ bucketId: z.string(),
447
+ action: z.enum(["share", "duplicate"]),
448
+ }),
449
+ )
450
+ .optional(),
451
+ })
452
+ .refine((v) => v.layers === undefined || v.baseProposalId !== undefined, {
453
+ message:
454
+ "layers requires baseProposalId — the layers describe how to carry the base proposal's buckets forward",
455
+ })
456
+ export type PluginCoreProposalsCreateArgs = z.infer<
457
+ typeof PluginCoreProposalsCreateArgs
458
+ >
459
+
460
+ /**
461
+ * Result of {@linkcode PluginCoreProposalsApi.create} — the id of the new
462
+ * proposal.
463
+ *
464
+ * | Property | Type | Description |
465
+ * |---|---|---|
466
+ * | `id` | `string` | The id of the newly created proposal |
467
+ */
468
+ export const PluginCoreProposalsCreateResult = z.object({
469
+ id: z.string(),
470
+ })
471
+ export type PluginCoreProposalsCreateResult = z.infer<
472
+ typeof PluginCoreProposalsCreateResult
473
+ >
474
+
475
+ /**
476
+ * Arguments for {@linkcode PluginCoreProposalsApi.rename}.
477
+ *
478
+ * | Property | Type | Description |
479
+ * |---|---|---|
480
+ * | `proposalId` | `string` | The id of the proposal to rename |
481
+ * | `name` | `string` | The new display name (capped at 24 chars) |
482
+ */
483
+ export const PluginCoreProposalsRenameArgs = z.object({
484
+ proposalId: z.string(),
485
+ name: z.string(),
486
+ })
487
+ export type PluginCoreProposalsRenameArgs = z.infer<
488
+ typeof PluginCoreProposalsRenameArgs
489
+ >
490
+
491
+ /** Result of {@linkcode PluginCoreProposalsApi.rename} — the updated record. */
492
+ export const PluginCoreProposalsRenameResult = PluginCoreProposalRef
493
+ export type PluginCoreProposalsRenameResult = z.infer<
494
+ typeof PluginCoreProposalsRenameResult
495
+ >
496
+
497
+ /**
498
+ * Arguments for {@linkcode PluginCoreProposalsApi.setActive}.
499
+ *
500
+ * | Property | Type | Description |
501
+ * |---|---|---|
502
+ * | `proposalId` | `string` | The id of the proposal to activate |
503
+ */
504
+ export const PluginCoreProposalsSetActiveArgs = z.object({
505
+ proposalId: z.string(),
506
+ })
507
+ export type PluginCoreProposalsSetActiveArgs = z.infer<
508
+ typeof PluginCoreProposalsSetActiveArgs
509
+ >
510
+
511
+ /**
512
+ * Result of {@linkcode PluginCoreProposalsApi.setActive} — the now-active
513
+ * proposal's id.
514
+ *
515
+ * | Property | Type | Description |
516
+ * |---|---|---|
517
+ * | `id` | `string` | The id of the now-active proposal |
518
+ */
519
+ export const PluginCoreProposalsSetActiveResult = z.object({
520
+ id: z.string(),
521
+ })
522
+ export type PluginCoreProposalsSetActiveResult = z.infer<
523
+ typeof PluginCoreProposalsSetActiveResult
524
+ >
525
+
526
+ /**
527
+ * Arguments for {@linkcode PluginCoreProposalsApi.delete}. Exactly one
528
+ * disposition is required: `deleteObjects: true` XOR `targetProposalId` (a
529
+ * **different** proposal). A bare delete with neither is rejected.
530
+ *
531
+ * | Property | Type | Description |
532
+ * |---|---|---|
533
+ * | `proposalId` | `string` | The id of the proposal to delete |
534
+ * | `deleteObjects` | `true \| undefined` | Destroy the geometry in exclusive buckets (mutually exclusive with `targetProposalId`) |
535
+ * | `targetProposalId` | `string \| undefined` | Transfer this proposal's buckets here before deleting (must differ from `proposalId`) |
536
+ */
537
+ export const PluginCoreProposalsDeleteArgs = z
538
+ .object({
539
+ proposalId: z.string(),
540
+ deleteObjects: z.boolean().optional(),
541
+ targetProposalId: z.string().optional(),
542
+ })
543
+ .refine((v) => (v.deleteObjects === true) !== (v.targetProposalId != null), {
544
+ message:
545
+ "Provide exactly one disposition: deleteObjects: true (destroy exclusive-bucket geometry) or targetProposalId (transfer buckets, then delete)",
546
+ })
547
+ .refine(
548
+ (v) => v.targetProposalId == null || v.targetProposalId !== v.proposalId,
549
+ {
550
+ message: "targetProposalId must be a different proposal than proposalId",
551
+ },
552
+ )
553
+ export type PluginCoreProposalsDeleteArgs = z.infer<
554
+ typeof PluginCoreProposalsDeleteArgs
555
+ >
556
+
557
+ /**
558
+ * Result of {@linkcode PluginCoreProposalsApi.delete} — confirmation that the
559
+ * proposal was deleted.
560
+ *
561
+ * | Property | Type | Description |
562
+ * |---|---|---|
563
+ * | `deleted` | `true` | Always `true` on success |
564
+ */
565
+ export const PluginCoreProposalsDeleteResult = z.object({
566
+ deleted: z.literal(true),
567
+ })
568
+ export type PluginCoreProposalsDeleteResult = z.infer<
569
+ typeof PluginCoreProposalsDeleteResult
570
+ >