` entries). Hunt for these under ANY variable name; a root that silently becomes `{}`/`null` puts the component into a dead offline mode.- Dotted-name dispatchers (`commands["Takeoffs.list"]`, `cmd('Pages.list')`) must split the pa and resolve `window[namespace][method]`.- Availability probes are unreliable: namespace globals are lazy proxies, so ANY method name — including typos and removed commands — is truthy and callable, and only rejects with "Unknown command" when invoked. Fix probe NAMES (e.g. `'listByPage'` → `'list'`), treat an "Unknown command" rejection as a typo, and never feature-probe by calling entity commands (a probing insert creates real records).- `zzTakeoff.darkMode`, `zzTakeoff.post/on`, and `window.zzTakeoff` presence checks still work ## 8. Insert behavior (usually no code change) Inserts honor an explicit `order` and default to the next slot under the parent (folders sortfirst). Typed non-folder takeoffs get an auto color unless `properties.color` is set.`DrawObjects.insert` defaults `layerId` to the current layer and it must match the linkedtakeoff's layer. `startTakeoff` with `properties` auto-assigns nothing — include`properties.color` or the drawing is invisible. Layers/Libraries/Lists `insert` accepts `{name}`or `{properties: {name: {value}}}`. ## 9. Discipline - Fix comments/docs that describe removed APIs, but leave user-visible strings unless misleading.- Do not touch identifiers that merely LOOK like the old API (a plugin's own `this.commands` array, an XLSX parser's `l.name`, `canvas.getContext('2d')`).- Preserve line endings (some files are CRLF) and minified formatting where present.- After editing, re-scan for: `.commands`, `Storage`/`PluginLocalStorage`, `listByPage`, `getChildren`/`getDescendants`/`getCutouts`, `clearSelectedDrawObjects`, positional `DrawObjects.list(x)`/`Lists.list(x)`, single-object `X.update({...Id...})`, capitalized `.button` comparisons, and flat `.name`/`.id` reads on layer/library/list records — including inside strings, probes, fallback chains, and comments."/> ` entries). Hunt for these under ANY variable name; a root that silently becomes `{}`/`null` puts the component into a dead offline mode.- Dotted-name dispatchers (`commands["Takeoffs.list"]`, `cmd('Pages.list')`) must split the pa and resolve `window[namespace][method]`.- Availability probes are unreliable: namespace globals are lazy proxies, so ANY method name — including typos and removed commands — is truthy and callable, and only rejects with "Unknown command" when invoked. Fix probe NAMES (e.g. `'listByPage'` → `'list'`), treat an "Unknown command" rejection as a typo, and never feature-probe by calling entity commands (a probing insert creates real records).- `zzTakeoff.darkMode`, `zzTakeoff.post/on`, and `window.zzTakeoff` presence checks still work ## 8. Insert behavior (usually no code change) Inserts honor an explicit `order` and default to the next slot under the parent (folders sortfirst). Typed non-folder takeoffs get an auto color unless `properties.color` is set.`DrawObjects.insert` defaults `layerId` to the current layer and it must match the linkedtakeoff's layer. `startTakeoff` with `properties` auto-assigns nothing — include`properties.color` or the drawing is invisible. Layers/Libraries/Lists `insert` accepts `{name}`or `{properties: {name: {value}}}`. ## 9. Discipline - Fix comments/docs that describe removed APIs, but leave user-visible strings unless misleading.- Do not touch identifiers that merely LOOK like the old API (a plugin's own `this.commands` array, an XLSX parser's `l.name`, `canvas.getContext('2d')`).- Preserve line endings (some files are CRLF) and minified formatting where present.- After editing, re-scan for: `.commands`, `Storage`/`PluginLocalStorage`, `listByPage`, `getChildren`/`getDescendants`/`getCutouts`, `clearSelectedDrawObjects`, positional `DrawObjects.list(x)`/`Lists.list(x)`, single-object `X.update({...Id...})`, capitalized `.button` comparisons, and flat `.name`/`.id` reads on layer/library/list records — including inside strings, probes, fallback chains, and comments."/>
Prompt to Migrate Scripts & UI Components:
# Migrate zzTakeoff plugin code to the new Plugin API
You are given zzTakeoff plugin artifacts: **scripts** and/or **components**. Rewrite them from the old
plugin API to the new one using the rules below. Make minimal, surgical edits — do not
restructure, reformat, or "improve" unrelated code. All changed code must still parse.
## 1. Entity `update` commands: single config object → positional `(id, updates)`
Old form passed one object containing the ID; new form takes the ID first, then an updates object:
- `Takeoffs.update({takeoffId, ...})` → `Takeoffs.update(takeoffId, {properties, parentId, order, toolMode})`
- `DrawObjects.update({drawObjectId, ...})` → `DrawObjects.update(drawObjectId, {properties, points, parentId})`
- `Bookmarks.update({bookmarkId, ...})` → `Bookmarks.update(bookmarkId, {name, pageId, parentId, order, properties})` (`pageId` is a new shortcut for the page property)
- `Templates.update({templateId, ...})` → `Templates.update(templateId, {properties, parentId, order, toolMode})`
- `Layers.update({layerId, name})` → `Layers.update(layerId, {name})`; same pattern for
`Libraries.update(libraryId, {name})` and `Lists.update(listId, {name})` (each also accepts `{properties: {name: {value}}}`)
- `Records.update({recordId, listId, set, unset})` → `Records.update(recordId, {type, order, parentId,
properties, applyProperties, toolMode, kind, listId})` — mongo-style set/unset is GONE. Put fields
directly in updates; a field set to `null` unsets it (`parentId: null` = move to root,
`properties: null` = clear all). `properties`/`applyProperties` are now MERGED with the existing
record, not replaced. `listId` inside updates is routing scope only (defaults to the current list).
Create-only fields must be REMOVED from update payloads (they are ignored or invalid now):
`kind`, `type`, `templateId`, `layerId` on takeoffs; `type`, `pageId`, `takeoffId`, `layerId` on
draw objects. To move a record to another layer, re-create it with the target `layerId` and delete
the original. `parentId` accepts `null` or `""` for root everywhere. Properties are merged by key
on all updates; pass a property as `null` to clear it.
## 2. Traversal commands replaced
- `X.getChildren(parentId)` is REMOVED for Bookmarks/Takeoffs/Templates → `X.list({parentId})`
(root children: `{parentId: ""}` — null, "", and missing all match each other). Also update any
feature probes (`typeof X.getChildren === "function"`) and comments that mention it.
- `X.getDescendants(id, {includeRecord})` → renamed `X.listDescendants(id, {includeRecord})`, same args.
## 3. `list` commands take one optional Mongo-style equality filter object
Primitive values only; dot paths reach nested values (`{type: "area", "properties.name.value": "Slab"}`).
Results are consistently sorted. Specific migrations:
- `DrawObjects.list(takeoffId)` (positional) → `DrawObjects.list({takeoffId})`
- `DrawObjects.listByPage(pageId)` is REMOVED → `DrawObjects.list({pageId})`. It no longer def
to the current page — pass the page ID explicitly. Also fix listByPage referenced via STRING
(e.g. `cmd('DrawObjects.listByPage', ...)`, name arrays, capability probes) and remove
fallback retry branches that attempt the old method in object and positional forms.
- `Lists.list('product')` → `Lists.list({type: 'product'})`
- `DrawObjects.getCutouts(id)` is REMOVED — cutouts are embedded on the parent draw object's
`cutouts` array, and the parent's measurements already subtract holes. Unfiltered
`DrawObjects.list()` includes annotations, cutout records, and system objects.
- Renamed: `DrawObjects.clearSelectedDrawObjects()` → `DrawObjects.clearSelected()` (fix probe
strings too). New: `Layers.get(layerId)`.
## 4. Return shapes: flat `name` promoted into `properties`
Layers, Libraries, and Lists now return `{_id, ..., properties: {name: {value, result, formatt
instead of a flat `name` (layers also return the full record instead of `{_id, name, projectId
Fix EVERY read of `.name` on records returned by Layers/Libraries/Lists commands — under ANY
variable name (`l.name`, `lib.name`, `active.name`, `layers[i].name`, map/label/log expressions) —
to `x.properties.name.value` (defensively: `(x.properties && x.properties.name && x.properties
Watch for stale `.id` reads too — records use `_id`. Bookmark results no longer include the page
under properties — read the root `pageId`. Do NOT touch `.name` on a plugin's own internal objects;
only records that came from these commands.
## 5. Dialogs
`promptPopover` now returns the clicked button's name lower/camel-cased: `"OK"` → `'ok'`,
`"Generate"` → `'generate'`, `"Save As"` → `'saveAs'`. A button named Cancel, or dismissing,
resolves `undefined`. Fix every comparison against capitalized button names
(`resp.button !== 'Generate'` → `'generate'`). A pattern like `if (!r || r.button === "Cancel")`
is safe (the `!r` guard covers cancel) — leave it. Input `type` names are space-separated word
(`"text multi line"`), `date` returns an ISO string, `radio` is a boolean toggle not a choice group.
## 6. Storage: `Storage` and `PluginLocalStorage` namespaces DO NOT EXIST (and never did)
The only storage namespaces are `PluginLocalSettings`, `PluginUserSettings`,
`PluginWorkspaceSettings` (key-value: positional `get(key)`, `set(key, value)`, `remove(key)`,
`keys()`, `clear()`), and `PluginLocalDB` (document DB). Replace every use of `Storage.*` or
`PluginLocalStorage.*` — including guarded probes (`typeof Storage !== "undefined"`,
`commands.Storage`, fallback chains like `PluginLocalStorage || PluginUserSettings || Storage`
with the appropriate real namespace (default `PluginLocalSettings` for device-local state;
`PluginUserSettings`/`PluginWorkspaceSettings` for synced state). Object-form calls
(`set({key, value})`) must become positional (`set(key, value)`). In components these are async
(await them); in scripts call them without await. Note `window.Storage` in an iframe is the
browser's native constructor — selecting it as a storage API is always a bug.
## 7. Components only: `zzTakeoff.commands` is GONE
`window.zzTakeoff` now carries only `pluginComponentKey`, `darkMode`, `post`, and `on`. Every
command namespace is a window global: `Pages`, `Projects`, `Layers`, `Bookmarks`, `Takeoffs`,
`DrawObjects`, `Libraries`, `Templates`, `Lists`, `Records`, `Reports`, `Files`, `XML`, `Tools
`Dialog`, `PluginLocalSettings`, `PluginUserSettings`, `PluginWorkspaceSettings`, `PluginLocalDB` —
plus the `Files`/`XML`/`Tools`/`Dialog` functions bound bare (`downloadFile`, `promptPopover`,
`notifySuccess`, `getContext`, `saveAll`, …). All calls are async.
Migrations:
- Delete destructures like `const {Pages} = zzTakeoff.commands` (the bare names already exist).
- Any aliased command root — `zz.commands`, `z.commands`, `host.commands`, `bridge.commands`,
`sn?.commands ?? {}`, `(b && b.commands) ? b.commands : null`, etc. — must resolve to the window
globals instead (e.g. `zz ? window : null`, or a curated object of the needed
`window.<Namespace>` entries). Hunt for these under ANY variable name; a root that silently
becomes `{}`/`null` puts the component into a dead offline mode.
- Dotted-name dispatchers (`commands["Takeoffs.list"]`, `cmd('Pages.list')`) must split the pa
and resolve `window[namespace][method]`.
- Availability probes are unreliable: namespace globals are lazy proxies, so ANY method name —
including typos and removed commands — is truthy and callable, and only rejects with
"Unknown command" when invoked. Fix probe NAMES (e.g. `'listByPage'` → `'list'`), treat an
"Unknown command" rejection as a typo, and never feature-probe by calling entity commands
(a probing insert creates real records).
- `zzTakeoff.darkMode`, `zzTakeoff.post/on`, and `window.zzTakeoff` presence checks still work
## 8. Insert behavior (usually no code change)
Inserts honor an explicit `order` and default to the next slot under the parent (folders sort
first). Typed non-folder takeoffs get an auto color unless `properties.color` is set.
`DrawObjects.insert` defaults `layerId` to the current layer and it must match the linked
takeoff's layer. `startTakeoff` with `properties` auto-assigns nothing — include
`properties.color` or the drawing is invisible. Layers/Libraries/Lists `insert` accepts `{name}`
or `{properties: {name: {value}}}`.
## 9. Discipline
- Fix comments/docs that describe removed APIs, but leave user-visible strings unless misleading.
- Do not touch identifiers that merely LOOK like the old API (a plugin's own `this.commands`
array, an XLSX parser's `l.name`, `canvas.getContext('2d')`).
- Preserve line endings (some files are CRLF) and minified formatting where present.
- After editing, re-scan for: `.commands`, `Storage`/`PluginLocalStorage`, `listByPage`,
`getChildren`/`getDescendants`/`getCutouts`, `clearSelectedDrawObjects`, positional
`DrawObjects.list(x)`/`Lists.list(x)`, single-object `X.update({...Id...})`, capitalized
`.button` comparisons, and flat `.name`/`.id` reads on layer/library/list records — including
inside strings, probes, fallback chains, and comments.