Documentation is usually organised by cause. Debugging starts from a symptom. This page is the index that bridges them.
Every entry is something that has actually happened, and most of them share a property that makes them expensive: they do not produce an error. The call returns 201, the record looks right, and the page is wrong.
Quick index
| What you see | Cause |
|---|---|
| Page deploys, nothing changes on screen | data.screens overrides data.html |
| Your JavaScript never runs | Written to data.style, not top-level style |
<script> tag present but empty | The parser discards script bodies |
getElementById returns null at startup | No readiness gate |
create => Not a new metrics… | Missing isNew: true |
This form is not available for public access | Guest missing from permission.read |
Reservation Definition not found | Wrong datatype name |
Booking returns 401 from a page | create needs auth |
| Favicon does not change | site.data.favicon must be a string |
| Whole site renders unstyled | CSS link regex matched one spelling |
| Nav links 404 across every page | Fragment links not rewritten |
| One click, two records | Two bootstraps, or no send guard |
| Prices and images disappeared | {"data": {...}} replaced the whole object |
| Products load but no category has them | Categories belong in post |
| Product tiles blank after a load | image not set alongside images |
| Upload path contains the filename twice | location is a folder |
| SVG will not render | Served as application/octet-stream |
403 with body error code: 1010 | No User-Agent |
404 customer … not found on a read | x-client-authorization sent with an operator token |
data-bind renders blank | Bindings cannot call JS globals |
| Configured options are not charged | Not in options[] with a numeric price |
[object Object] where a price should be | calculatedPrice is an object |
| Canvas text draws in the wrong font | fonts.load() needs a size |
Every URL returns 200 | Unknown paths serve the fallback body |
| Order is missing the customer's choices | Checkout drops freeform fields |
| A global defined in one handler is undefined in another | Attribute handlers do not share globals |
| Confirmation emails never arrive | Org has no credit |
Page deploys, nothing changes
You see: update-partial returns 201. The rendered page is unchanged. You redeploy three times before suspecting anything.
Cause: the page has data.screens, which the renderer uses in preference to data.html. Any page ever saved in the visual editor has it.
Fix: clear the other shapes in the same write.
{ "data.html": "…", "data.screens": [], "data.sections": [], "data.content": "" }Your JavaScript never runs
You see: no errors, no effect. The page behaves as if the code is not there.
Two causes.
You wrote to data.style.javascript. The renderer reads a top-level style, a sibling of data. Nothing reads data.style.
print("style" in rec) # must be True
print("style" in rec.get("data", {})) # must be FalseOr you used a <script> tag. The HTML parser is configured with blockTextElements: { script: false }, which discards script bodies. The element survives as an empty node:
[...document.querySelectorAll('script:not([src])')].filter(s => s.textContent.length === 0)getElementById returns null
You see: your code runs — a flag at the top is set — but every element lookup is null, or window.appmint is undefined.
Cause: style.javascript is injected into the head. It executes before the body is parsed and before the runtime mounts.
Fix: a readiness gate, checking a real element and the specific namespace you use.
(function boot() {
if (!document.getElementById('app-root') || !(window.appmint && window.appmint.cart)) {
return void setTimeout(boot, 30);
}
// …
})();Not DOMContentLoaded — it may have fired before your script was injected, and then nothing runs at all.
create => Not a new metrics…
You see:
{ "statusCode": 400,
"error": "create => Not a new metrics, please use update or set the new property" }Cause: the payload is missing isNew. The message says "the new property", which reads as a field named new. It is not.
Fix: "isNew": true at the top level of the body.
Form "not available for public access"
You see: an anonymous submit refused with a message about public access, while permission.create clearly includes Guest.
Cause: the message is about read. Before accepting a submission the platform loads the form definition as the caller, and an anonymous caller with no read grant cannot load it.
Fix: Guest in both permission.create and permission.read.
Reservation Definition not found
You see: the slots endpoint cannot find a definition whose id you are certain is correct.
Cause: it was created with the wrong datatype. The correct one is reservation_definition — not crm_reservation_definition, which creates a record in a collection the slot generator never looks in.
Related: workDays must be capitalised full day names. "monday" matches nothing and that day generates no slots, silently.
Booking returns 401
You see: definitions and slots work anonymously; create returns missing_authorization_header.
Cause: crm/reservations/create requires auth. The public endpoints let you show availability without credentials, but not take a booking.
Fix: call it through window.appmint.reservation.create on a hosted page, or through your own server. Never by putting an operator token in the page.
Favicon does not change
You see: the site record has a favicon, the file fetches fine, the tab shows the platform default.
Cause: site.data.favicon was stored as an object. It is passed straight into the framework's icons field with none of the unwrapping the logo gets, so an object yields no icon link at all.
Fix: store a string URL. Park the detail elsewhere.
{ "data.favicon": "https://…/favicon.png?…",
"data.faviconAssets": { "ico": "…", "png": "…" } }Verify on the rendered page, never the record:
curl -s "https://your-site/?cb=$RANDOM" | grep -oE '<link rel="icon" href="[^"]{0,80}'Related: page-level <link rel="icon"> tags are deliberately stripped by the renderer. The site record is the only mechanism.
Site renders unstyled
You see: a wall of unstyled text. It looks catastrophic.
Cause: the deploy's CSS-inlining regex matched <link …> but the page uses <link … /> (or the reverse), so no substitution happened and the relative stylesheet 404s.
Fix: match both spellings, and assert the count before substituting.
n = len(re.findall(r'<link rel="stylesheet" href="styles\.css"\s*/?>', html))
assert n == 1, f"{name}: {n} styles.css links"Nav links 404
You see: every page's navigation is broken, on a deploy that reported success throughout.
Cause: the link-rewriting regex required the closing quote straight after .html, so anchored links — index.html#pricing, index.html#modules — were never rewritten. Those are exactly the links in a nav.
Fix: capture the fragment, and assert nothing survives.
html = re.sub(r'href="([a-z0-9\-]+)\.html([#?][^"]*)?"', rewrite, html)
assert not re.findall(r'href="(?!https?:)[^"]*\.html[^"]*"', html)One click, two records
You see: identical submissions milliseconds apart. It looks like a double-click.
Two causes, often together.
Two bootstraps on the page. A deploy script that appends by slicing the document re-captures the bootstrap from the previous run. Strip every existing one and regenerate — never dedupe by picking a survivor.
No send guard. Disabling the button is not enough; a second submit can come from the keyboard.
if (form.dataset.sending) return;
form.dataset.sending = '1';
// … delete it in the final .then()Prices and images disappeared
You see: an update meant to change one field, and the record has lost everything else.
Cause: {"data": {...}} replaces the entire data object.
Fix: dot-paths — "data.price": 40 — one key each. And snapshot before any bulk write; a find dump is the only thing that makes this recoverable.
Products load but categories are empty
You see: every product created successfully; no category lists any of them.
Cause: categories and tags were put in data. They are read from post.
"post": { "categories": [...], "tags": [...] }Product tiles are blank
Cause: data.images was set — an array of objects — but data.image, a plain string thumbnail, was not. Both are needed.
Upload path doubled
You see: products/chair/chair.jpg/chair.jpg.
Cause: location is the folder. The endpoint appends the filename.
SVG will not render
Cause: uploads are served as application/octet-stream. Browsers sniff raster formats, so PNG and JPEG survive it; SVG does not.
Fix: inline the SVG markup, or use a raster format.
403 error code: 1010
Cause: no User-Agent. The CDN rejects the default one some HTTP clients send. It looks like a permissions failure and is not.
Fix: set a browser-like User-Agent on every request.
404 customer … not found
You see: a repository read failing with a message about a customer that has nothing to do with your request.
Cause: x-client-authorization was sent alongside an operator token, so the caller was resolved as a customer — and the operator is not one. The lookup fails before your request is considered.
Fix: for operator calls send Authorization: Bearer <token> and nothing else.
data-bind renders blank
Cause: binding expressions cannot call JS globals — Number, Math, parseInt, toLocaleString are shadowed to undefined, and the binding silently blanks.
Fix: use data-format for formatting, or compute the value in your own code.
Configured options are not charged
You see: the customer picks £40 of upgrades and is charged the base price.
Cause: the choices were not in options[], or their price was not numeric. calculate-cart counts a delta only from options[].
options: [{ name: 'size', label: 'Size', value: 'Large', price: 90 }][object Object] in a price
Cause: calculatedPrice is an object — { finalPrice, originalPrice, discount, … }. Read .finalPrice.
Canvas text, wrong font
You see: canvas text renders in a fallback face even though the webfont is loaded.
Cause: document.fonts.load() needs a size in the specifier. 'Orbitron' resolves without loading anything; '64px "Orbitron"' works.
Fix: repaint as each face lands, and again on document.fonts.ready.
Every URL returns 200
You see: your 404 check passes for paths you invented.
Cause: an unknown path serves the site's fallback body with a 200.
Fix: verify by <h1>, not status code.
Order missing choices
Cause: storefront/checkout-cart persists options[] and drops freeform attributes and design.
Fix: push every choice through options[], including ones priced at zero.
Global is undefined
You see: onclick="doThing(this)" reports doThing is not defined, though the code defining it demonstrably ran.
Cause: attribute handlers on a hydrating host are not guaranteed to see globals assigned elsewhere.
Fix: bind with addEventListener from inside your code. If you must use an inline handler, put the whole body in the attribute.
Emails never arrive
You see: a booking or submission succeeds, the record is there, nobody is notified. Sometimes a banner about credits.
Cause: outbound mail is billed and the org has no credit. The record is still written — only the notification is lost.
Fix: top up, and do not rely on the email as your only signal. Poll the collection.
Most of these are silent. If you build one habit from this page, make it verify the outcome, not the response: read the record back, fetch the rendered page, count the rows. A 201 is a receipt, not a result.