System overview Using the editor The Exotool language HTML templates The diagram The element catalogue

Exotool diagrams — authoring reference

Overview

Exotool is an RMAD platform: a visual diagram compiles to a PWA (Framework7-based front end). An app is one diagram tree — viewdiagram (root) → viewpool (the app) → pages / popups / panels wired together by sequence_connectors.

You work on diagrams through the exotool MCP server. A diagram is a document with a revision: you load it, then change it either by sending a few JSON Patch operations (exotool_patch_diagram, for a small change) or by sending the whole edited tree back (exotool_save, for a large change or a new app). The tools:

  • exotool_list_projects — list the logged-in user's projects, each with its preview-install URL.
  • exotool_get_preview_url — get a project's preview URL (where its compiled PWA can be opened/previewed; on production a per-user Docker instance).
  • exotool_create_project — create a project seeded with a Home page + one extra page; returns the full diagram JSON.
  • exotool_copy_project — copy an existing project into a NEW own project. The source may be your own project OR a system sample (e.g. element_gallery) — handy for starting from a sample/template rather than building from scratch.
  • exotool_get_diagram — load a project's diagram (outline + full JSON) and its revision. With scope (an element key, sheet:<n>, or a list of these) only that part is returned, each part with its path for a patch; use it to keep a large diagram out of the conversation.
  • exotool_get_example — worked examples: without a name, the list of system samples, each with its number of screens and the element types it uses; with a name, that sample's complete diagram. Use it to see how an element type you have not authored before is written, and copy that shape. Read-only; to build on a sample, copy it with exotool_copy_project.
  • exotool_save — save and compile (one step). Pass the ENTIRE edited viewdiagram tree and the revision the load gave. Returns OK or per-element compiler_errors, with the new revision and a summary of what changed.
  • exotool_patch_diagram — change part of a diagram with JSON Patch operations (RFC 6902: add, remove, replace, move, copy, test), then save and compile, in one step. Only the operations are sent, which is many times faster than sending the diagram back. See Patching below.
  • exotool_undo_diagram — put a project's design back to what it was before its last save (diagram, settings and uploaded files). Each undo goes one save further back; the newest four automatic backups are kept, so at most four.
  • exotool_validate_diagram — compile a diagram for its errors and save nothing: the tree you are about to save (diagram), or the saved one (project alone).
  • exotool_validate_activity — check one piece of activity code, on its own or in a project's diagram (project, and element to check it in place of an activity's code).
  • exotool_get_build_errors — the compiler errors of a project as it is saved, each with the element's key and path. See Validation below.
  • exotool_list_presets — list a project's presets (named parameter-value bundles per element class); pass view_class for full values. See Presets (in the elements reference, exotool_docs("elements")).
  • exotool_store_preset — create / store (overwrite) / delete a preset; e.g. copy one into a new user preset.
  • exotool_docs — return one of the Exotool reference documents: overview, editor, language, templates, diagrams (this document) and elements (the element catalogue). Served from files on the server's hosts.
  • exotool_get_editor_url — the address of the Exotool editor, to give the user for what is done only there: creating or changing a resource, deleting a project. With project (one of the user's own) the address opens that project's diagram in the editor; the results of exotool_create_project, exotool_copy_project and exotool_save carry it, and it is passed on to the user with the preview address. There is no MCP tool for either (exotool_delete_project and exotool_create_resource were retired on 2026-09-29; the server refuses them).

Resources (account-level, reusable external-service accounts — FTP/MySQL/SMTP/ PayPal…, each a guid; a project "uses" one by role):

  • exotool_list_resources — list the user's resources (guid/category/sub_category/ itemtitle; never secrets).
  • Resources are created and maintained only in the editor, because a resource holds a secret that must not pass through the conversation. When the user needs one that does not exist yet, call exotool_get_editor_url, ask the user to add it on the editor's Resources page, then list again to find its guid.
  • exotool_set_project_resources — point a project's roles (database, email_server, ecommerce_server, web_server) at resource guids and recompile so the association takes effect. Own projects only. Every value is checked first (a guid must be one of the user's own resources, of the category the role needs: Database, E-mail Server, E-Commerce Server, Web Server); an invalid value rejects the whole call and nothing is saved. database also takes heap or files; the other roles take none. A role you omit is unchanged.

