Appearance
Proposals
Design options — multiple variants of one project. Accessed via snaptrude.core.proposals.
A proposal owns a set of buckets (a bucket == a drawing layer); switching the active proposal shows/hides the buckets it owns. Reads never throw (a miss returns null / [] / false) and degrade to empty on legacy / no-proposal projects. Writes are gated by the plugin write floor and are non-undoable (they persist directly, bypassing core.history); create is additionally Pro-gated.
Types
PluginCoreProposalRef
A safe projection of a proposal (the raw redux state is never exposed).
| Property | Type | Description |
|---|---|---|
id | string | Proposal id |
name | string | Display name |
isActive | boolean | Whether this is the active proposal |
defaultBucketId | string | The proposal's default bucket id |
bucketIds | string[] | Buckets owned by the proposal |
createdAt | string | Creation timestamp |
createdBy | string | Id of the user who created it |
Functions
list()
List every proposal in the project.
- Returns:
{ proposals: PluginCoreProposalRef[] }
ts
const { proposals } = await snaptrude.core.proposals.list();get(proposalId)
Get one proposal by id.
- Parameters:
proposalId:string— The proposal id
- Returns:
PluginCoreProposalRef | null—nullwhen no proposal matches
getActive()
Get the currently active proposal (honours the present-mode capture override).
- Returns:
PluginCoreProposalRef | null—nullon a legacy / no-proposal project
listForComponent(component)
List the proposals a component belongs to (cross-proposal — buckets can be shared).
- Parameters:
component:ComponentHandle— The component to look up
- Returns:
{ proposals: PluginCoreProposalRef[] }— empty when the component is unknown
isActive(proposalId)
Whether a proposal id is the active one.
- Parameters:
proposalId:string— The proposal id
- Returns:
boolean
create(name, options?)
Create a new proposal — blank, or configured from a base proposal (the New Proposal modal's two paths). Pro-gated and write-gated; non-undoable. The name is truncated to 24 chars; empty/duplicate names throw.
Blank (no options) — a bare proposal with a single empty default bucket. Nothing is carried forward and the active proposal does not change.
Configured (options.baseProposalId) — the modal's Configure path: carries the base proposal's buckets (layers) forward per options.layers. Each entry names a bucket of the base proposal with an action — "share" (the new proposal references the same live bucket) or "duplicate" (an independent copy of the bucket and its contents). Buckets not listed are left behind; omitting layers duplicates every base bucket (the modal's default). The base proposal's views and area targets are copied either way, and the new proposal becomes the active one (implicit switch — blocked with PROPOSAL_SWITCH_BLOCKED while an in-canvas agent run is in flight, like setActive).
- Parameters:
name:string— The new proposal's nameoptions:object(optional) — Configure path:{ baseProposalId: string, layers?: { bucketId: string, action: "share" | "duplicate" }[] }.layersrequiresbaseProposalId; everybucketIdmust belong to the base proposal
- Returns:
{ id: string }— The created proposal id - Throws: when writes are disabled, the workspace is not on a Pro plan, the name is empty/duplicate,
layersis passed withoutbaseProposalId, the base proposal is unknown, a listed bucket does not belong to the base proposal (or repeats), or (configured only)PROPOSAL_SWITCH_BLOCKEDwhile a canvas-agent run is in flight
ts
// blank
const { id } = await snaptrude.core.proposals.create("Option B");
// configured: duplicate every layer of the active proposal
const base = await snaptrude.core.proposals.getActive();
const { id: dup } = await snaptrude.core.proposals.create("Option C", {
baseProposalId: base!.id
});
// configured: share one layer, leave the rest behind
const { id: shared } = await snaptrude.core.proposals.create("Option D", {
baseProposalId: base!.id,
layers: [{ bucketId: base!.defaultBucketId, action: "share" }]
});rename(proposalId, name)
Rename a proposal. Write-gated; non-undoable.
- Parameters:
proposalId:string— The proposal to renamename:string— The new name (truncated to 24 chars)
- Returns:
PluginCoreProposalRef— The renamed proposal - Throws: for an unknown id or an empty/duplicate name
setActive(proposalId)
Switch the active proposal — shows/hides the buckets it owns. After the switch, writes are guarded to the new active proposal (edits targeting components outside it are rejected), but list reads are NOT proposal-filtered — design.query.list* and similar reads stay project-wide; filter their results via listForComponent if you need proposal-scoped sets. Write-gated; non-undoable.
- Parameters:
proposalId:string— The proposal to activate
- Returns:
{ id: string }— The now-active proposal id - Throws: for an unknown id, or
PROPOSAL_SWITCH_BLOCKEDwhen an in-canvas agent run is in flight
ts
await snaptrude.core.proposals.setActive(id);delete(proposalId, options)
Delete a proposal. DANGEROUS AND IRREVERSIBLE — not undoable. You must choose exactly one explicit disposition (mirroring the product's delete dialog):
{ deleteObjects: true }— delete the proposal AND permanently destroy the geometry in its exclusive buckets. Buckets shared with other proposals are never destroyed.{ targetProposalId }— transfer all of this proposal's buckets to another proposal first (keeping the geometry alive), then delete the emptied proposal — "merge and remove". The target must be a different proposal.
A bare delete(proposalId) is rejected: there is no safe default — without a disposition the engine would drop the proposal's bucket records while leaving their components orphaned. Passing both dispositions at once is also rejected as ambiguous.
Server-validated (a collaborator active in the proposal, or a validation timeout, refuses the delete). The last remaining proposal cannot be deleted. If the proposal is active, the host switches away first. Write-gated.
- Parameters:
proposalId:string— The proposal to deleteoptions:object— Exactly one disposition:{ deleteObjects: true }(destroy exclusive-bucket geometry) or{ targetProposalId: string }(transfer buckets there, then delete; must differ fromproposalId)
- Returns:
{ deleted: true } - Throws: if no disposition (or both) is given,
targetProposalIdequalsproposalId, the id is unknown, it is the last remaining proposal, or a refused/timed-out server validation
ts
// destroy the proposal and its exclusive geometry
await snaptrude.core.proposals.delete("prop_2", { deleteObjects: true });
// …or keep the geometry by transferring its buckets to prop_1 first
await snaptrude.core.proposals.delete("prop_2", { targetProposalId: "prop_1" });