` 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 path 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 behaviorInserts 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 ONLY`{properties: {name: {value}}}` (`Lists.insert` also takes root `type`) — the old flat `{name}`is stripped and throws "name is required", so rewrite every `X.insert({name: ...})` for thesethree to the properties form.## 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...})`/`Records.delete({...})`, flat `{name}` in Layers/Libraries/Lists insert/update payloads, direct iteration of `list` results without `.records`, root-level `.more`/`.skip` reads on `Records.list` results, 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 path 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 behaviorInserts 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 ONLY`{properties: {name: {value}}}` (`Lists.insert` also takes root `type`) — the old flat `{name}`is stripped and throws "name is required", so rewrite every `X.insert({name: ...})` for thesethree to the properties form.## 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...})`/`Records.delete({...})`, flat `{name}` in Layers/Libraries/Lists insert/update payloads, direct iteration of `list` results without `.records`, root-level `.more`/`.skip` reads on `Records.list` results, capitalized `.button` comparisons, and flat `.name`/`.id` reads on layer/library/list records — including inside strings, probes, fallback chains, and comments."/>
Back to zzTakeoff Community Channel LogoDocumentation
42d 19h

Scripting & UI Components Migration Prompt - TEST Server

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, {pageId, parentId, order, properties})` (`pageId` is a new shortcut for the page property). There is NO top-level `name` — a name change must go through `properties: {name: {value}}`. - `Templates.update({templateId, ...})` → `Templates.update(templateId, {properties, parentId, order, toolMode})` - `Layers.update({layerId, name})` → `Layers.update(layerId, {properties: {name: {value}}})`; same pattern for `Libraries.update(libraryId, {properties: {name: {value}}})` and `Lists.update(listId, {properties: {name: {value}}})`. The flat `name` field is NOT accepted anywhere on these three — it is stripped and the call throws "name is required". Every flat `{name: ...}` in an update OR insert payload for Layers/Libraries/Lists must be rewritten to `{properties: {name: {value: ...}}}`. - `Records.update({recordId, listId, set, unset})` → `Records.update(selector, updates)` — the selector is the recordId string, or `{recordId, listId}` when targeting a list other than the current one. `listId` goes ON THE SELECTOR, never inside updates (an updates `listId` is written to the record as a field — a bug). Updates accept `{type, order, parentId, properties, toolMode, kind}`. 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` is now MERGED with the existing record, not replaced. 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 `(query, options)` and return a pagination envelope The query is 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. **Return shape changed: every `list`/`listDescendants` result is now `{records, pagination: {more, skip, limit}}`, not an array.** Every place migrated code iterates, indexes, spreads, measures (`.length`), or `Array.isArray`-checks a list result must read `.records` instead. To page, pass `{limit, skip}` in options and feed the returned `pagination.skip` back in while `more` is true. Specific migrations: - `DrawObjects.list(takeoffId)` (positional) → `DrawObjects.list({takeoffId})` - `DrawObjects.listByPage(pageId)` is REMOVED → `DrawObjects.list({pageId})`. It no longer defaults 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'})` - `Records.list({listId, search, skip, limit})` → `Records.list({listId}, {search, limit, skip})` — the query supports ONLY `listId` (defaults to the current list); any other query key now throws. Its return changed from `{records, more, skip}` to `{records, pagination: {more, skip, limit}}` — rewrite every root-level `.more`/`.skip` read to `.pagination.more`/`.pagination.skip`. - `Records.delete({recordId, listId})` (config object) → positional `Records.delete(recordIds, listId)` (`recordIds` is an ID or array of IDs; `listId` optional, defaults to the current list). - `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, formatted}}}` 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.name.value)`). 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 words (`"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 path 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 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 ONLY `{properties: {name: {value}}}` (`Lists.insert` also takes root `type`) — the old flat `{name}` is stripped and throws "name is required", so rewrite every `X.insert({name: ...})` for these three to the properties form. ## 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...})`/`Records.delete({...})`, flat `{name}` in Layers/Libraries/Lists insert/update payloads, direct iteration of `list` results without `.records`, root-level `.more`/`.skip` reads on `Records.list` results, capitalized `.button` comparisons, and flat `.name`/`.id` reads on layer/library/list records — including inside strings, probes, fallback chains, and comments.

You must be logged in to post replies. If you don't have an account you can signup here.