Asset uploads (separate from the diagram — they place files in the project, not elements in the tree; all take the file as data_base64 (base64-encoded content, plus fname for its type where the tool asks for it) and are scoped to the user's own projects):

  • exotool_upload_app_icon — set the app icon from one image; Exotool generates every PWA / desktop-tab / iOS icon size. svg/jpg/jpeg/gif/png.
  • exotool_upload_app_background — set the app background image. svg/jpg/jpeg/gif/png.
  • exotool_upload_image — add an image asset under a chosen name (no extension); Exotool makes sized versions, stored as JPEG (no transparency). Reference it from a template by its path, e.g. img/<name>.jpg (see static images (in the elements reference, exotool_docs("elements"))).
  • exotool_upload_file — add an arbitrary asset file (name + broad type list), stored as img/<name>.<ext>, exactly as it arrives: the way to keep a PNG transparent, or an SVG an SVG.

The three image uploads also take svg: the image as SVG text, in place of data_base64. An icon or image that you draw is sent that way: it is the smallest thing to send, and Exotool makes every size from it (an SVG icon keeps its transparency and is padded to a square, not stretched). An SVG holds no DOCTYPE or ENTITY declaration, no script and no reference to another file, and is under 1 MB. Sending a file is slow (about 20 seconds for each kilobyte), so upload only a small picture yourself. For a photograph, a video or any large file (up to 8 MB), the user uploads it on Exotool's upload page: say what the link opens and ask, then call exotool_get_editor_url with open: "upload", the project, and upload: {slot, name} (slot is app_icon, app_background, image, file or library_file; the last three need name, the name the diagram will refer to). Give the link at once, exactly as returned: it works once, must be opened within two minutes, and lets whoever opens it upload one file into that one place and nothing else. When the user says it is done, call exotool_get_editor_url with the handoff id the link came with, and use the file only when the state is done. A file by its link. Where the server offers it, the four tools that take a file's data (exotool_upload_app_icon, exotool_upload_app_background, exotool_upload_image, exotool_upload_file) take url in its place: the https address of the file, a direct or pre-signed download link. Exotool downloads it (up to 8 MB) and keeps its own copy, so prefer it to the upload page whenever you have such a link: a Canva export, an Unsplash image, a Dropbox link ending ?dl=1, a Cloudinary address. Pass a temporary link promptly. exotool_upload_image and exotool_upload_file also take urls, a list of up to 20, stored as <name>_1, <name>_2, ... in the order given; one that fails is reported by itself. Give exactly one of data_base64, svg, url and urls. A share link that opens a web page (Google Drive, OneDrive, Dropbox without ?dl=1) is refused as "a web page and not a file": use the upload page for those. Rights: go ahead when the file is the user's own content from a connector they have connected, or comes through its connector from a freely licensed source (Unsplash); for a link the user pasted, one found on the web, a stock preview or a file someone else shared, first tell the user, once in a conversation, that they are responsible for having the right to copy and use it, and go on when they confirm. Connectors: when the user asks for a background, an icon or an image and you have no connector to an image or design service (Canva, Figma, Adobe Express, Unsplash, Dropbox), say once that such connectors exist and work with Exotool, and carry on by the means you have. - exotool_upload_library_file — upload a php/js backend source file (under a chosen name) for a lib element.

These don't change the diagram tree, so they need no exotool_save; the change is immediate. After changing the icon/background, suggest a rebuild/preview to see it.

Element types and their fields are catalogued in the elements reference (exotool_docs("elements")). Read it before authoring unfamiliar elements.

When to use this skill

Use this skill when the user wants to:

  • Create a new Exotool project/app, or add/modify pages, popups, panels, navbars, links, menus, toolbars, or content in an existing one.
  • Wire navigation between screens (links/symlinks → connectors).
  • Inspect, validate, or compile an Exotool diagram.
  • Understand the diagram element model or a specific classname.

Trigger on mentions of "exotool", "exotool diagram/app/project", or diagram-to-PWA authoring.

Workflow

  1. Locate — exotool_list_projects to confirm the project name (and that the server is reachable). For a new app, prefer exotool_copy_project from a sample (e.g. element_gallery); exotool_create_project gives an empty seed.
  2. Load — exotool_get_diagram to get the full tree.
  3. Edit — for a small change, write JSON Patch operations against the loaded diagram (step 4); for a large change or a new app, apply the changes to a complete copy of the viewdiagram JSON. Either way keep every existing field you aren't intentionally changing (geometry, _params, action, hooks, presets, the root metadata block).
  4. Text content lives in a contentblock template.
  5. A new screen is a page/popup/panel under pool1.elements.
  6. Navigation = a link (or back_link/symlink) wired by a sequence_connector whose from = source element key and to = target screen key. Each "always" connector also needs a root translated_code[<connKey>] = { condition: false } entry.
  7. A symlink is a teleport that works ONLY as a both-ends-wired pair: a source → Ⓐ connector AND a Ⓐ → target connector, with the two symlinks sharing the same title. A badge with nothing feeding in (or nothing leaving) is a dangling half-pair that navigates nowhere — see the elements reference (exotool_docs("elements")).
  8. A link/back_link may NOT be a direct child of a page/panel/popup — it must be nested in a link container (navbar, bottom_toolbar, or linkmenu). Only pagebody-type elements are valid direct children. The compiler won't flag a bad nesting; it just renders nothing. See the containment rules (in the elements reference, exotool_docs("elements")).
  9. An api node (pool-level, shown as >>) publishes a server-side API endpoint: wire its single output connector into the client_activity that does the work and list its output_vars. The compiler generates an API_<identifier> handler (callable via the jsonp_dgapp_responder protocol) and documents it — with JavaScript and Python call stubs — in the app's backend.php. See api (in the elements reference, exotool_docs("elements")).
  10. Reuse the seed's existing keys/connectors where possible rather than inventing new ones.
  11. Sheets — a diagram can be split across sheets (the editor shows one at a time). An element's sheet is set by an integer section field (absent/0 = main sheet); all elements stay in the single pool1.elements map regardless. Connections that cross sheets can't be drawn directly — they use a symlink pair, each badge carrying the section of the element it links to and sharing a title. A sheet also acts as a call-scope boundary: a call to an activity on another sheet is scoped by that sheet just as a subView would scope it. See Sheets (in the elements reference, exotool_docs("elements")) and Calls, returns & scopes (in the elements reference, exotool_docs("elements")).
  12. Layout & readability — geometry (x/y/size) is cosmetic to the compiler, but a human reads the diagram on the canvas, so lay it out cleanly:
    • No overlaps, and boxes are drawn bigger than stored. The editor draws a box 18 wider and 18 taller than its width/height (symlinks and decisions are drawn as stored), and a page/popup settles taller still once its children are laid out. Compare drawn boxes, and keep 20 or more between them.
    • Anchor at the top-left of each sheet with a small margin — enough room for auto-routed connectors, not a big empty gutter — so elements are visible the instant a sheet opens.
    • Lay flows left-to-right, with a clear gap of 60–80. A sequence_connector leaves the right edge of its source and enters the left edge of its target. Under about 35 of clear gap the line is drawn as a loop around both elements; a target to the left forces the same loop.
    • Line up the attachment points, not the tops. A single connector attaches at the middle of the edge; several spread evenly, ordered by the other end's y. A symlink attaches 17.5 below its y, a 40-high activity 29 below (21.67 and 36.33 with two connectors). Links in a navbar/bottom_toolbar attach at that bar's right edge; links in a linkmenu or swipeouts at their own. Matching these gives a straight line; a mismatch gives a kink.
    • Line up with the ITEM a connector leaves from or arrives at, not with the page. A connector leaves the right edge of its link, list, form or button, at that item's height; several entering one side of a box arrive at 1/2, at 1/3 and 2/3, at 1/4, 1/2 and 3/4 of that side, in the order of the other ends' positions. Put an incoming symlink or activity level with ITS slot.
    • Lay screens in ROWS that follow the user's flow left to right (splash → load → list → detail → form on one row; sign-in and account on another) and put a decision activity that several rows reach between the rows. Leave 60 or more between screens of a row for connectors and their diamonds.
    • An activity goes to the RIGHT of what triggers it, at that element's height — never below it. A conditional branch goes to the right of its diamond, never under it.
    • Inside a page, children stack top to bottom (navbar group, body items in ordering order, toolbar group), each about 17 below the previous one (a navbar at y 26, height 20, is followed at y 63). Leave a page's width and height to the editor, which recomputes them when the person touches the diagram; a height you must set is just tall enough for the last child.
    • Footer links such as a Home icon go in a bottom_toolbar, not the navbar; their connector leaves at the toolbar's height, so park that symlink level with the toolbar. A Home link goes to the first real screen after the splash, not to the splash, or its delay replays at every tap.
    • The vertical leg of a forward connector lands at the x-MIDPOINT between its two ends (ends closer than ~70 use a short stub instead). So keep the midpoint column empty between the two heights, and choose a target's x so that the midpoint misses any box that lies between: target.x must be beyond 2 * box.x - source.right to pass left of a box, with 15 or more to spare.
    • Targets of the links in one container stack in the links' order. Two links in a menu whose targets sit in the opposite vertical order cross right outside the page, wherever else the targets go.
    • A tall page sits so the link that opens it is level with the page's centre: a single incoming connector attaches at half the drawn height, so page.y = link's y - drawn height / 2. When a page moves, every badge and activity keyed to its links moves with it by the same amount.
    • Slots follow the sources' heights, and that can force a crossing. Two connectors into one target: the lower source takes the lower slot, so the line into the upper slot runs between the page and any activity placed below it. An activity fed from a page that shares the page's target goes ABOVE the page's other link, never below the page; moving it sideways cannot clear the crossing.
    • A branch badge beside a decision sits above or below the other branches' lines, never level with the line to the next branch, or that line runs through it.
    • Several connectors out of one element attach in the order of their targets' heights, whatever their order in the pool (the editor once gave the upper attachment to the connector it created last; fixed 2026-10-14, roadmap K28).
    • Park a single-use symlink badge beside its element, level with the attachment point and 60–80 away — that yields a short straight arrow. Fan several badges out of one source vertically, about 60 apart. Use a symlink pair for cross-sheet jumps, and for any connector that would otherwise run through another element.
    • The two sides of a symlink pair sit differently. The badge an element links TO goes just right of that item at the item's y (40 of gap, 60 when the connector carries a condition, for the diamond). The badge that links OUT to a target goes just LEFT of the target, level with its arrival slot — also when the target already has another incoming connector, so both arrive from the left; never above or below it. Several sources may share one target badge (every page's "H" into the one "H" beside the home page). If a target at the left edge leaves no room, shift the whole diagram right (+60 on every top-level x) rather than crowding the badge.
    • Before saving a layout, check: children stacked in order inside every page box; every connector straight or nearly; each outgoing badge right of its source at the same y with room for a diamond; each incoming badge left of its target at the slot's y; nothing overlapping or crowding; stale line_path values removed from moved connectors.
    • Connectors auto-route — do NOT hand-author line_path. Clear any stale line_path (e.g. on harvested or moved elements) so the router redraws to the current positions.
    • Keep a sheet to about one laptop screen, 1600 by 900, and SPLIT a larger diagram into sheets rather than spreading it: people do not always have a large screen. Split by the user's flow, one band per sheet (sign-in and role choice; the rider's screens; the driver's; the account), and bridge each connection that crosses a sheet with a symlink pair, the badge on each side carrying the section of the element it links to. A hub reached from several sheets (the main page, Log out) gets one incoming badge per label beside it.
    • Keep sheets (section) contiguous — no empty sheets. Strip stray section values inherited when copying elements.
    • subView geometry is SEMANTIC, not cosmetic: a subView scopes (covers) any element whose box it fully encloses. Size/place it deliberately and check it doesn't accidentally swallow a neighbour (the one geometry check that changes behaviour, not just looks).
  13. Authoring an unfamiliar element = copy a known-good one. exotool_get_diagram on a project that already uses it, lift that element's subtree, re-key it (fresh unique keys + identifiers; remember references are by KEY — from/to, hooks, error_hook_action.hook_target, covered_elements), and splice it in. Real compiling instances carry the exact envelope/_params the compiler expects.
  14. Save + compile — exotool_patch_diagram with the operations, or exotool_save with the entire tree; pass the revision with either. This compiles in one step, and re-installs the (production-sanitized) PWA to the project's live preview — so the shareable preview URL then reflects this build.
  15. Confirm — report the compile result, and give the preview URL when useful (exotool_get_preview_url, a shareable live-preview link). Optionally exotool_get_diagram again to verify the round-trip.

