Documentation
Table of Contents
1. Introduction
Actually Integrated is a browser-based builder for HMI-style pages and dashboards: drag widgets onto a canvas, wire them to live data, and export the result as a single self-contained page. There's no account, no install, and no server — everything you build lives in your browser until you export it.
This page describes every part of the builder in detail. Use the contents on the left (or the list below on mobile) to jump to what you need, or read it straight through if you're starting from scratch.
New here? The ? icon inside the builder (top bar, next to AI Context) opens a short interactive walkthrough that covers the same basics hands-on, in the app itself — this page is the deeper reference to come back to afterward. Prefer to see it built first? The Examples page walks through building and exporting a real dashboard, with real screenshots at every step. Want the builder as its own installed app — its own window and icon, and working offline once you've opened it online at least once? See the App page for install steps on Chrome, Edge, and Safari.
2. The Builder Tab
Where you place and arrange widgets on the canvas. This is the tab you land on when you open the builder.
Canvas basics
- Add a widget by clicking a template in the left Templates panel — templates are click-to-add, not drag-and-drop.
- Move a widget by dragging it; resize using the corner and bottom/right edge handles (there are no top/left edge handles — reach those sides via a corner).
- Select a widget by clicking it. Clicking additional widgets adds them to the selection (selection is additive, not toggled); clicking empty canvas clears it. The first widget you select is the "primary" one (its handles are a different color) and anchors the align/distribute toolbar that appears once you have 2 or more selected.
- Delete a widget by dragging it fully off the canvas edge — you'll be asked to confirm before it's removed. Canceling snaps it back to where the drag started.
- Grid snapping (toggle with
Ctrl+G) is a builder-only alignment aid, from 1–200px. It has no effect on your exported page's coordinates. - Undo/redo with
Ctrl+Zcovers widget moves and resizes on the current page.
Template library
The left panel's Library tab has three sub-tabs:
- JavaScript — code-driven widgets: text, shapes, buttons, counters, charts, forms, clocks, a live variable inspector, and more. These run directly on the page and can read and write global variables.
- HTML — markup-driven widgets (a landing-page block, a font manager, a dashboard shell, an embeddable web page viewer). These render in an isolated preview while you're building, and can read variables for things like theming.
- Local — widgets you've previously saved yourself with a widget's own "Save as Template" button (see below), loaded back in from a
.jsonfile.
Use the search box at the top of the Library panel to filter the catalog by name as it grows — new templates are added over time, so this list is not meant to be exhaustive here.
Global colors & fonts (Styles tab)
Next to Library is a Styles tab for two things that are shared across your whole project:
- Global colors — reusable color values that widgets can bind to instead of a hardcoded hex value, so changing one color here updates every widget using it. When you select one or more widgets on the canvas, this panel's add-widget buttons are replaced with Colour, Text, Font, and Events tools that act on the selected widget(s) — the first three scan for hardcoded values of that kind, Events composes and inserts trigger/action code (see below). The same four tools are also always available with nothing selected, from Tools > Style Tools in the menu bar (acting on every widget on the current page), and from the Editor tab's own toolbar (acting on whichever widget you have open there).
- Fonts — upload a font file and it becomes available to every widget on every page, immediately, without re-uploading it per widget.
Layers & window properties (Tools panel)
The right "Tools" panel covers everything about an individual widget or the page as a whole:
- Layers — every widget sits on a numbered layer (higher = drawn on top). Toggle a layer's visibility, rename it, add or remove layers, and set which layer new widgets land on. Hiding a layer doesn't just hide it visually — a JavaScript widget on a hidden layer stops running entirely (its timers pause) until the layer is shown again. Deleting a layer moves its widgets to layer 1 rather than deleting them; you'll be warned first if that layer isn't empty.
- Properties — with a widget selected, this is the main place to edit it: its Name and Description, numeric Position (X/Y) and Size (Width/Height), Rotation, a row of action buttons (move it up/down a layer, Save as Template, jump to the Editor tab, or Delete it), and — for JavaScript and HTML widgets — a full code editor at the bottom of the panel, described below.
- Background & app name — with nothing selected, this panel shows your Page Name, App Name (used as the browser tab title and the exported file name), and Background Settings (canvas width/height/color) — plus a project-wide Page JS editor for helper code shared by every widget (see below).
jsCode for JavaScript, content for HTML), with syntax highlighting and a drag handle above it to resize how much vertical space it takes up. It's the same underlying data as the code editor that appears on the canvas when you click a widget's own Edit button, and the same data the standalone Editor tab opens — edit in any of the three places and the others stay in sync automatically. For a JavaScript widget, changing the code here does not re-run it by itself; click that widget's own ▶ Run/Edit control (on the widget, or via Open in Editor) to apply it. HTML widgets update their preview live as you type, no separate run step needed. When nothing is selected, this same editor space is repurposed for Page JS — helper code that applies to the whole project (for example window.myHelper = () => { /* ... */ };) which any widget's own code can then call; it has its own ▶ Run button to re-apply it against the live canvas on demand.
Pages
The page tab strip above the canvas lets you build a multi-page project. Each page has its own widgets, layers, and background — but global variables and data connections are shared across every page.
- Click + to add a page; double-click a tab to rename it; click its × to delete it (hidden once only one page remains).
- Drag a tab to reorder it — the pin icon on each tab marks and lets you set the default/landing page: whichever page sits first is the one your exported site opens on. Click the greyed pin on any other tab to make that page the new default.
Widget Types
Every widget on the canvas is one of three types, and the type decides how it runs.
JavaScript widgets
These run directly on the page (not in an iframe) inside a sandboxed function, so your code sees its own scoped document/window that only reaches the widget's own container by default — while still having real access to things like fetch, setTimeout, and global variables.
- A small floating toolbar appears on the widget itself while it's hovered or selected: a drag grip (⠿) to move it, and either a ▶ Run button (before it's been run) or an Edit button (once it has) — Edit clears the last run's output and reopens the code for changes.
- Editing the code does not re-run it automatically — click ▶ Run again to apply changes (it also runs once automatically right after you add it).
- For a bigger editing surface, or to change position/size/name alongside the code, use the Properties panel's own code editor instead — see Editing a widget's code from the Properties panel above. Both edit the exact same code, kept in sync.
- This is the widget type that can read and write global variables, call page-navigation functions, and use every function described in the Runtime Reference below.
HTML widgets
These render your raw HTML/CSS/JS markup. While you're building, it's shown in a sandboxed preview frame that live-updates as you type — no separate "Run" step needed.
- The widget's own floating toolbar has a single Edit / Preview toggle button to switch between the live preview and the raw markup.
- HTML widgets can read global variables (for example to theme a color from a value you've set elsewhere) but they only see a snapshot taken when the preview last rebuilt — they don't write values back.
- On export, this isolation goes away: your HTML is placed directly into the final page, at the same trust level as the page itself. Keep that in mind if you're pasting in markup from somewhere you don't fully trust.
Image widgets
A simple image drop-zone — drag an image onto it or click to browse, and it displays that image. No code involved.
Save as Template
Any widget — of any type — can be saved to your own computer as a .json template file, using the Save as Template button in the Properties panel's action row (select the widget, then Tools → Properties). Load it back in later from the Library's Local tab, on this project or any other.
Buttons & Controls Reference
Every button shown as it actually looks in the app, organized by where you find it — not just named, so you know exactly what to look for.
Canvas toolbar
The bar directly above the canvas changes with your selection:
| Button | Purpose |
|---|---|
|
JavaScript
HTML
Media
|
Adds a new blank widget of that type. Shown only when nothing is selected. |
|
Colour
Text
Font
Events
|
Replaces the three add-widget buttons the moment 1+ widgets are selected (also always reachable from Tools > Style Tools and the Editor tab — see above). Full walkthrough for each — what Colour/Text/Font scan for and the code they write, what Events composes and inserts — just below. |
| 20 px ▾ | Toggles the alignment grid and sets its spacing (1–200px), with quick presets in the small dropdown arrow. Builder-only — has no effect on your exported page. |
| When off, an idle widget's outline only appears on hover/selection instead of always — a decluttering aid while you work. | |
| Window name Edit | Shown when exactly one widget is selected — the field renames it inline (the same name used everywhere else: Layers list, Properties, the runtime's window.windows['Name']); Edit jumps straight to that widget in the full Editor tab. |
|
|
Appear once 2+ widgets are selected, in this order: align left/right/top/bottom/center (horizontal or vertical), match width/height/both to the first-selected widget, distribute with equal gaps (needs 3+ selected). The same actions are also in the Properties panel and, on mobile, the Tools menu. |
Colour tool, in detail
Select one or more widgets and it replaces the add-widget buttons (also always reachable from Tools > Style Tools and the Editor tab, with nothing selected). It scans the selected widget(s)' JavaScript code for hardcoded colour values and lets you turn any of them into a real, editable global variable.
What it scans for — any top-level const/let/var whose name contains COLOR/COLOUR, or whose value looks like a hex (#1a1a1a) or rgb()/rgba() colour, regardless of name — including inside an array or object (RAMP = ['#111', '#222'] scans as two separate rows). A value already linked to a global variable is recognized and shown as already-tagged rather than offered again as hardcoded.
Each row's Style Tag box is one of: — None (hardcoded) —, an existing global_style_* tag (listed with its current value), or + New Tag. Editing the value field on a row left at None just rewrites that literal in place:
// before
const TEXT_COLOR = '#1a1a1a';
// after typing a new value into the row's Value field
const TEXT_COLOR = '#ff0000';
+ New Tag mints the next free slot (e.g. global_style_4), seeds it with the row's current value, and rewrites the constant to read it — picking an existing tag does the same rewrite bound to that key instead:
const TEXT_COLOR = (window.globalVariables?.global_style_4) || '#1a1a1a';
Switching a tagged row back to — None — unlinks it (the tag itself is not deleted — other widgets may still be reading it):
const TEXT_COLOR = '#1a1a1a';
Text tool, in detail
Same mechanism as the Colour tool, for hardcoded display text instead. What it scans for — any top-level const/let/var holding a plain quoted string that the Colour and Font tools' own rules don't already claim (a name containing COLOR/FONT, or a hex/rgb-shaped value, goes to those tools instead even if it's technically a string).
Same Style Tag box (global_text_* tags this time), same rewrite shape:
const TEXT = (window.globalVariables?.global_text_1) || 'Hello World';
Font tool, in detail
Same mechanism again, for font-related constants. What it scans for — any top-level constant whose name contains FONT or ALIGN (family, size, weight, text alignment) — name-based rather than value-based, since a font weight's own value ('700') looks identical to a plain string otherwise.
Same Style Tag box (global_font_* tags), same rewrite shape. A FONT_FAMILY row's dropdown also offers every font uploaded in Styles > Fonts directly, in addition to the plain Style Tag options.
const FONT_FAMILY = (window.globalVariables?.global_font_1) || 'Arial, sans-serif';
Events tool, in detail
A different shape of tool from Colour/Text/Font above: those three scan a widget's existing code for hardcoded values. Events doesn't scan anything — it composes a small, ready-to-run snippet from a trigger (when it should run) and an action (what it should do), shows you the exact code before anything happens, and appends it to a widget's code only once you click Insert.
Triggers — when the snippet runs:
- Value Change — runs whenever a global variable's value changes, checked on a short poll (the same way every widget already reads
globalVariables). - Timer — runs repeatedly on a fixed interval, for as long as the widget exists.
- Hover — runs once each time the mouse enters a given element's area (a CSS selector, same as the actions below).
- Click — runs each time a given element is clicked.
Actions — what the snippet does:
- Change a value — sets a global variable.
- Change text — sets an element's
textContent, by CSS selector. - Change colour — sets an element's
colororbackground-color, by CSS selector. - Change border — sets an element's
border, by CSS selector. - Change any attribute — sets any attribute on an element, by CSS selector.
Every selector-based action reaches into the widget's own markup with document.querySelector(...) — never a bare container identifier. Some built-in widgets (Cloud Nav 3D among them) wrap their own code in a private (function () { ... })(); for local scoping, which puts anything they declare with const/let — including a variable some of them happen to call container — out of reach of code appended after that wrapper closes. document has no such problem: it's a sandbox-provided parameter of the widget's whole code, not something the widget's own code declares, so it's always reachable regardless of what the existing code does with its own locals — and it's already scoped to just this widget's real container, same as a hand-named container variable would have been.
The preview updates live as you fill in each field, so you always see the exact code before it's inserted — wrapped in a comment "fence" that carries the trigger's own id:
// >>> Events Tool: valueChange:global_style_1 >>>
(function () {
let _last = JSON.stringify(globalVariables['global_style_1']);
setInterval(function () {
const cur = JSON.stringify(globalVariables['global_style_1']);
if (cur !== _last) {
_last = cur;
globalVariables['my_variable'] = '#ff0000';
}
}, 300);
})();
// <<< Events Tool: valueChange:global_style_1 <<<
Clicking Insert into <widget name> appends exactly that fenced block to the end of the widget's JavaScript. Nothing runs until you click Insert; Copy is there if you'd rather paste it in by hand at a specific spot instead.
Re-inserting replaces, it doesn't stack. If this window already has a fenced block for the same trigger — the same kind for Timer/Hover/Click, or the same watched variable for Value Change — the dialog shows it under "Already on <widget name>" (with its own Remove button), and Insert becomes Replace on <widget name>: it finds that exact fenced block by its id and swaps it in place, selector/interval/action and all, instead of leaving the old one running underneath a second copy.
A Click trigger (selector .my-label) paired with a border change, for example, comes out as:
// >>> Events Tool: click >>>
document.querySelector('.my-label').addEventListener('click', function () {
document.querySelector('.my-label').style.border = '2px solid #ff0000';
});
// <<< Events Tool: click <<<
On the widget itself
A small floating toolbar appears directly on a widget while it's hovered or selected:
| Button | Purpose |
|---|---|
| Drag to move the widget. | |
| ▶ Run (JavaScript, before first run) | Executes the widget's code. |
| Edit (JavaScript, after it's run) | Clears the current output and reopens the code for changes — run it again with ▶ Run when you're ready. |
| Edit / Preview (HTML, one toggle button) | Switches between the live rendered preview and the raw markup. |
Properties panel (Tools → Properties, one widget selected)
| Button | Purpose |
|---|---|
| ▲ Layer ▼ Layer | Moves the widget one layer up or down. Disabled at the top/bottom layer. |
| Save as Template | Downloads this widget as a reusable .json file (see §2 above). |
| Open in Editor | Jumps to this widget in the standalone Editor tab (JavaScript/HTML only). |
| Delete | Removes the widget, after a confirmation prompt. |
| Code editor drag handle | The thin bar above the code editor at the bottom of the panel — drag it to resize how much vertical space the editor takes up. |
See Editing a widget's code from the Properties panel above for how the code editor itself works.
Layers panel (Tools → Layers)
| Button | Purpose |
|---|---|
| + Add New Layer | Creates a new, empty layer above the current highest one. |
| Move layer up / down — reorders a layer's drawing position relative to the others. | |
| Click a layer's name to rename it; ✓ saves, ✕ cancels. | |
| Shows/hides every widget on that layer — hiding also pauses any JavaScript widgets on it. | |
| Set Active | Chooses which layer a newly-added widget lands on. |
| Remove Layer — deletes it; if it has widgets on it, you're warned first, and they move to layer 1 rather than being deleted. | |
| Move window up / down within a layer's own list — reorders widgets within the same layer. |
Page tabs
| Button | Purpose |
|---|---|
| Adds a new page. | |
| Deletes a page (hidden once only one page remains). | |
| Double-click a tab | Renames that page. |
| Drag a tab | Reorders pages. |
| Pin icon — solid on the current default/landing page; click a greyed pin on any other tab to make that page the new default. |
Data Connections
| Button | Purpose |
|---|---|
| Test API | Runs the connection immediately and shows the raw response, with detailed troubleshooting if it fails. |
| Auto-populate Variables | Generates a variable list automatically from the shape of the last test response. |
| Import from Gateway | Merges connections and variables in from another exported project's JSON file. |
| Quick Start ▾ | Pre-fills a whole connection (generic GET/POST/PUT) as a starting point. |
| Delete (header / query string / parameter rows) — removes that one row from the connection's config. |
Runtime Reference
Functions and values available inside a JavaScript widget's own code. This same reference is also built into the app itself — open Runtime Reference from the Builder tab for a live, copyable, searchable version scoped to your actual project (with the real names of your pages and widgets filled in).
Global scope — callable from any widget, on any page
| Call | What it does |
|---|---|
goTo<PageName>() | Navigate to a specific page by name — one function is generated per page in your project (for example a page named "Settings" gets window.goToSettings()). |
getContainerSize() | This widget's own real, live size — accurate in an exported page even if it's been resized responsively. |
window.windows['Name'].rerun() | Re-run a specific widget, by its display name, from anywhere — including from a different widget or a different page's code. |
window.myPage.width / .height / .color | Read or set the current page's own background size/colour — the same values shown in Tools → Properties when nothing is selected. |
Example — bounce this widget around the page, using its own live size so it never clips past an edge:
// Runs once, then re-positions this widget on every tick.
let vx = 2, vy = 2;
setInterval(() => {
const size = getContainerSize(); // this widget's own real width/height
const pageW = window.myPage.width; // the page's own configured width
const pageH = window.myPage.height;
let x = window.myWindow.x + vx;
let y = window.myWindow.y + vy;
// Bounce off whichever edge this widget's own size would otherwise clip past.
if (x <= 0 || x + size.width >= pageW) vx *= -1;
if (y <= 0 || y + size.height >= pageH) vy *= -1;
window.myWindow.x = x;
window.myWindow.y = y;
}, 16);
Swap window.myWindow.x/.y for window.windows['Other Widget'].x/.y to drive a different widget's position instead — for example keeping a label glued just to the right of a gauge, using the gauge's own getContainerSize() to know how far right "just past it" actually is.
Window scope — only inside a widget's own code
Every one of these can also be written as window.windows['ThisWidget'].x (etc.) from anywhere else — but from inside a widget's own code, use window.myWindow instead of typing out its own name. myWindow always refers to whichever widget the currently-running code belongs to — it's a property of window, not a variable of its own, so it's always written window.myWindow, never bare myWindow.
| Call | What it does |
|---|---|
window.myWindow.rerun() | Re-run this widget from scratch. |
window.myWindow.x / .y | Read or set this widget's own position. |
window.myWindow.width / .height | Read or set this widget's own size. |
window.myWindow.layer | Read or set this widget's own layer. |
rerenderOn('varName') | Re-run this widget automatically whenever the named global variable changes, instead of polling for it by hand. |
Reading & writing global variables
Inside a JavaScript widget, a global variable named token is reachable three interchangeable ways:
globalVariables.token // via the parameter passed into your widget
window.token // as a bare property on window
token // as a bare identifier, if the name is valid JS
Writing to any of these updates the value everywhere else immediately — every other widget reading that same variable sees the change without needing a page reload.
View preferences
Preferences for how the builder itself looks while you work — light or dark theme, and page zoom. These are local to your browser and have no effect on your exported page.
3. Data Connections Tab
Configure real API connections and map their responses onto global variables your widgets can use — found in the Data Connections tab.
Configuring a connection
- URL & method — GET, POST, PUT, or DELETE.
- Headers — with common presets available.
- Query Strings — always appended to the URL, regardless of method.
- Body Parameters — sent as a query string for GET, or as the request body for everything else.
- Additional JSON Body — raw JSON text; an object merges with your Body Parameters (parameters win on a name clash), an array is sent exactly as written and Body Parameters are ignored.
- Both headers, query strings, and the additional JSON body support
{{variableName}}placeholders, substituted from your current global variables at request time. An unresolved placeholder is left as literal text — handy for spotting a typo'd name.
Three Quick Start presets (generic GET/POST/PUT) can pre-fill a whole connection to start from, and "Import from Gateway" can merge in connections and variables from a JSON file.
Triggers
| Type | Behavior |
|---|---|
| Cyclic | Fetches automatically on a fixed interval you set. |
| Conditional | Watches a variable and fires only the moment it becomes true (not repeatedly while it stays true). |
| Manual | Never fires on its own — call it from a widget with await apiConnections['ConnectionName'](). |
Every enabled cyclic or conditional connection also fires once shortly after the page loads, so you're not stuck waiting for the first full interval to see a value.
Mapping the response
Each connection has a list of variables, each defined by a dot-separated path into the JSON response (e.g. data.items.0.result — a numeric segment indexes into an array). Leave the path empty to capture the whole response body. Use Auto-populate Variables after a successful test to generate this list automatically from the response shape.
Testing & CORS
The Test API button runs your connection immediately and shows the raw response, with detailed troubleshooting notes if it fails (by status code, or by connection-level network errors). If an API doesn't allow direct browser requests, the Use CORS Proxy checkbox routes the request through a public proxy — fine for quick testing, but not something to depend on for a production deployment; for that, handle CORS on the API side or run your own proxy.
4. Variables Tab
A flat, direct editor over every global variable in your project — whether you created it by hand, it was populated by a data connection, or a widget created it itself at runtime. Found in the Variables tab.
Browsing & filtering
The search box filters by name as you type. Two dropdowns narrow the list further: one switches between All Variables and just the ones a data connection defines; the other filters to a single Group (see below). Variables are listed under a collapsible header per group — click a group's own arrow to collapse or expand it.
Creating a variable
+ Create Global Variable opens a small form: a name, a Type (String, Number, or Boolean — this only changes how the Initial Value field parses what you type, not a hard constraint enforced later), an initial value, an optional description, and a Group (type an existing group name to add it there, or a new one to create it on the spot — not limited to the three built-in groups below).
Editing, renaming & deleting
Edit turns a row's name and value into input fields in place. Changing the value alone just updates it. Changing the name does something more thorough: every window's code that reads the old name (as globalVariables.oldName, window.oldName, or the bare identifier) is rewritten to the new name too, everywhere in your project — not just the entry in this list — so nothing silently starts reading undefined. Delete removes the variable from this list and from the live runtime immediately; any widget still reading its name afterward just sees undefined, the same as reading a name that was never defined.
Where variables come from
Every variable in this list falls into one of three groups, shown as the Group column:
- Colours — automatic for any variable named
global_style_<number>, the slot convention the Colour tool's own "+ New Tag" (and every built-in template) uses. - API — a variable defined by a Data Connection's own variable mapping, whether or not it's been populated by a real response yet (see the Data Connections tab above) — shown as Pending here until the connection actually runs once.
- General — everything else, including anything you type into this tab's own Create form, and any
global_text_*/global_font_*tag the Text/Font tools create.
A variable a widget creates itself at runtime with a plain globalVariables['name'] = value assignment (see the Runtime Reference above) also shows up here automatically, usually within a second or two, since this list is kept in sync with the live runtime rather than the other way around.
5. Editor Tab
A dedicated code editor for one widget at a time, with a live preview alongside — useful when you want a bigger, distraction-free editing surface than the canvas provides. It's the exact same underlying widget data as the canvas: edit here or on the canvas, and both stay in sync. Found in the Editor tab.
Picking a widget
The left column lists every JavaScript and HTML widget on the current page (Image widgets have no code, so they don't appear here). Click one to open it; arriving via a widget's own Open in Editor button (Properties panel or canvas toolbar) jumps straight to it.
The code editor
Syntax-highlighted, with a live rendered preview alongside so you can see the effect of a change without switching back to the canvas. For a JavaScript widget, editing here doesn't re-run it automatically — same as everywhere else this code lives — apply changes the same way (▶ Run/Edit on the widget, or from the canvas). An HTML widget's preview updates as you type instead, no run step needed. Drag the vertical handle between the preview and the editor to resize either side.
Colour/Text/Font/Events toolbar
The same four tools described in the Buttons & Controls Reference above, shown here in the code editor's own header whenever a JavaScript widget is open — acting on that one widget specifically, with no selection needed.
Runtime Reference panel
A collapsible column on the right edge holds the same live, searchable Runtime Reference mentioned above (functions, plus every current variable's name and value, filled in for your actual project). It can be pinned open as a persistent column instead of a collapsible one — the pin state is shared with the identical panel in the Properties panel on the canvas, so it stays open (or closed) consistently wherever you're editing code.
6. AI Context Tab
A copy-ready brief describing this app's project file format, meant to be pasted into an external AI chat so it can generate a valid project for you to import — useful if you'd rather describe what you want in words than build it by hand. Found in the AI Context tab, which also has a "How to use this" walkthrough of the same round trip described below.
Example
Click Copy Markdown, paste it as the first message to an AI chat (ChatGPT, Claude, or similar), then describe what you want in the same message or a follow-up — for example:
Build me a single JavaScript widget: a digital clock, 300x80px,
showing HH:MM:SS in a monospace font, text color bound to a global
variable so I can theme it later. Give me the full project JSON.
The brief tells the model the exact shape this app expects, so a reasonable response comes back as a real, importable project — something like this trimmed down to one widget:
{
"globalFonts": {},
"builderPages": [{
"id": "page-1",
"name": "Home",
"windows": [{
"id": "window-1",
"title": "Digital Clock",
"type": "javascript",
"content": "",
"jsCode": "const TEXT_COLOR = (window.globalVariables?.global_style_1) || '#1a1a1a';\nfunction tick(){ container.textContent = new Date().toLocaleTimeString(); }\nsetInterval(tick, 1000); tick();",
"position": { "x": 0, "y": 0 },
"size": { "width": 300, "height": 80 }
}],
"backgroundConfig": { "width": 800, "height": 600, "color": "#ffffff" },
"layerStates": [{ "layerNumber": 1, "visible": true, "name": "Layer 1" }]
}],
"dataConnections": { "apiConnections": [], "globalVariables": { "global_style_1": "#1a1a1a" } },
"exportType": "multi-page",
"totalPages": 1
}
Save that response as a .json file and use File → Load From File to bring it in — or paste it straight into a new chat's response and copy it from there. If the model gets a field wrong, importing will either ignore what it doesn't recognize or throw a clear error rather than silently corrupting your existing project; try again with the error message pasted back in.
7. Exporting Your Project
Everything you build lives only in your browser until you export it. Available from the File menu — see the Buttons & Controls Reference above for exactly where each control sits.
File → Save (JSON project save/load)
The only format that round-trips back into the builder. Use this as your real save file — save it when you want to preserve your work, and use Load From File to bring it back in later, on this computer or another one. The builder also keeps a best-effort autosave of your last session in your browser, but that's a safety net for an accidental tab close, not a substitute for saving.
File → Export: HTML
The main deliverable — one fully self-contained .html file containing every page, all your widgets, and the complete data-connection runtime, inline. Open it directly in a browser or embed it as an iframe anywhere. This is a one-way output for deployment, unlike the JSON save above — you can't load an exported HTML file back into the builder to keep editing it.
File → Export: JS
A .js-only bundle meant to be pasted into markup you already have, rather than opened on its own.
Both HTML and JS exports have two extra options next to them:
- Strip Comments — removes comments from each widget's own code in the exported output. Doesn't apply to the JSON save, since that's meant to still be readable/re-editable later. Useful when the export is headed for something with limited storage, like a microcontroller's flash.
- Gzip — compresses the downloaded file further (compression is dramatic here — a real 15-widget test page went from 315KB raw to 21.7KB gzipped). Comes with a Format choice: Readable Text (.h) gives you a hex byte-array (also copied to your clipboard) to paste straight into an Arduino sketch or other C/C++ firmware source with no filesystem needed; Raw Binary (.gz) gives you the actual gzip file, for serving a pre-gzipped file from a filesystem or static web server with a
Content-Encoding: gzipheader.
Every export format has a matching Preview option that opens the result in a new tab before you download anything.
8. Tips & Good Habits
- Export a JSON save file regularly. The in-browser autosave is a convenience, not a backup — clearing your browser data or a storage limit can lose it.
- Use the System/API variable inspector templates (in the JavaScript template library) while building — they render a live table of every current global and API variable, which is the fastest way to confirm a value is actually what you think it is.
- Prefer
myWindowover a widget's own name when writing that widget's own code — it keeps the code copy-paste-safe if you ever rename the widget or duplicate it. - A hidden layer stops its widgets from running, not just from showing — useful for pausing something you're not currently working on rather than deleting it.