{
  "$comment": [
    "TEMPLATE RELEASE MANIFEST — the update channel's only server-side artifact.",
    "",
    "Tenants fetch this with a plain GET: no auth, no query string, nothing that",
    "identifies the install. That is deliberate — a version check that registered",
    "every self-hosted instance would make 'everybody can selfhost' untrue.",
    "",
    "This copy is served from the template itself so the format is versioned",
    "alongside the code and a fresh clone has something valid to parse. The LIVE",
    "manifest tenants actually read lives at UPDATE_MANIFEST_URL (default",
    "https://lasersite.ai/releases.json) and must be published on every release.",
    "",
    "FIELDS",
    "TOP-LEVEL (the kill switch — a release cannot credibly revoke itself)",
    "  revoked        versions that must NEVER be applied and are warned about if",
    "                 already running. They are filtered out of the offered list",
    "                 entirely, so every consumer inherits it without remembering",
    "                 to check. Use when one bad release must be stopped mid-spread.",
    "  minimumVersion a floor: anything below it is treated as withdrawn. Blunter,",
    "                 and the right tool when the problem spans several versions.",
    "",
    "PER RELEASE",
    "  version       required, dotted numeric. Must match package.json of that release.",
    "  date          ISO date, display only.",
    "  publishedAt   REQUIRED for unattended apply. ISO timestamp the release went public; the soak window is measured from it. Omit it and autoApplyDecision() blocks the release forever with \"No publish date\" — `date` does NOT substitute, it is display only. release.yml emits this field; copy it through.",
    "  title         one line, shown as the release heading.",
    "  summary       one short paragraph — what changed and why it matters.",
    "  bullets       what you GET. Feature-level changes.",
    "  notices       what you must KNOW. Rendered separately from bullets, because a",
    "                deadline read as a feature is a missed deadline. Each entry is",
    "                { kind, text, sunset? } where kind is:",
    "                  action       must be done BEFORE updating (export data, change",
    "                               config). Makes the release ineligible for",
    "                               auto-apply by definition — there is a human step.",
    "                  deprecation  works now, stops later. Set `sunset` to the version",
    "                               or date so people get lead time, not an outage.",
    "                  info         worth knowing, requires nothing.",
    "  breaking      true when a human must do something beyond redeploying.",
    "  security      true raises the VOLUME only: a persistent, non-dismissible",
    "                banner. Never applies anything, never skips consent.",
    "  safe          publisher's assertion that this may be auto-applied. NOT",
    "                sufficient alone — the tenant must also opt in, AND the client",
    "                re-derives eligibility and refuses anything breaking or carrying",
    "                an action notice, so a publishing mistake cannot auto-deploy.",
    "  changelogUrl  https only. Preferred link — image buyers have no source to diff.",
    "  diffUrl       https only. Code compare, for people running from source.",
    "  migrations    filenames that apply on next boot. Transparency only.",
    "",
    "Newest first is conventional but not required — the client sorts by version."
  ],
  "releases": [
    {
      "version": "0.1.60",
      "date": "2026-08-23",
      "publishedAt": "2026-08-23T19:02:54Z",
      "imageDigest": "sha256:2e0f5da22386046498992f332e7826fa74ca3bc0d92035d4e4543c2024d26e85",
      "title": "0.1.60",
      "summary": "Generated from the 17 commit subjects since v0.1.54. These are raw commit titles, not hand-written release notes.",
      "bullets": [
        "0.1.60",
        "Fix three defects in the manifest job, before it ever runs",
        "Make decryptSecret say WHICH way it failed",
        "Give Fly a deploy hook, since it is the one host without one",
        "Apply an update by hand, where that can be said truthfully",
        "Move deployHookStatus behind the session check, and read decryptSecret right",
        "Give the deploy hook a screen, so auto-apply can be switched on at all",
        "Do not set cache-control on the manifest",
        "Publish every release to the manifest, and make the deploy hook settable",
        "Correct four status claims that no longer match the code",
        "Record which Fly app is actually lasersite.ai",
        "Put the language switcher in every hand-rolled navbar",
        "Rewrite the knowledge base, English and German",
        "Validate a coupon against the payment type the buyer picked",
        "Stop a zero-total checkout from being fulfilled twice",
        "Send a licence buyer to their key, not to an empty course",
        "Make a licensed purchase actually deliver"
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.60",
      "migrations": []
    },
    {
      "version": "0.1.53",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T20:49:03Z",
      "imageDigest": "sha256:f59ba4b68cbb561600efbc82d29113ed3b1b77092b363a21c9250e4fdaacfa94",
      "title": "Update script survives a slow registry",
      "summary": "The Fly update script now retries the image copy. Fly's registry regularly reports 'app repository not found' on a first push and accepts the identical command seconds later — that no longer aborts your update.",
      "bullets": [
        "scripts/update-fly.sh retries the image copy three times with backoff; a copy that genuinely fails still stops with your app untouched."
      ],
      "notices": [
        "No database migrations in this release.",
        "Only affects the Fly update helper script — the application itself is unchanged."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.53",
      "migrations": []
    },
    {
      "version": "0.1.52",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T20:16:05Z",
      "imageDigest": "sha256:5a67f841a6ae05e50b5940167223458aec1f79db809f5fd17980049126318951",
      "title": "Product card button follows the bullets",
      "summary": "On product cards that list feature bullets, the button now sits under the list in your brand colour instead of on the photo above it. Cards without bullets are unchanged.",
      "bullets": [
        "Card call-to-action moves below the feature bullets and uses the brand colour; it stays pinned to the bottom so buttons line up across a row."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.52",
      "migrations": []
    },
    {
      "version": "0.1.51",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T16:35:28Z",
      "imageDigest": "sha256:11dbfddbbf81252529c1506cdb24420025f795810bad5f571fed5fe3c24d658e",
      "title": "Product pages lead with their cover",
      "summary": "The sales band now places the pitch, the benefit list and the buy card on the product photo, so a visitor sees the offer and the way to take it in one view. On phones the photo stays a band above stacked text and card. Product cards also line up properly, and new product pages ship with an optional long-form block.",
      "bullets": [
        "Product page hero: cover photo as the stage, with copy and buy card on it; height follows the content rather than filling the viewport.",
        "On small screens the photo stays a band above the stacked copy and buy card.",
        "Product card titles cap at two lines, so a long name no longer pushes one card taller than its neighbours.",
        "New product pages include a long-form section, shipped switched off and empty until you write it."
      ],
      "notices": [
        "No database migrations in this release.",
        "Existing product pages keep their current sections; the new long-form block appears on pages created from now on."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.51",
      "migrations": []
    },
    {
      "version": "0.1.50",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T15:41:02Z",
      "imageDigest": "sha256:a0797663b3f4b38379936db55516e4c1f3cd6ff920ab97fe75229d9c8690b3a2",
      "title": "Checkout waiver speaks the buyer's language",
      "summary": "The withdrawal-waiver checkbox was hardcoded English while the rest of the checkout translated. It now comes from the message catalogue, and the consent recorded with the order is the exact sentence the buyer was shown — in their language.",
      "bullets": [
        "Withdrawal waiver is translated (English and German ship; other languages fall back to English).",
        "The stored consent record and the on-screen sentence are the same string, per language."
      ],
      "notices": [
        "No database migrations in this release.",
        "The German wording is a plain translation of the English default, not vetted legal text. Have your own lawyer read every language you offer — this checkbox gates the purchase button."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.50",
      "migrations": []
    },
    {
      "version": "0.1.49",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T15:19:32Z",
      "imageDigest": "sha256:3a29344a35625590194a34b01f6b86858998b6ef019f6e8384a4e1d32061c30e",
      "title": "Shorter withdrawal-waiver wording",
      "summary": "The checkout's withdrawal-waiver checkbox now reads 'I acknowledge that I lose my right of withdrawal once performance has begun.' — without the separate request to begin performance immediately.",
      "bullets": [
        "Shorter waiver sentence at checkout; the checkbox still gates the purchase button and its exact text is still stored with the order.",
        "Orders placed before this release keep the wording their buyer actually saw."
      ],
      "notices": [
        "No database migrations in this release.",
        "This sentence is legal wording for YOUR shop. It ships as a default, not as advice — have your own lawyer confirm it fits your jurisdiction and what you sell."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.49",
      "migrations": []
    },
    {
      "version": "0.1.48",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T14:38:56Z",
      "imageDigest": "sha256:740ca365b78bee1b715c0263d43bc307087eed1f44a6d1ae3e8fbc2a58203a2b",
      "title": "Pages that read as pages",
      "summary": "A design pass across the public site: blog covers keep their titles readable, pages no longer open with half a screen of empty space, product cards show what is actually included, the product page leads with its cover image, and sections alternate a subtle brand tint so a page reads as bands instead of one endless column.",
      "bullets": [
        "Blog card titles stay readable on bright cover photos.",
        "A page's first section no longer stacks its padding on top of the navigation clearance — collection pages start above the fold.",
        "Product cards show up to three of the product's bullet points under the cover image.",
        "The product page leads with its cover image, and the description heading uses the product's name instead of its type.",
        "New pages alternate a subtle brand tint between sections; the FAQ block aligns left with the rest of the page."
      ],
      "notices": [
        "No database migrations in this release.",
        "Existing published pages keep their current backgrounds — the alternating tint applies to newly created pages."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.48",
      "migrations": []
    },
    {
      "version": "0.1.47",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T13:01:30Z",
      "imageDigest": "sha256:18b97062b47b0d5b67557bdb66b5f79d3403432ac99890ea6bc33b6e06a056ed",
      "title": "Demo banner is actually visible",
      "summary": "In demo mode the banner sat at the top of the page, where the site navigation — which is fixed to the top edge — painted over it. It now pins to the bottom edge, below the sticky mobile buy bar. Only affects installations running DEMO_MODE.",
      "bullets": [
        "The demo banner moved to the bottom edge, so a fixed navigation can no longer hide it."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.47",
      "migrations": []
    },
    {
      "version": "0.1.46",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T10:36:19Z",
      "imageDigest": "sha256:a0d1c638fd2f3b644b6deff5e011f67ebac3763a5f98d1a566160db17d78659e",
      "title": "Readable blog card titles",
      "summary": "Blog cards with a light cover photo kept their title readable only at the top; the scrim faded to nothing exactly where a three-line title ends. The gradient now holds a light wash through the middle and the title carries its own soft shadow.",
      "bullets": [
        "Blog card cover scrim no longer goes fully transparent mid-card, so titles stay readable on bright cover photos."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.46",
      "migrations": []
    },
    {
      "version": "0.1.45",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T09:03:57Z",
      "imageDigest": "sha256:ad796f53344606db82d03895fda19d87331b898f1c99fa9584a14e33531023fe",
      "title": "Demo noindex fix",
      "summary": "In demo mode the noindex header is now set per request instead of at build time, so it applies on every hostname the instance answers on — including the platform's own *.fly.dev address. Only affects installations running DEMO_MODE.",
      "bullets": [
        "X-Robots-Tag is set in middleware at runtime; the build-time header config could never see a runtime environment variable."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.45",
      "migrations": []
    },
    {
      "version": "0.1.44",
      "date": "2026-08-18",
      "publishedAt": "2026-08-18T08:50:49Z",
      "imageDigest": "sha256:18073b19f6361982dc813f7379e8d543ff2ce528cf3998382c6f91494e4936d6",
      "title": "Public demo mode",
      "summary": "A new DEMO_MODE switch turns an installation into a safe public demo: credential settings become read-only, access tokens expire on their own, the whole host is marked noindex, and a banner explains what the visitor is looking at. Off unless you set it — nothing changes for a normal installation.",
      "bullets": [
        "DEMO_MODE=1 makes the integration, payment, storage and licence settings read-only, with an explanation instead of a silent failure.",
        "Access tokens minted while in demo mode expire after two hours, so a token taken off the box stops working on its own.",
        "Every route answers X-Robots-Tag: noindex, so a demo cannot compete with the real site in search.",
        "A banner names the demo and shows its sign-in details; login and link come from DEMO_LOGIN_EMAIL / DEMO_LOGIN_PASSWORD / DEMO_ABOUT_URL, never hardcoded."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.44",
      "migrations": []
    },
    {
      "version": "0.1.42",
      "date": "2026-08-15",
      "publishedAt": "2026-08-15T01:17:26Z",
      "imageDigest": "sha256:7a7725c7d6d4beeb6135fb47c73d9bef2849fcc30a5ff6492229db43bba0d283",
      "title": "Contact heading alignment",
      "summary": "The contact section's heading and description now center above the centered form card (the no-details layout), instead of sitting left-aligned as the only left heading on the page.",
      "bullets": [
        "A contact section without address/email/phone details centers its heading and description over the centered form card."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.42",
      "migrations": []
    },
    {
      "version": "0.1.41",
      "date": "2026-08-14",
      "publishedAt": "2026-08-15T00:06:32Z",
      "imageDigest": "sha256:1e677cf4ef4e7c7e1c9f3492e7accb5484fa1f0bfc4ee243b7545764d666583c",
      "title": "Contact form polish",
      "summary": "The public contact form now matches the site's design language (taller, rounded fields), a contact section without contact details centers its form instead of leaving an empty column, and the floating footer's inner spacing lines up with the section cards above it.",
      "bullets": [
        "Contact form fields match the site's style — taller, rounded, on site tones — instead of the admin's compact controls.",
        "A contact section with no address/email/phone details centers the form; the empty half-column is gone.",
        "The floating footer's content inset now matches the section cards, so stacked cards read as one system."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.41",
      "migrations": []
    },
    {
      "version": "0.1.40",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T23:41:05Z",
      "imageDigest": "sha256:9ad9b9fa728ec2676fa82c856c40734a157f318bba2d276df402606da4a280f4",
      "title": "Two small fixes",
      "summary": "The floating footer card gets proper breathing room on phones (content used to sit almost on the card's edge), and the page health check no longer wrongly flags comparison sections as empty.",
      "bullets": [
        "Floating footer: comfortable side padding on mobile — the brand and link columns no longer hug the card border.",
        "Page health check: comparison sections are judged by their columns and rows, not a field they never had — filled tables no longer show a false 'has no items' warning."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.40",
      "migrations": []
    },
    {
      "version": "0.1.39",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T21:11:03Z",
      "imageDigest": "sha256:d99077a7be33e76c41c63fb2440ac60f6cc6a933578f1e64e3ce9377299446ce",
      "title": "Five new sections for pages that sell",
      "summary": "The page builder gains five section kinds aimed at sales and event pages: a countdown that counts to your next event date (or a fixed moment) and honestly disappears when it passes, an agenda/programme with time slots, a comparison table with a highlightable recommended column, an image gallery, and a custom-HTML block that inherits your theme and is strictly sanitized. Sales and event pages can pin a floating buy/register bar to the bottom on phones, and the add-section menu offers a one-click guarantee band.",
      "bullets": [
        "Countdown section: counts to a fixed date & time or automatically to your next event date. When it passes it shows your closing line or disappears — it never fakes a new deadline.",
        "Agenda section: a time-slot programme (time, title, speaker, description) for workshops, conferences and event pages.",
        "Comparison section: compare 2–4 plans or options in a table; mark one column as recommended; yes/no answers render as check marks.",
        "Gallery section: a responsive image grid with optional captions, in 2–4 columns.",
        "Custom HTML section: your own markup inside the themed page — headings, text, lists, tables, links and images; scripts are always stripped (script embeds stay in the sandboxed Embed section).",
        "Sticky mobile buy bar: sales and event pages can pin the buy/register button to the bottom of the screen on phones — long pages no longer scroll the one action away.",
        "Guarantee band: one click in the add-section menu inserts a ready guarantee block you edit to your own terms.",
        "Over MCP: business name, description, contact details and the favicon are now settable (update_business_settings) — AI assistants can finish the branding basics."
      ],
      "notices": [
        "No database migrations in this release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.39",
      "migrations": []
    },
    {
      "version": "0.1.38",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T20:22:02Z",
      "imageDigest": "sha256:0523933ec49116ca2c7906f952796e0d543f403ddf0bb419c9c648073b5b41de",
      "title": "Your site speaks for your business, not the template",
      "summary": "The setup wizard now asks one optional sentence about your business, and every default that used to be generic becomes yours: the homepage headline, the search-engine description, and social cards all use your own words. Fresh pages ship with publishable copy instead of fill-me-in instructions, placeholder contact details and empty footer columns can no longer appear on the live site, the browser-tab icon is finally yours to change, and setup offers eleven finished visual styles applied with one click.",
      "bullets": [
        "Setup asks 'one sentence on what you do' (optional) — it becomes your homepage intro, search snippet and social card text. Editable anytime under Settings → Branding.",
        "Installs still carrying the template's old stock description get it replaced automatically with a neutral one composed from your business name (only if you never edited it).",
        "New-site pages open with real, publishable copy — the fill-me-in editor instructions that could end up live are gone, and the home page gained How-it-works steps and an FAQ.",
        "Placeholder leftovers can't publish anymore: sample contact details render as empty, footer columns without links disappear from the live site, and the page health check flags anything still default.",
        "The favicon is now yours: upload one under Settings → Branding and the browser tab follows your brand instead of the template's icon.",
        "Setup ends with a 'Pick a look' step — eleven finished styles, one click applies and publishes colours, type and backgrounds. Skippable, changeable anytime.",
        "Add-section defaults no longer invent social proof ('10K+ Customers', fake team names) — they show self-evident placeholders the health check flags until real numbers replace them.",
        "Sample products use your install's currency; collection pages no longer link to themselves; empty-state wording is consistent."
      ],
      "notices": [
        "Two database migrations (0043, 0044) apply automatically at startup; both are idempotent and need no action.",
        "Migration 0043 only touches homepage descriptions that still EXACTLY match the template's old stock text — anything you edited is left alone. Consider writing your one-sentence description under Settings → Branding for a better search snippet.",
        "Not marked for unattended auto-apply because 0043 rewrites stored text (the release checklist rule); the change is cosmetic and rolling back is harmless."
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.38",
      "migrations": [
        "0043_meta_description_debrand.sql",
        "0044_branding_favicon.sql"
      ]
    },
    {
      "version": "0.1.37",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T18:27:17Z",
      "imageDigest": "sha256:763813409283b2e243156047020f236f70d4b0530c44a03e7f25d3d61a686918",
      "title": "Build whole pages over MCP, and scoped access tokens",
      "summary": "An AI connected over MCP can now build and publish complete pages — custom landing pages, sales pages and event pages, not just the structural site pages — and manage SEO, media, testimonials, pricing plans and theme templates while doing it. Every publish keeps a restorable history. Access tokens got a security overhaul: they are stored hashed, can be limited to site, CRM or commerce writes, are rate-limited, and every tool call is logged against the token that made it.",
      "bullets": [
        "Custom landing pages over MCP: create, edit section by section, preview the draft, and publish to a live URL — the same surgical tools now address site, custom, event and product pages alike.",
        "Publish history: every publish stores a restorable revision (latest 20 per page); restore goes to the draft so nothing goes live unreviewed, and a draft can be discarded back to the published state.",
        "SEO over MCP: page titles, descriptions and social cards, robots.txt, and redirects.",
        "Media over MCP: list assets and ingest an image from a URL into the media store (size-capped, internal addresses refused).",
        "Testimonials, contact lookup, About and business-settings reads, and demo content — the content surfaces AI builds kept dead-ending on.",
        "Product billing plans (monthly/yearly) readable and settable; theme templates listable and applicable in one call, with scoped defaults and a page-styling reset.",
        "Read-only page stats and conversion summaries, so 'the page isn't converting — adjust it' becomes possible.",
        "Access tokens: value shown exactly once at generation (stored hashed), optional per-area write scopes (site / CRM / commerce; none = read-only), a per-token rate limit, and a call log attributing every tool call.",
        "Fixed: robots.txt settings' very first save on a fresh install silently stored nothing; contact lookup tools referenced nonexistent columns; the admin tool list was missing 27 tools."
      ],
      "notices": [
        "Two database migrations (0041, 0042) apply automatically at startup; both are idempotent and need no action.",
        "Access tokens are now stored hashed: your existing MCP token keeps working unchanged, but its value can no longer be displayed in the admin — from now on a value is shown only once, at the moment it is (re)generated.",
        "Not marked for unattended auto-apply: rolling back to 0.1.36 after this update would leave MCP personal tokens unusable until regenerated, because 0.1.36's code cannot verify hashed tokens."
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.37",
      "migrations": [
        "0041_site_page_revisions.sql",
        "0042_pat_hash_scopes_calllog.sql"
      ]
    },
    {
      "version": "0.1.36",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T14:05:00Z",
      "imageDigest": "sha256:94601bb50bb68ce59269cd9b11b6ffefabadc50571731f695ff5f913ccd92723",
      "title": "One theme, one pricing model, cleaner pages",
      "summary": "Consistency release. Setting a default theme now applies it everywhere at once (colours, cards and backgrounds used to be able to follow different themes). Pricing plans point at one product whose billing plans drive the monthly/yearly toggle. Old-format event pages are converted to the current builder automatically, with their rich text and reviews carried over. Emails follow your theme's brand colour even when it comes from the palette.",
      "bullets": [
        "Set as default applies the WHOLE theme — palette, cards, backgrounds and gradients can no longer follow different themes.",
        "A pricing plan references one product; its billing plans power the monthly/yearly switch, and one-time products show a plain price. The split desktop layout is selectable.",
        "Old-format event pages convert to the current page builder automatically at startup — formatting, FAQ items and review sections carried over, drafts and published versions kept apart.",
        "Testimonial sections on a product or event page can scope to that product or event, so reviews for one offer no longer appear under another.",
        "The product page shows upcoming live session dates when the setting is on (it was fetched but never shown).",
        "Emails pick up the brand colour from palette-based themes too.",
        "A theme can declare dark or light as its default look for first-time visitors.",
        "Half-filled grids centre their items instead of leaving empty slots, and long headings no longer end up as truncated menu items.",
        "The embed section's lazy-load toggle actually sticks now."
      ],
      "notices": [
        "Two small database migrations (0039, 0040) apply automatically at startup; both are idempotent and need no action."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.36",
      "migrations": [
        "0039_theme_pointer_backfill.sql",
        "0040_sections_doc_shape.sql"
      ]
    },
    {
      "version": "0.1.35",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T11:20:00Z",
      "imageDigest": "sha256:cbc4beb749f7f4d0003e35fcea5c3c3c6da11ab1e0358ce1237f96c3b83d17ec",
      "title": "AI assistants can check their work before it goes live",
      "summary": "Connected AI assistants get a full verify loop: an exact, self-checking schema for every section type, dry-run mode on every page edit, private preview links for unpublished drafts, and a site-wide health check that now covers custom pages, event pages and product sales pages too.",
      "bullets": [
        "New: ask for any section type's exact fields, allowed values and defaults — the answer is checked against the site's own validation as it is generated, so it can never be out of date.",
        "Every page and theme edit supports a dry run that reports exactly what would be saved, without saving anything.",
        "Preview links show an unpublished draft in the browser — signed, private, expiring after ~30 minutes, with a clear draft banner.",
        "The health check now audits all four page types (site, custom, event, product sales) and flags dead references, leftover placeholder text, and a disabled buy/registration section.",
        "Tool documentation now lists all 21 section types instead of 10."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.35",
      "migrations": []
    },
    {
      "version": "0.1.34",
      "date": "2026-08-14",
      "publishedAt": "2026-08-14T10:19:38Z",
      "imageDigest": "sha256:d9b7ee6c0f44c3617894499fb1485f7bb22025bc4d9f30080387de2f78635c6a",
      "title": "Connected AI assistants can now edit your whole site",
      "summary": "The MCP tools an AI assistant uses to build your site are repaired and hardened. Tools for pages, navigation, footer, theme, event pages and translations were refusing every connected assistant; they now work, every write is checked server-side, and the assistant is told exactly which fields were accepted. Renaming a product now keeps the old link working with an automatic redirect.",
      "bullets": [
        "Site pages, nav/footer/theme components, event pages and translations are editable by connected AI assistants (they previously returned Unauthorized).",
        "All ten structural pages are reachable, including Pricing, Terms and Contact.",
        "Edits are validated and sanitized like admin edits, and report per field what was applied, adjusted or rejected — invented fields can no longer be silently stored.",
        "Images set by an assistant actually render (they were stored under a key the site never read; existing broken configs self-repair).",
        "Editing an event page can no longer wipe a built page — event tools use the current builder format, and the legacy write path refuses newer pages.",
        "The builder tools work on a brand-new install instead of erroring before the first page exists.",
        "Renaming a product creates a permanent redirect from the old address, so shared links and ads keep working.",
        "Publishing a theme over MCP now actually marks it live."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.34",
      "migrations": []
    },
    {
      "version": "0.1.33",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T22:05:00Z",
      "imageDigest": "sha256:f383063311abcc2e8b5488b5d73f63b7236bf9255a72b63770b09f43ff78336d",
      "title": "Discount labels use your brand colour",
      "summary": "The savings badges were green, which matched nothing else on the page. They now use your own brand colour, in the same pill style as the rest of the pricing section.",
      "bullets": [
        "Savings badges on pricing sections, product pages and product cards all use your brand colour.",
        "On cover artwork the badge stays solid rather than tinted, so it is readable whatever the artwork looks like."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.33",
      "migrations": []
    },
    {
      "version": "0.1.32",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T22:05:00Z",
      "imageDigest": "sha256:ad5e04b246b7ea19ba09643f172ff962f6e4cebcd7cf127dc512c53e47308d71",
      "title": "The yearly saving sits on the yearly option",
      "summary": "The discount label moved onto the Annual button, so it reads as a reason to switch rather than a claim about the monthly price beside it.",
      "bullets": [
        "The saving is shown on the Annual option itself.",
        "A one-off product beside a subscription still shows no discount."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.32",
      "migrations": []
    },
    {
      "version": "0.1.31",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T21:35:00Z",
      "imageDigest": "sha256:51bbdbee388e03ca23bb5ec0f110048d3fe2322c05cefca9d163225f04804f3a",
      "title": "Clearer saving on pricing and product cards",
      "summary": "The yearly saving now shows on pricing sections that use the split layout, and the discount badges are readable against light artwork.",
      "bullets": [
        "The 'Save up to X%' badge now appears in the split pricing layout, next to the Monthly/Annual switch.",
        "The saving is worked out per product, so a one-off product never shows a discount it does not offer.",
        "Discount badges are solid rather than a light tint, so they stay readable on any cover artwork."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.31",
      "migrations": []
    },
    {
      "version": "0.1.30",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T21:35:00Z",
      "imageDigest": "sha256:3bad7cdee7c27359e6a12e3f4332f3fef1d261ec7a15435763b90d3a9cc8c0c0",
      "title": "Product cards show every billing option",
      "summary": "A subscription card now shows what it costs per month and lists each billing option, instead of printing a single price with no period. Card text and prices are larger.",
      "bullets": [
        "Cards show the lowest monthly cost, with each billing option and its saving listed beneath.",
        "Description and price are larger — the cover carries the whole offer now.",
        "Products with one price are unchanged."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.30",
      "migrations": []
    },
    {
      "version": "0.1.29",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T21:35:00Z",
      "imageDigest": "sha256:fdb0889a9e94afcca6315a8f11353302b7e663590794cda68270bfd9be974b6e",
      "title": "Pricing toggle works from one product, and a warning before you orphan a page",
      "summary": "A pricing card's monthly/yearly switch now reads the billing options from a single product instead of needing two separate products. Deleting or archiving a product also warns you when a page section points at it.",
      "bullets": [
        "A pricing section's Monthly/Annual switch works from one product with several billing options.",
        "Deleting or archiving a product now lists every page section that references it, so nothing silently empties.",
        "Pricing sections built from two separate products keep working exactly as before."
      ],
      "notices": [
        "If you archive a product that a pricing section points at, that slot is left empty — the dialog now tells you before you do it."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.29",
      "migrations": []
    },
    {
      "version": "0.1.28",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T20:10:00Z",
      "imageDigest": "sha256:c50cda05c7fd7853124c7dab6e75d67fce9d001c8cb708feb06fefb14dde7675",
      "title": "One product can offer monthly and yearly billing",
      "summary": "A subscription product can now offer several billing periods on one page — monthly and yearly side by side — instead of being duplicated once per period. Customers pick their period at checkout, and the yearly option shows what it costs per month so the saving is obvious.",
      "bullets": [
        "Add several billing periods to one subscription product in Product settings.",
        "Customers choose their billing period on the product page, with the per-month equivalent and the saving shown.",
        "Each period gets its own commitment length, so a yearly plan can require a longer commitment than a monthly one.",
        "Works with both Stripe and PayPal."
      ],
      "notices": [
        "Existing subscription products are unchanged: each keeps its current billing period, its price and its existing Stripe price and PayPal plan, now shown as a single billing option you can add to.",
        "Removing a billing option stops it being offered to new customers. Anyone already subscribed to it keeps their subscription — it is not cancelled."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.28",
      "migrations": [
        "0038_product_plans.sql"
      ]
    },
    {
      "version": "0.1.27",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T16:35:00Z",
      "imageDigest": "sha256:469a6a95af99e24471691c3f79e54e9103b88d67153777111b9b34341f2174c6",
      "title": "Product card fixes",
      "summary": "Three fixes to the new product cards: the top of a card is no longer clipped when you hover it, text on the cover artwork is readable everywhere, and the admin cards no longer show the same status twice.",
      "bullets": [
        "Hovering a product card no longer cuts off its top edge.",
        "Text on the cover — module and lesson counts, prices, purchase counts — is readable against the artwork.",
        "The publish button reads 'Published' in green and turns into a red 'Unpublish' when you point at it.",
        "The Live and Published labels no longer appear twice on a product card, and the 'Paid' label is gone — the price already says it."
      ],
      "notices": [
        "Archived products still show an 'Archived' label, since nothing else on the card indicates it."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.27",
      "migrations": []
    },
    {
      "version": "0.1.26",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T16:05:00Z",
      "imageDigest": "sha256:a14abcb1a7eddef7d2830feb6fbd1449fdc5b5f6eeb23bfe94cb7dbc87c92bf8",
      "title": "New product card artwork, and a tidier blog cover setting",
      "summary": "Product cards get new generated artwork — soft flowing light in your brand colour — with the title, description, price and button all sitting on it. Blog covers now default to your brand colours for new installs, instead of the old multi-coloured style.",
      "bullets": [
        "Product cards draw a soft, flowing field of light in your brand colour, different for every product.",
        "The whole card is the cover: type, title, description, price and a 'View details' button all sit on the artwork.",
        "Blog covers use your brand colours by default. The old multi-coloured 'vibrant' style has been retired."
      ],
      "notices": [
        "If your blog cover style was still set to 'vibrant', it now uses your brand colours instead. Posts with their own cover image are unaffected.",
        "Product cards look different again — this replaces the striped artwork from the previous release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.26",
      "migrations": [
        "0037_retire_vibrant_blog_covers.sql"
      ]
    },
    {
      "version": "0.1.25",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T14:28:00Z",
      "imageDigest": "sha256:17425997403201c4c562b4e5035dc902f80ff5631765598bd172063640420153",
      "title": "Product cards look the way the last release intended",
      "summary": "The previous release moved a product's title, price and description onto its cover, but the artwork faded out before the text began — so it still looked like a picture with a caption underneath. The artwork now stays visible behind the text, and a row of cards lines up evenly instead of one long description making its neighbours look short.",
      "bullets": [
        "The cover artwork now shows behind the title, price and description, rather than stopping above them.",
        "Cards in a row are the same height. Extra space goes to the artwork, so a card with a shorter description simply shows more of its cover.",
        "More artwork is visible on every card."
      ],
      "notices": [
        "This is a visual correction to 0.1.24 — no settings or content change, and nothing to do after updating."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.25",
      "migrations": []
    },
    {
      "version": "0.1.24",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T13:56:00Z",
      "imageDigest": "sha256:a19ba52ece22e6a5e7d2f722b8b726eaf378c789aacb844c1210d1ab03cc9b25",
      "title": "Product cards lead with the product",
      "summary": "A product card now carries its title, price and description on the cover artwork itself, with only the buttons underneath. The same card is used on your site, in your products list and in the builder, so all three look alike.",
      "bullets": [
        "Title, price and description sit on the cover instead of in a block beneath it.",
        "Cards are more compact, and the product is the first thing you read.",
        "The product type is no longer printed twice on cards whose type and payment method share a name.",
        "Cards grow to fit a long description instead of cutting it off."
      ],
      "notices": [
        "Product cards look noticeably different — the same information, arranged on the artwork rather than under it. Nothing was removed.",
        "Because the information sits on the artwork, less of the artwork is visible than in the previous release."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.24",
      "migrations": []
    },
    {
      "version": "0.1.23",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T13:10:00Z",
      "imageDigest": "sha256:d174be4a06fab322534d8d412d56c78865b0fc9419fbea3b0af112d26d7d4af5",
      "title": "Remove a product from the builder — without losing your sales",
      "summary": "You can now delete a product straight from the Product Pages list or from Builder settings. A product that has been sold is archived instead of deleted, so it leaves your site immediately while your orders, payments and invoices stay exactly where they are.",
      "bullets": [
        "Delete a product from its card in Product Pages, or from Builder settings.",
        "A product that has been sold is archived rather than deleted — it comes off your site, and every order behind it is kept.",
        "The confirmation now tells you exactly what will be removed, including how many purchases the product has.",
        "Archived products carry an 'Archived' badge, so they are no longer confused with unfinished drafts.",
        "Product cards in the builder now use the same generated cover as the rest of your site."
      ],
      "notices": [
        "Deleting a product that has sales is no longer possible from anywhere in the admin — it is archived instead. Previously this was allowed and would have deleted those orders, their payments and their referrals along with it.",
        "Bringing an archived product back is done by publishing it again from the Products page."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.23",
      "migrations": [
        "0036_product_archive.sql"
      ]
    },
    {
      "version": "0.1.22",
      "date": "2026-08-13",
      "publishedAt": "2026-08-13T04:11:30Z",
      "imageDigest": "sha256:d7e8f5a109a7e3c2bc04acc79ab25795526fac0414b3fc0ae4e0d0a96066a5fa",
      "title": "Product cards get a generated cover",
      "summary": "A product with no uploaded image used to show a grey package icon on a pink-tinted gradient — identical on every card, and tinted with a colour that was not yours. Cards now draw their own cover from your brand colour, different for each product, with the title and price on it.",
      "bullets": [
        "Products without a cover image get generated artwork instead of a placeholder icon — light rays and glows drawn from your brand colour.",
        "Each product's cover is distinct, and stays the same every time it is shown.",
        "The title, type and price now sit on the cover, so the card leads with the product.",
        "The admin product list shows the same cover as your visitors see.",
        "Products where you uploaded your own image are unchanged."
      ],
      "notices": [
        "Product cards will visibly change colour. The old cards had a pink tint hardcoded into them regardless of your brand; they now use your own primary colour. If your brand is not pink, this is what corrects it.",
        "The type badge and title no longer repeat in the card body — they are on the cover."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.22",
      "migrations": []
    },
    {
      "version": "0.1.21",
      "date": "2026-08-12",
      "publishedAt": "2026-08-12T14:50:08Z",
      "imageDigest": "sha256:c1dff3584464e96ca087c15bd714f74552ec477281546a49616c98a93d05d13b",
      "title": "Email improvements now reach sites that already exist",
      "summary": "Until now a release that only changed email wording did nothing on an install that was already running: system templates were created once and never updated again. Your templates are now brought up to date automatically — but only the ones nobody has edited.",
      "bullets": [
        "Wording improvements shipped in a release now apply to your existing email templates, including everything in 0.1.20.",
        "A template you have edited is never overwritten. Your version wins, permanently.",
        "New \"Reset to default\" action on each system template, for when you do want the shipped version back."
      ],
      "notices": [
        "Whether a template counts as edited is decided by comparing its content, not its timestamps. A template that was changed before this release is treated as edited and left alone — use Reset to default if you would rather have the new version.",
        "Adds a column to the email template table. Applied automatically on first boot."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.21",
      "migrations": [
        "0035_template_default_hash.sql"
      ]
    },
    {
      "version": "0.1.20",
      "date": "2026-08-12",
      "publishedAt": "2026-08-12T12:43:29Z",
      "imageDigest": "sha256:af24dc1c73bd8203d8445f8a27dc56ce522fdb9cb5fc5eaeecd14971e7321af3",
      "title": "Clearer, quieter transactional email",
      "summary": "A full pass over all ten system emails. The plain-text half of every message now hides the same optional rows the HTML half already hid, so nobody receives a line reading \"Total:\" with nothing after it. Purchase confirmations carry the licence key in plain text for the first time.",
      "bullets": [
        "Licence keys now appear in the plain-text purchase confirmation. Previously they existed only in the HTML version, so anyone reading in plain text never received the key they had paid for.",
        "Optional rows are hidden when empty across every plain-text body, matching the HTML.",
        "A dead \"Access your purchase\" button no longer renders for products that have no download, and the cancel/reschedule links no longer render when there is nothing to link to.",
        "Calmer wording throughout: seventeen exclamation marks removed, including from subject lines.",
        "Cancellation emails now link back to upcoming dates instead of ending nowhere."
      ],
      "notices": [
        "The account welcome email no longer shows a \"Temporary Password\" row or tells you to change your password. Nothing in the system has ever issued a temporary password, so that row was always blank and the instruction was always wrong.",
        "Applied on its own, this release only affects NEWLY created installs. Update to 0.1.21, which delivers these changes to sites that already exist."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.20",
      "migrations": []
    },
    {
      "version": "0.1.19",
      "date": "2026-08-12",
      "publishedAt": "2026-08-12T10:44:03Z",
      "imageDigest": "sha256:c9a99f6387fae03723f35f48c8ae6db0d2eb965abe250722112cb7149c200898",
      "title": "Abandoned checkout recovery, and no welcome email before payment",
      "summary": "A customer's account is created before they reach the payment page, so the welcome email was arriving while they were still deciding — telling people their account was ready before they had bought anything. It now goes out once payment succeeds. The same early account is what makes the new abandoned checkout email possible: switch it on for a product and anyone who starts a checkout without paying gets one reminder an hour later. It is off everywhere until you turn it on.",
      "bullets": [
        "A new 'Send an abandoned checkout email' switch on each product, off by default. Nothing is recorded and nobody is enrolled until you turn it on for a specific product.",
        "Anyone who pays within the hour is never mailed. The reminder waits for the payment rather than counting down blindly, and any purchase cancels it — someone who bought a single course instead of the bundle has not abandoned anything.",
        "One reminder, an hour later. Recovery drops sharply after the first hour and roughly halves by the next day, while a message arriving five minutes after someone closes a tab reads as surveillance.",
        "No discount, no countdown, no second and third chase. The email says their details are still there and links straight back, and tells them plainly that doing nothing ends it.",
        "Wording, timing and the whole flow are editable under Automations, as 'Abandoned Checkout'. It is an ordinary automation, not a hidden behaviour.",
        "The welcome email now sends after payment instead of when a contact appears — so nobody who merely leaves an email address is told they have an account and a dashboard to sign in to."
      ],
      "notices": [
        "Buyers now receive the welcome email alongside the purchase confirmation. They carry different things — the login and the receipt. If one is enough, switch either off under Automations.",
        "If you had edited the Welcome Email automation, your changes are kept. Only its trigger moves; the steps, wording and branches are left exactly as you set them.",
        "The reminder counts from when checkout starts, not from when someone clicks 'back' on the payment page — most people who abandon simply close the tab, and waiting for them to return would miss them."
      ],
      "breaking": false,
      "security": false,
      "safe": true,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.19",
      "migrations": [
        "0033_abandoned_checkout.sql",
        "0034_welcome_email_on_payment.sql"
      ]
    },
    {
      "version": "0.1.18",
      "date": "2026-08-12",
      "publishedAt": "2026-08-11T22:58:17Z",
      "imageDigest": "sha256:666e5e485839af86b41b9af7f65f264368d290ce4e12c5a907d085c0dda8dcb8",
      "title": "Your emails now use your brand colour",
      "summary": "Email templates kept the colours they were created with, so changing your site's accent changed the website and nothing else. Your accent is now applied when each email is sent, and stale palettes on existing installs are brought up to date without touching a word of your content.",
      "bullets": [
        "Emails take their brand colour and link colour from your site theme, applied at send time — so rebranding reaches your customers' inboxes without editing a single template.",
        "Only the brand colours are taken. Text, background and border stay with the template, so an accent chosen to stand out cannot end up painting a background and making mail unreadable.",
        "If your accent is a saved colour variable, its dark-mode value is used as you defined it. Otherwise a lighter version is worked out, because a colour picked against a white page is usually too dark on a dark one.",
        "Templates created before this release get the current palette on the next restart. Only the palette — every heading, wording and block you have arranged is left exactly as it is.",
        "A detail line whose value is missing no longer prints a stray label. The welcome email in particular was showing \"Temporary Password:\" with nothing after it whenever the account came from checkout."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.18",
      "migrations": []
    },
    {
      "version": "0.1.17",
      "date": "2026-08-12",
      "publishedAt": "2026-08-11T22:11:29Z",
      "imageDigest": "sha256:87e93c83f48a992a35397dbfab0911a62b31ddb02451af646aba1c271dc025a2",
      "title": "The withdrawal waiver is now recorded in two places",
      "summary": "The waiver a buyer accepts at checkout was stored only with the payment provider. It is now also written to your own consent log, with the buyer's IP address and browser, and the two records reference each other.",
      "bullets": [
        "Your consent log gains a withdrawal_waiver entry for each accepted waiver, holding the exact wording shown, the product, the buyer's email, IP and browser, and the payment session it belongs to.",
        "Two independent records from two systems is materially better evidence in a dispute than one — and only your own record carries who accepted it, not just that someone did.",
        "Writing the record can never cost you a sale: if the log write fails the purchase continues, and the copy held by the payment provider still exists."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.17",
      "migrations": []
    },
    {
      "version": "0.1.16",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T21:59:00Z",
      "imageDigest": "sha256:65373d3cd461b880faccae352d789d35bf357b668b4dfc5fbe343a02b5985166",
      "title": "Checkout now captures the withdrawal waiver for digital sales",
      "summary": "If you sell digital products to consumers in the EU, their 14-day right of withdrawal only ends when they have expressly asked you to start immediately and confirmed they know that costs them the right. Checkout now asks, and records the answer.",
      "bullets": [
        "A required tick-box at checkout captures both halves of the waiver (§ 356 (5) BGB). The buy button stays disabled until it is ticked, because without it the withdrawal period never starts — and then runs for twelve months rather than fourteen days.",
        "The acceptance, the time and the exact wording shown are stored on the Stripe checkout session, so the proof sits with a third party and its own clock rather than only in your database.",
        "It sits beside the optional marketing tick-box and behaves in the opposite way on purpose: marketing consent must never be a condition of sale, this one legally must be."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If you sell only to businesses, the waiver is unnecessary but harmless. If you sell to consumers outside the EU, check what your own jurisdiction requires — this implements the German and EU rule."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.16",
      "migrations": []
    },
    {
      "version": "0.1.15",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T21:18:04Z",
      "imageDigest": "sha256:f42e0a4c93269952c084f03a892442b2f198dd77833f84bd68990f61f42349bc",
      "title": "Adding a legal link starts blank instead of pre-filled",
      "summary": "\"Add legal link\" used to create a link already pointing at /privacy, so adding a Terms link and typing a new label left it going to the wrong page. New links now start empty.",
      "bullets": [
        "A new legal link starts with an empty label and URL. An empty field asks to be filled in; a plausible wrong value does not.",
        "If you added a legal link before this release, check where it actually points — the label may say one thing and the URL another."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.15",
      "migrations": []
    },
    {
      "version": "0.1.14",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T20:58:31Z",
      "imageDigest": "sha256:72d1b250182de6837f7b3965e70eaec6650da5370d7a1001491b844be8dc5edc",
      "title": "Legal links stay hidden on every page, not just some of them",
      "summary": "Links to an unpublished privacy, imprint or terms page were correctly hidden on the collection pages and still shown on the home page. They are now hidden everywhere.",
      "bullets": [
        "Every page hides links to legal pages you have not published yet — previously only some pages did, and the home page was not one of them.",
        "Legal pages now come with drafted starting content instead of an empty scaffold, so publishing one is a matter of reviewing and filling in your own details."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "Drafted legal text is a starting point, not legal advice. It leaves the details only you can supply clearly marked, and the withdrawal instructions for consumer sales in particular should be reviewed by a lawyer before you sell."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.14",
      "migrations": []
    },
    {
      "version": "0.1.13",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T19:00:44Z",
      "imageDigest": "sha256:9667b25d9415155a9c47ab4de665d414390d6b9a6168ceb6310e34c75b46d4ac",
      "title": "The legal-link switch now sticks, and the preview matches the site",
      "summary": "Turning a legal link on appeared to work, then reverted the next time you opened the editor — and the preview showed links your visitors never saw. Both come from the same place now, so what you see in the builder is what the site does.",
      "bullets": [
        "Switching a legal link on is saved. It was being discarded on autosave, so the site never received it and the editor reopened with the switch back off.",
        "The builder preview hides exactly what the live site hides. The rule lived in two places and they disagreed; there is now one, with both surfaces feeding it the same information."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If you switched a legal link on before this release and it did not stay on, switch it on once more — it will hold this time."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.13",
      "migrations": []
    },
    {
      "version": "0.1.12",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T17:20:27Z",
      "imageDigest": "sha256:af016fad04159a999b648ccd010ef0941b37bf85c5da7938e5675b9d81997aa5",
      "title": "The show/hide switch on legal links now works",
      "summary": "Switching a legal link off left it on the site, and switching one on for an unpublished page did nothing. Both are fixed, and the switch now shows what your visitors will actually see rather than just what is stored.",
      "bullets": [
        "Turning a legal link off hides it. The footer draws its legal row differently in its two layouts and only one of them was honouring the switch — the default layout was the one ignoring it.",
        "Turning a link on now wins, even when the page behind it is not published. Useful when the page lives elsewhere or goes live in an hour; the automatic hiding only applies to links you have not decided about.",
        "The switch reads as off when the page is not published, because that is what the site does — and the editor tells you which page is missing instead of leaving you to guess."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.12",
      "migrations": []
    },
    {
      "version": "0.1.11",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T16:56:11Z",
      "imageDigest": "sha256:368e1b868974e824a49004c66ce137277124ce8053f96e3e3254f8f237f91468",
      "title": "Legal links that only appear once the page behind them exists",
      "summary": "Your footer no longer links to a privacy, imprint or terms page you have not written yet — those links stay hidden until the page is published, so nobody lands on a 404. There is also a new Terms page, an on/off switch on every legal link, and the address block is on by default.",
      "bullets": [
        "New Terms page at /terms, alongside Privacy and Imprint. Rename the link to \"AGB\" or anything else in the builder — the address stays the same, so nothing you have published breaks.",
        "Legal links appear only once their page is published. Until then the footer simply does not show them.",
        "Every legal link has its own show/hide switch, separate from the automatic hiding — the switch is your choice, the automatic part is whether the page exists.",
        "The contact and address block under the footer logo is on by default. In several countries publishing an address is a requirement rather than a preference.",
        "Legal pages now open with a text block ready to edit, instead of an empty page.",
        "Your business name reaches the navigation and footer even on a brand-new install, where the site is built before you have entered it."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If your footer previously showed a legal link and it has disappeared, that page is not published yet — publish it under Pages and the link comes back on its own."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.11",
      "migrations": []
    },
    {
      "version": "0.1.10",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T16:28:49Z",
      "imageDigest": "sha256:a86d33eba870c64e5a3499f434d1669b62fa2960f868de8093cc73ebaae66021",
      "title": "Your navigation, footer and theme are now yours to edit",
      "summary": "A new site rendered a navigation bar, footer and theme — but the admin showed \"No navs yet\" and \"No theme found\", so there was nothing to open and nothing to change. They now exist as real, editable components from the first boot, and any install already in that state repairs itself on restart.",
      "bullets": [
        "Site Components lists a navigation and a footer you can actually edit, instead of an empty screen beside a site that visibly has both.",
        "Theme Studio opens your site theme instead of reporting that none exists.",
        "Your business name is filled in from setup, in the navigation wordmark and the footer copyright.",
        "Existing installs get these on the next restart — nothing to run, and anything you have already created is left exactly as it is."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "The seeded navigation contains only a Home link. Add the rest in the builder — links to a blog, products or events page stay hidden on the live site until that section has something in it."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.10",
      "migrations": []
    },
    {
      "version": "0.1.9",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T16:12:45Z",
      "imageDigest": "sha256:48800bfe504675df41971f0a665ee9aab7dcb3e0a187fed92fdec2d503b43194",
      "title": "A new site shows your name, and a way to sign in",
      "summary": "Two things a fresh install got wrong in its own navigation bar: it showed the placeholder \"Your brand\" instead of the business name you entered during setup, and it offered no way to sign in or reach an account.",
      "bullets": [
        "The navigation and footer now use your business name — it was already stored from setup, and showing a placeholder made a working install look unconfigured.",
        "The account affordance is on by default: the profile menu when you are signed in, a \"Sign in\" link when you are not. Courses, licences and orders all live behind a login, so a bar with no way in was a brochure.",
        "Only the built-in defaults changed. A navigation bar you have created and saved is yours — placeholder text included — and is never rewritten."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If your site has a saved navigation bar that never had the account setting touched, it will now show the sign-in / profile affordance. Bars where the setting was set explicitly — including deliberately off — do not change. You can switch it off per bar in the builder."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.9",
      "migrations": []
    },
    {
      "version": "0.1.8",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T15:47:25Z",
      "imageDigest": "sha256:44dead2924143153dff199a744f64d5ca583b58c54971485e701b47037f7b3e2",
      "title": "Empty collections stop advertising themselves",
      "summary": "If your site links to a blog, products or events page that has nothing in it yet, those links now disappear from the navigation and footer until there is something to show. Plus a fix for sections whose heading had been cleared, which were rendering an empty title element.",
      "bullets": [
        "Navigation and footer links to an empty blog, products or events page are hidden until that collection has content. A visitor who clicks through to nothing has been actively misled; a link that is simply absent costs them nothing.",
        "This is the same \"Hide when empty\" switch the sections use, read from the collection page itself — so on a collection page that switch now means \"advertise this only when there is something in it\".",
        "The collection page itself is never hidden. /blog with no posts still says so; it just is not linked to from the chrome.",
        "A section whose heading had been cleared rendered an empty title element — invisible, still taking its space, and announced by screen readers as an empty heading. It is now omitted, and shown greyed in the builder so you can still click it to type one back in."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If a navigation link you expect is missing, the collection behind it is empty. Add a post or product, or switch \"Hide when empty\" off on that collection page's section to keep the link visible regardless."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.8",
      "migrations": []
    },
    {
      "version": "0.1.7",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T13:05:00Z",
      "imageDigest": "sha256:231075afea397c49b36892120f144fd1d6f04cbcb970e0ee6fa8beec2e7ee946",
      "title": "A cleared heading no longer renders an empty title",
      "summary": "A section whose heading had been emptied still rendered a title element with nothing in it — invisible, still taking its space, and announced by screen readers as an empty heading. It is now left out entirely.",
      "bullets": [
        "Clearing a section's heading removes the heading, instead of leaving a blank one behind.",
        "In the builder the heading still appears, greyed, so you can click it and type a new one — a setting you can remove but never restore is worse than the original problem."
      ],
      "notices": [],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.7",
      "migrations": []
    },
    {
      "version": "0.1.6",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T11:52:00Z",
      "imageDigest": "sha256:e1005aec17e45781040c0c854c66bd323587d17ef9628fa7140058069335633c",
      "title": "Housekeeping: faster releases and a renamed repository",
      "summary": "No behaviour changes. Releases are now built on native hardware for each processor architecture instead of emulating one of them, which took a release from about 30 minutes to about 5, and the project repository was renamed to match the product.",
      "bullets": [
        "Releases build roughly six times faster. Nothing about the resulting image changes — it is still multi-architecture and still signed the same way.",
        "The repository rename is invisible from your side: the registry address, your licence key and the pull command are all unchanged."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "Update signature verification now accepts both the old and new project names, so releases published before and after the rename both verify. Nothing to change on your install."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/lasersite/releases/tag/v0.1.6",
      "migrations": []
    },
    {
      "version": "0.1.5",
      "date": "2026-08-11",
      "publishedAt": "2026-08-11T11:38:04Z",
      "imageDigest": "sha256:83d65f83509058aed486717cc9925fdf400753a70377d6a42b55d8180d741661",
      "title": "A new site now looks like a site",
      "summary": "A fresh install used to render with no navigation and no footer, followed by four headings whose only content was \"No products yet.\" The chrome is fixed, empty sections now hide themselves instead of advertising an absence, and you can add example content during setup — or from Settings — and remove it again in one click.",
      "bullets": [
        "Navigation and footer appear on a new install. They were always in the page, but pointed at a site-wide default record that nothing ever created, so they rendered as nothing.",
        "Sections with nothing in them hide themselves on the live site. Products, reviews, posts and events — previously only events could do this, and it was off by default.",
        "Collection pages are exempt: /products with no products still says so rather than serving a blank page, and a blog filtered to a tag with no matches keeps its filter chips so you can get back.",
        "You can still show an empty section deliberately — switch \"Hide when empty\" off on that section and it stays put.",
        "Sample content is offered as a checkbox during setup, and can be added or removed any time from Settings → General.",
        "Removing sample content leaves anything you have edited. If you turned an example product into a real one, it stays, and the count tells you so."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "Sections you had already switched off by hand stay off — this changes what an ENABLED but empty section does, nothing else. If a section you want visible disappears, its list is empty: switch \"Hide when empty\" off on that section."
        },
        {
          "kind": "info",
          "text": "No sample reviews are ever created. A made-up testimonial on a live site is fabricated social proof rather than a placeholder, so the testimonials section hides itself until you have a real one instead."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.5",
      "migrations": [
        "0032_demo_content"
      ]
    },
    {
      "version": "0.1.4",
      "date": "2026-08-11",
      "publishedAt": "2026-08-10T23:00:27Z",
      "imageDigest": "sha256:b7ca57cf0d1fab40f162278c5c5a067a4cb85808ae78fe9ad42450e294e97843",
      "title": "Licence gets its own settings tab, and Fly installs get a one-command update",
      "summary": "The licence was filed under Contacts, which is where it least belongs — it has nothing to do with contacts, and it is the one setting the Updates screen sends you to when a subscription lapses. It now has its own tab, and the Updates screen links straight to it instead of reciting a path. Separately, if you host on Fly.io there is now a script that applies a release in one command and refuses to deploy an image whose digest does not match the published one.",
      "bullets": [
        "Settings → Licence is its own tab. Something you are told to go and check should be findable by its own name.",
        "The lapsed-subscription notice links directly to it, and says why a re-check exists at all: the verdict is cached, so a renewal is not noticed straight away.",
        "New: scripts/update-fly.sh — applies a release on Fly.io in one command. It reads the release manifest, verifies your licence before doing anything slow, copies the image into Fly's registry, checks the digest, deploys, and reports what the machine ended up running.",
        "The digest check is a hard stop, not a suggestion. If the copied image does not match the published digest it is no longer the artefact that was signed, and nothing is deployed.",
        "A lapsed licence now produces a sentence about your subscription instead of a registry error from the middle of a file transfer.",
        "The script never echoes your licence key, keeps credentials in a temporary file removed on exit, and uses an app-scoped 30-minute Fly deploy token rather than your account token."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "Only Fly.io needs the extra step. Everywhere you control the Docker daemon — a VPS, Docker Compose, Coolify, Dokploy, Kubernetes — a plain `docker login` and `docker pull` is the whole update. Fly will not hold a third-party registry credential, so the image has to be copied into its registry first."
        },
        {
          "kind": "info",
          "text": "If a pull ever fails with \"denied\" or \"Authentication required\", check Settings → Licence before assuming the worst. On Fly that error is usually the missing copy step rather than anything wrong with your licence."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.4",
      "migrations": []
    },
    {
      "version": "0.1.3",
      "date": "2026-08-10",
      "publishedAt": "2026-08-10T21:38:33Z",
      "imageDigest": "sha256:5689e8e2974e0dce6fb3153e674ebbf0548a4d5c8c6553bcf14c3342d675ba42",
      "title": "The update command now points at a registry you can actually pull from",
      "summary": "If you run this as a container, the Updates screen was giving you a pull command that could not work: it named the public build repository, where your licence is not a credential, instead of the registry your purchase email told you to log in to. It also left the image digest as a placeholder inside a copy-and-paste box. Both are fixed, and the command now includes the login step it always needed.",
      "bullets": [
        "The pull command uses your licence registry when one is configured, so the credentials you were given at purchase are the credentials the command expects.",
        "The real image digest is written into the command instead of \"<imageDigest from the release above>\". Copying it now does what copying it looks like it should do.",
        "The `docker login` step is shown when the registry is licence-gated — without it the pull is refused with nothing explaining why. It is printed without your key, because this screen is one screenshot away from being shared.",
        "Set UPDATE_IMAGE_REPO if you mirror the image yourself; it takes precedence over everything else."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "Nothing to do before updating. If your previous pull failed with \"denied\", that was this bug and not your licence."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.3",
      "migrations": []
    },
    {
      "version": "0.1.2",
      "date": "2026-08-10",
      "publishedAt": "2026-08-10T19:42:37Z",
      "imageDigest": "sha256:c47a4d9c08331f0de6ad710519104ad7e073aa9109f6070953d3200bb54fddf1",
      "title": "Licence-aware updates, visitor geography, and a transactional email rebuild",
      "summary": "This install can now tell you whether it is still entitled to updates, instead of leaving you to discover it at the pull. Alongside that: every visitor's location is recorded and every contact keeps an IP history you can age out on your own schedule, the buy button shows the payment methods Stripe will actually accept, and the transactional emails were rebuilt to survive Outlook and dark mode.",
      "bullets": [
        "Settings → Licence: store this install's licence key and see, in plain words, whether updates are included and until when. The key is encrypted at rest and never sent back to the browser once saved.",
        "The Updates screen now explains itself. Release notes stay visible whatever your subscription state — only the apply instructions are swapped for a renewal prompt when entitlement has lapsed.",
        "Visitor geography: country, region, city and timezone are recorded on every route, for anonymous visitors and signed-in contacts alike.",
        "Each contact gains an IP tab — every address they have used, when it was last seen, whether it is still active, and whether it is shared with another contact.",
        "Opt-in cleanup for that history: off by default, with a configurable \"not used for N days\" threshold. Nothing is deleted unless you switch it on.",
        "Products show the payment methods your Stripe account actually accepts — card marks, Apple Pay, Google Pay, Amazon Pay, giropay, Klarna — read live from Stripe rather than hardcoded.",
        "A gateway with no working credentials is no longer offered at checkout. It used to be selectable and then fail at the last step.",
        "Transactional emails rebuilt: one palette shared by both renderers, a real dark mode, no CSS gradients (Outlook drops them), a filled <title>, and a preheader. The purchase email carries the licence key as a proper panel.",
        "Mailgun's status is now verified against Mailgun on each check, instead of trusting a flag stored when it was first set up. A green light that had gone stale used to stay green.",
        "The site builder speaks German: navigation labels are translatable and the shipped copy has a /de counterpart.",
        "A `split` pricing layout for one-time plans — one panel, up to four columns, frameless inside the shared surface.",
        "MCP builder tools v2: surgical page, section and theme edits instead of whole-document rewrites."
      ],
      "notices": [
        {
          "kind": "info",
          "text": "If you bought this template, your licence key was in the purchase email. Saving it under Settings → Licence is optional — nothing stops working without it, you simply will not see why an update is or is not available to you."
        },
        {
          "kind": "info",
          "text": "Visitor geography and IP history are recorded from this version on. Existing contacts start with an empty history rather than a backfilled one, because the data to backfill it honestly does not exist."
        }
      ],
      "breaking": false,
      "security": false,
      "safe": false,
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.2",
      "migrations": [
        "0029_visitor_geo_and_ips",
        "0030_ip_retention",
        "0031_install_licence"
      ]
    },
    {
      "version": "0.1.1",
      "date": "2026-08-05",
      "title": "Zero-downtime deploys and a health check that can fail",
      "summary": "The readiness probe now checks the database and that migrations completed, so a broken instance is held back instead of quietly serving. Deploys are blue-green: the replacement is verified healthy before traffic moves, and rolls itself back if it is not.",
      "bullets": [
        "Readiness probe verifies the database AND that migrations finished — a half-migrated instance no longer serves traffic",
        "Blue-green deploys: traffic moves only after the new instance passes its health check, and reverts automatically if it never does",
        "Update windows — choose the hours during which an unattended update may run"
      ],
      "breaking": false,
      "security": false,
      "migrations": [
        "0007_update_window.sql"
      ],
      "notices": [],
      "safe": false,
      "imageDigest": "sha256:95a78b2c36869f7e05656ddee5258a748f90c5a0e7cc4ad9ff67a54143fdcc76",
      "publishedAt": "2026-08-05T13:17:14Z",
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.1"
    },
    {
      "version": "0.1.0",
      "date": "2026-08-05",
      "title": "Baseline",
      "summary": "The first version that carries an update channel. Instances on this release can see, read and inspect everything published after it.",
      "bullets": [
        "Update checks: on by default, switchable off, and they send nothing that identifies your install",
        "Updates are always offered and never applied for you — you read what changed, inspect the diff, and decide",
        "Database migrations apply automatically when you deploy, so pulling an update is a single step"
      ],
      "breaking": false,
      "security": false,
      "migrations": [],
      "notices": [
        {
          "kind": "info",
          "text": "The image is private. Log in to ghcr.io with the token from your licence before pulling: echo $TOKEN | docker login ghcr.io -u <your-github-user> --password-stdin"
        }
      ],
      "safe": false,
      "imageDigest": "sha256:9b245fb5521944070af3b0f52bf77716575df28ec4077efd008f28a8b6f5dc1a",
      "publishedAt": "2026-08-05T12:53:19Z",
      "changelogUrl": "https://github.com/busybilla/business-crm/releases/tag/v0.1.0"
    }
  ],
  "revoked": [],
  "minimumVersion": null
}