For anything beyond a trivial edit, confirm the target project before writing — saving compiles and overwrites the live project. Every save, patch and undo first backs up the design it replaces, and exotool_undo_diagram takes the last save back (see Revisions, patching and undo below). Never save to a project you did not create in this conversation without the user's confirmation. Deleting a project is done in the editor (exotool_get_editor_url), not through MCP. The editor's own projects (dgApp, dgApp2) are refused by every tool, for every caller.

Revisions, patching and undo

Revisions. exotool_get_diagram gives the diagram's revision (a number that changes with every save, whoever makes it). Pass it back with every exotool_save, exotool_patch_diagram and exotool_undo_diagram: it is required, and a save without one is refused. A project you have just created or copied gives its revision in the result. A stale revision is refused and nothing is written: the user (or another conversation) has changed the diagram meanwhile. Load again, re-apply your change to what you now see, and send it with the new revision. Each result gives the new revision; use that one for your next call, without loading again.

Patching. ops is a list of operations, each with a path that is a JSON Pointer (/ separates the steps; in a key, ~ is written ~0 and / is ~1):

  • Without scope, a path starts at the root of the diagram: /elements/pool1/elements/<pageKey>/title.
  • With scope (one element key), a path is relative to that element: /title is its title, /elements/<childKey>/template a child's template.
  • A path that begins with /pool is relative to the app's pool whatever the scope: /pool/elements/<key> is any screen or connector.

