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 itsrevision. Withscope(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 aname, the list of system samples, each with its number of screens and the element types it uses; with aname, 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 withexotool_copy_project.exotool_save— save and compile (one step). Pass the ENTIRE editedviewdiagramtree and therevisionthe load gave. ReturnsOKor per-elementcompiler_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 (projectalone).exotool_validate_activity— check one piece of activity code, on its own or in a project's diagram (project, andelementto 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); passview_classfor fullvalues. See Presets (in theelementsreference,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) andelements(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. Withproject(one of the user's own) the address opens that project's diagram in the editor; the results ofexotool_create_project,exotool_copy_projectandexotool_savecarry it, and it is passed on to the user with the preview address. There is no MCP tool for either (exotool_delete_projectandexotool_create_resourcewere 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.databasealso takesheaporfiles; the other roles takenone. 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 chosenname(no extension); Exotool makes sized versions, stored as JPEG (no transparency). Reference it from atemplateby its path, e.g.img/<name>.jpg(see static images (in theelementsreference,exotool_docs("elements"))).exotool_upload_file— add an arbitrary asset file (name+ broad type list), stored asimg/<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
- Locate —
exotool_list_projectsto confirm the project name (and that the server is reachable). For a new app, preferexotool_copy_projectfrom a sample (e.g.element_gallery);exotool_create_projectgives an empty seed. - Load —
exotool_get_diagramto get the full tree. - 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
viewdiagramJSON. Either way keep every existing field you aren't intentionally changing (geometry,_params,action,hooks,presets, the root metadata block). - Text content lives in a
contentblocktemplate. - A new screen is a
page/popup/panelunderpool1.elements. - Navigation = a
link(orback_link/symlink) wired by asequence_connectorwhosefrom= source element key andto= target screen key. Each "always" connector also needs a roottranslated_code[<connKey>] = { condition: false }entry. - A
symlinkis a teleport that works ONLY as a both-ends-wired pair: asource → Ⓐconnector AND aⒶ → targetconnector, with the twosymlinks sharing the sametitle. A badge with nothing feeding in (or nothing leaving) is a dangling half-pair that navigates nowhere — see theelementsreference (exotool_docs("elements")). - A
link/back_linkmay NOT be a direct child of apage/panel/popup— it must be nested in a link container (navbar,bottom_toolbar, orlinkmenu). 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 theelementsreference,exotool_docs("elements")). - An
apinode (pool-level, shown as>>) publishes a server-side API endpoint: wire its single output connector into theclient_activitythat does the work and list itsoutput_vars. The compiler generates anAPI_<identifier>handler (callable via thejsonp_dgapp_responderprotocol) and documents it — with JavaScript and Python call stubs — in the app'sbackend.php. Seeapi(in theelementsreference,exotool_docs("elements")). - Reuse the seed's existing keys/connectors where possible rather than inventing new ones.
- Sheets — a diagram can be split across sheets (the editor shows one at a
time). An element's sheet is set by an integer
sectionfield (absent/0= main sheet); all elements stay in the singlepool1.elementsmap regardless. Connections that cross sheets can't be drawn directly — they use asymlinkpair, each badge carrying thesectionof the element it links to and sharing atitle. A sheet also acts as a call-scope boundary: a call to an activity on another sheet is scoped by that sheet just as asubViewwould scope it. See Sheets (in theelementsreference,exotool_docs("elements")) and Calls, returns & scopes (in theelementsreference,exotool_docs("elements")). - 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 apage/popupsettles 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_connectorleaves 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 itsy, a 40-high activity 29 below (21.67 and 36.33 with two connectors). Links in anavbar/bottom_toolbarattach at that bar's right edge; links in alinkmenuorswipeoutsat 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
orderingorder, toolbar group), each about 17 below the previous one (a navbar at y 26, height 20, is followed at y 63). Leave a page'swidthandheightto 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
xso that the midpoint misses any box that lies between:target.xmust be beyond2 * box.x - source.rightto 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
symlinkbadge 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_pathvalues removed from moved connectors. - Connectors auto-route — do NOT hand-author
line_path. Clear any staleline_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
sectionof 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 straysectionvalues inherited when copying elements. subViewgeometry 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).
- No overlaps, and boxes are drawn bigger than stored. The editor draws a box
18 wider and 18 taller than its
- Authoring an unfamiliar element = copy a known-good one.
exotool_get_diagramon 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/_paramsthe compiler expects. - Save + compile —
exotool_patch_diagramwith the operations, orexotool_savewith the entire tree; pass therevisionwith 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. - Confirm — report the compile result, and give the preview URL when useful
(
exotool_get_preview_url, a shareable live-preview link). Optionallyexotool_get_diagramagain 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:/titleis its title,/elements/<childKey>/templatea child's template. - A path that begins with
/poolis 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_savevalidates only that the rootclassnameisviewdiagramand the root carriesinternal_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 brokenfrom/toreference comes back as acompiler_errorwith a per-elementerrors_list, not as a save error. - On a
compiler_error: read theerrors_list, fix the offending element in the tree, and re-save. Don't guess classnames — cross-check theelementsreference (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_diagramwithdiagram(the ENTIRE tree) andprojectchecks what you are about to save; withprojectalone 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_activitychecks one piece of activity code. Withprojectthe code is compiled inside that project's diagram, so its cloud objects, variables and subroutines are known; withelementtoo, 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_errorslists 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.