With scope set to a content block's key, this changes its text and adds a screen:

[{"op": "replace", "path": "/template", "value": "<p>Welcome back</p>"},
 {"op": "add", "path": "/pool/elements/page_about", "value": {"classname": "page", "...": "..."}}]

The operations are applied all or nothing: if one fails, the result names it and nothing is saved. add on a member that exists replaces it; a new member goes last. The rules of the tree hold for a patch exactly as for a save: unique keys and identifiers, references by key, and a new "always" connector still needs its root translated_code[<connKey>] = { condition: false } entry: a second add, at /translated_code/<connKey>. The root is not reachable under a scope, so send a patch that adds a connector without one. A scoped load gives each part's path, ready to use. Use exotool_save instead when the change touches much of the tree.

A failed compile is saved all the same, for a save and for a patch: the compiler errors come back in the result. Correct them with another patch, or undo.

Undo. exotool_undo_diagram restores the design as it was before the last save, compiles it, and returns the new revision and what changed. Use it when a save produced something the user did not want, and say so to the user before calling it on a project you did not just save. The design as it stood before the undo is itself backed up, and can be had back from the editor's Backups list.

Writing activity code & templates

Filling an element's code / do_on_action / do_on_entry (the activity language) or its template / wrapper_template (the {{ }} templates) means writing two sub-languages. High-value gotchas (full refs: exotool_docs("language") and exotool_docs("templates"); built-ins: section 13 of the language reference):

Activity language - No statement terminator (;); binary operators have NO precedence and associate RIGHT — parenthesize when grouping matters. Operators: + - * / && || <= < >= > == != & |, prefix - !. - if (cond) { … } else { … } — condition must be parenthesized; a bare body is a single statement (use { } for more). - always <simpleVar> = <expr> is a client-side reactive binding — never feed it a secret; target must be a simple variable; a bare-expression form requires a { } block. - Location is automatic; override with declare_location <var> client|server, if_on_client { } / if_on_server { }, or escape blocks PHP< … >PHP / JS< … >JS / async JS<P … >JS (surrounding scope via $args; the block's return flows back). - subroutine_def name() { … } — args are ignored (not yet implemented). declare_type <n> <type> (object with {…} = value; cloudobject with {…} = persistent). defmacro name(…) { … } = textual pre-compile substitution.

Templates (Handlebars/Template7 + exotool extensions; the output can itself BE exotool code) - {{parameters.x}} = a property-panel value (an undeclared one auto-adds a text control); {{@image x}} / {{@file x}} = asset controls; {{@element x}} = reference another element; {{#Declare-Parameter name:"…" type:"…"}} declares a control and renders nothing. - Blocks {{#each}} / {{#if}}…{{else}} / {{#ifeq a b}} / {{#with}}; specials {{@index}} {{@key}}; else is reserved. Template helpers (count sum max min any not intern dateString…) are a SEPARATE set from the activity-language functions (don't conflate same-named ones).

Validation

  • exotool_save validates only that the root classname is viewdiagram and the root carries internal_idctr / static_assets / translated_code / user_var_names (auto-added if missing). It does NOT validate element vocabulary.
  • The real check is the compiler (save == compile). A bad classname, malformed template, or broken from/to reference comes back as a compiler_error with a per-element errors_list, not as a save error.
  • On a compiler_error: read the errors_list, fix the offending element in the tree, and re-save. Don't guess classnames — cross-check the elements reference (exotool_docs("elements")).
  • A failed compile still PERSISTS the saved source. A bad element stays in the tree on the next exotool_get_diagram — so to recover, reload, delete the offending element, and re-save; don't just stop re-adding it.
  • Success looks like: Saved and compiled "<project>" successfully (OK).

Checking without saving. Three tools compile and change nothing in the project, neither the saved diagram nor the compiled app nor its preview:

  • exotool_validate_diagram with diagram (the ENTIRE tree) and project checks what you are about to save; with project alone it checks what is saved. scope (an element key, sheet:<n>, or a list) limits the errors reported to those parts; the whole diagram is compiled all the same.
  • exotool_validate_activity checks one piece of activity code. With project the code is compiled inside that project's diagram, so its cloud objects, variables and subroutines are known; with element too, it stands in place of that activity's code. It reports the errors of this code only, and says how many the rest of the diagram has.
  • exotool_get_build_errors lists the errors of a project as it stands.

Each error names the element's key and gives its path, in the form a patch takes. They check what the compiler checks, no more: a link nested in the wrong parent, or an element laid out off the screen, passes. Each call counts against the hourly compile limit, as a save does. Use exotool_validate_diagram before a large save and exotool_validate_activity for code you are unsure of; for a small change the patch itself reports the same errors.

Three cautions seen in practice. In a template, a bare name is looked up first among the element's OWN parameters and special names (title, identifier, classes, attributes, thisobject, thispage, subs, links, …) and only then in the data: {{title}} is always the element's title. Name data fields so they do not collide (cat_name, st_name), or reach one explicitly as {{this.title}}. In activity code a variable becomes Env.<name> on the client, so do not name one like the runtime's own (user_name, user_id, logged_in, sessionid, forms, action, …), like an element key, <key>_error, or with a leading __; a subroutine named like a built-in function replaces it (the language reference, "Reserved names"). Inside a JS< … >JS data literal, a straight apostrophe (') can end the block's string early: use a curly one (’), and avoid generic names such as states that may meet a built-in. When the compiler refuses a write to a server-side variable from client code, put declare_location <var> client at the top of that activity. Build behaviour with elements (a splash is the page with identifier home plus a page → activity connector that waits, then opens the main page), not with custom JavaScript on the pool.

An activity's code and its debugger copy. The root's translated_code holds, for an activity the editor has saved, a copy of its code with the debugger's statements put in. Change the element's code only: the server drops a copy that no longer matches, and the editor makes it again at its next save. Never edit translated_code[<activityKey>] by hand.