Roadmap

Where the platform is: everything shipped so far, the limits we know about, and what we're thinking about next. Recent releases are on the changelog.

Shipped

55

Live for every site today.

  • Stripe payments

    Checkout for plans (subscriptions) and credit packs, signature-verified webhook (/api/billing/webhook) with event dedupe, invoice.paid renewals, cancel-at-period-end. Keyless installs keep the instant dev-mode grants.

  • Password reset

    emailed single-use 30-minute links, session invalidation on reset, scripts/mint-reset.ts for operators without email.

  • Domain verification

    rainbow-verify DNS TXT challenge; only verified domains serve.

  • Live preview

    side-by-side draft iframe in the editor (desktop/mobile widths), reloads on save.

  • Media uploads

    local-disk storage, /media/<id> serving, editor upload + library picker, Claude-written alt text (light model, metered).

  • Blog/news

    posts with AI drafting, /blog + detail pages, BlogPosting JSON-LD, automatic News nav link, sitemap/llms.txt/RSS integration, and a "Latest posts" section.

  • Church specials

    sermon podcast RSS (/podcast.xml from sermon media URLs), video/livestream section (YouTube/Vimeo, privacy-enhanced embeds), weekly bulletin section.

  • Static export

    one-click zip of the published site (Next runtime stripped, CSS bundled, links relativized, media included, vanilla consent script, README). Forms keep posting back to the platform.

  • GA4 dashboard

    service-account Data API (no SDK), 28-day users/views/sessions, daily chart, top pages, in a new Analytics tab.

  • Model tiering

    a main model for generation plus a cheaper light model for rewrites and alt text, both set per provider in /admin.

  • Seasonal copy refresh

    per-site schedule (monthly/quarterly) with standing instructions, optional auto-publish, in-process scheduler (instrumentation.ts), and a "Run once now" button.

  • Brand import

    upload a logo/screenshot in the wizard; Claude vision extracts palette/font/tone and pins them for generation.

  • Multi-provider AI

    twelve providers behind one interface, all configured in /admin rather than the environment: key, model, light model, base URL, billing rates, and image support per provider. Keys sealed at rest, never in the page payload. Per-user overrides pin one account to its own provider. Prompt logging to JSONL, off by default in production.

  • Generation recovery

    a heartbeat plus a five-minute silence threshold detects jobs whose process died, so nothing sits on a spinner that can never resolve. A site with no valid draft gets a screen naming the failure and offering retry-from-the-saved-brief or delete; the wizard hands over to it rather than duplicating it.

  • Contact & location tab

    the site-wide contact block feeding the contact section, the footer, and the business JSON-LD is editable by hand instead of being frozen at whatever generation produced.

  • URL redirects

    siteredirects + siteslughistory, matcher with wildcards, admin CRUD + CSV import, hits, Netlify redirects in the export, 308 from old site slugs. Wave 3: page/post/product slug renames call queueRedirect().

  • Email outbox

    retries, HTML/sender/header support, emailoutbox tick and admin Delivery card (enqueueEmail in src/lib/email-queue.ts; campaign rows still need the verified-from refusal once campaigns land).

  • Newsletter subscribers

    statuses, tags, CSV import/export, Audience tab, Brevo bounce webhook (wave 1). Public signup form, double opt-in confirm and unsubscribe links come in wave 2 (upsertSubscriber already mints and returns the raw confirm/unsub tokens for that flow).

  • Store settings

    sealed payment credentials (Stripe Connect/BYOK, Razorpay), tax and shipping rules, order notification recipients, admin-only Store tab (src/lib/commerce/store.ts, StorePanel.tsx).

  • Product catalog

    products table, CRUD route, Products tab, draftproduct AI op with mock parity, npm run seed:store <slug>. Variants, storefront and checkout UI follow in later waves.

  • Commerce wave 1

    merchant Stripe checkout spike: Connect Standard onboarding route, appendable checkout step pipeline, POST /api/public/checkout buy-form contract, separate merchant webhook with dedupe-after-success, createPendingOrder/markPaid/ONPAIDHOOKS. Next: storefront pages + Orders tab (wave 2), tax/shipping/discount steps and order mails (wave 3), subscriptions/PWYW (wave 4). Integrator still owes the real test-mode 4242 transcript in docs/COMMERCE.md.

  • Pricing tiers

    plans.limits and planForUser()/limitsForPlan() describe every quota (sites, pages, posts, AI credits, subscribers, leads, team members, storage, upload size, collections) and flag (BYOK, watermark, directory, priority support); monthly or yearly prices from the plans table, public /pricing, admin plan editor. Plan limits enforcement (wave 2): the editor, publish, uploads and invites should read them and offer the next plan up when a limit is hit. Follow-ups: a subscriptions.interval column so dev-mode renewals of a yearly plan stay yearly, and monthly credit grants for yearly Stripe subscriptions (today invoice.paid grants monthlyCredits once per invoice).

  • API keys

    per-user rl tokens with scopes / site pinning / expiry, hashed at rest, requireSiteAccessFromRequest Bearer path (posts route adopted; other routes opt in per scope as they need it).

  • MCP server (waves 1–2)

    /api/mcp transport spike with the projects pack, InMemory tests and transport findings in docs/MCP.md. Wave 2 adds the publish pack (getpublishstatus, publishsite, unpublishsite — admin role plus the publish scope, over the shared publishSite/unpublishSite in src/lib/publish.ts with via: "mcp", so the plan gate, audit rows and after-publish hooks are the REST route's) and the analytics pack (gettraffic, getlivetraffic, getga4summary — analytics scope, the same numbers the Traffic panel draws). Next: site/blog content pack (wave 3), commerce/directory pack (wave 4) — each is one module under src/lib/mcp/tools/ plus one line in TOOLPACKS; switch enableJsonResponse off if a long-running tool wants progress notifications (SSE verified working through the route handler).

  • Custom form builder

    form section, per-form validation, sealed submissions + anonymised IP prefixes (npm run reseal:submissions for old rows), Forms tab grouped by form with search/pagination/spam/delete/CSV export. Lead plan caps land with plan-limit enforcement.

  • Visual editor

    native HTML5 drag-and-drop outline for pages and sections (pure reorder/dropIndex/followIndex in lib/spec/reorder.ts), a same-origin postMessage bridge (rl-select / rl-goto / rl-edit / rl-ready) linking preview clicks to the outline both ways, contenteditable text edits validated by applyInlineEdit before they touch the spec, and 1.5s-debounced autosave driving the preview. The bridge is the only client component a generated site can load, and only in draft preview.

  • Outbound webhooks

    webhookendpoints / webhookdeliveries, emitSiteEvent() from forms, subscriber confirmations, post and site publishes, domain verification and paid orders; HMAC-SHA256 signatures over <timestamp>.<body>, a 60 s delivery tick with 1m/5m/30m/2h/12h backoff, admin CRUD + delivery log + retry in Integrations, and docs/WEBHOOKS.md with copy-paste verifiers for Node and Python.

  • Sending domain

    sendingdomains plus the Brevo Senders & Domains client (src/lib/mail/brevo-domains.ts, injectable fetch) and the pure DNS rules in src/lib/mail/dns.ts: expected-record generation (DKIM + ownership from Brevo, SPF and p=quarantine DMARC added here, optional MX placeholder for the wave-4 inbox) and per-record matching resolved through node:dns. Admin route /api/sites/[id]/sending-domain (rate-limited verify, newsletter.domain audit) and the Settings → Sending domain card. resolveFrom(siteId) answers an address only once DKIM and SPF both verify — the contact-form notification uses it today, campaigns / order mail / inbox call the same function — and a later check that finds those records gone clears the verification so nothing sends unsigned. Record semantics, propagation lead time and what still needs proving on a live domain: docs/EMAIL.md.

  • Newsletter signup with double opt-in

    newsletterSignup section (banner/boxed, optional name field and consent line) plus a footer.newsletter toggle; POST /api/public/subscribe (honeypot + 5 per 10 min per IP/site, utm taken off the referer) queues the confirmation mail through the outbox with List-Unsubscribe / List-Unsubscribe-Post headers; GET /api/public/newsletter/confirm promotes the row and emits subscriber.confirmed; GET/POST /api/public/newsletter/unsubscribe serves the RFC 8058 one-click endpoint. Works identically on /s/<slug> and a custom domain, and the static export rewrites the form action to the platform URL.

  • Storefront pages

    the shop route family serves /shop, /shop/<product> (clean / showcase / editorial / compact / bold templates), /shop/success and /shop/cancel on both the shared domain and a custom one. Product pages emit Product + Offer/AggregateOffer and BreadcrumbList JSON-LD with stock-derived availability, honor seo.canonical/seo.noindex, and contribute to the sitemap, llms.txt and the static export. The buy form is a native POST to the wave-1 checkout pipeline with a <select name="variantId"> when the product has variants; products sold elsewhere link through /api/public/checkout/external/<productId>, which records an external/unknown order and 302s to the merchant. Still to come: pay-what-you-want and subscription buy paths (the form is only rendered for fixed-price store products today), and a full test-mode Stripe purchase end to end.

  • Orders tab — done (wave 2).

    order-status.ts holds the lifecycle as pure data (ORDERTRANSITIONS, planStatusChange, planTracking, orderTimeline), so the panel's buttons and the API's refusals cannot drift apart; orders.ts adds setStatus / setTracking / addNote / listOrders / createManualOrder, and the four order emails (confirmation, merchant, shipped, canceled) queue through the outbox. Still to come: a partial-refund amount in the UI (today a hand-marked refund is the whole order, and partial refunds only arrive from the provider through applyRefund), a receipt link on webhook-paid confirmations (only the token's hash is stored, so the link can only be built by the caller that minted it), and real restocking once variants.restockOrder lands.

  • Variants, SKUs and inventory

    option axes (max 3 × 20 values, 100 combinations) generate productvariants by cartesian product; regenerating reconciles on the combination so a surviving variant keeps its SKU, price and stock. Per-variant SKU (unique within the product, blank stored as NULL), price override, stock count, trackStock, allowBackorder and active/disabled status. variantStep in the checkout pipeline resolves the buy form's variantId, refuses a sold-out or disabled one before any provider call, and stamps the variant id, SKU and option-suffixed title onto the line item. adjustStockForOrder rides ONPAIDHOOKS; an oversell is recorded as an order note plus a store.oversold audit row rather than failing the webhook. restockOrder returns the units of a canceled or refunded order.

  • Plan limits

    src/lib/limits.ts counts an account's usage (sites, published sites, pages, posts, team seats, leads, subscribers, storage) and gates the routes that create each of them on the site owner's plan, so an editor on someone else's site is measured against that owner's quota. The publish gate rides BEFOREPUBLISHHOOKS, so REST, MCP and the scheduler all refuse alike. Visitor data soft-fails (overquota) instead of being lost.

  • Blog categories & tags

    postcategories / posttags with their link tables, managed from the Posts tab (CategoriesCard for categories; tags are minted from the post's tag box and swept up when unused). Archives are served by the blog route family on both the shared domain and custom domains, and a blogList section can be pinned to one category with categorySlug. Still open: per-term RSS feeds, paginated archives, and a redirect when a category is deliberately re-slugged.

  • Per-page & per-post SEO

    page.seo.canonical / noindex / nofollow / ogImage in the spec plus the matching post columns, honored by buildPageMetadata, the blog family's metadata, sitemap.xml and llms.txt (noindex pages and posts stay reachable but unlisted). Posts without a picture share a generated card at /og/blog/<slug>.png, served on both route families and bundled into the static export. Editor: a per-page SEO card with a SERP preview in the SEO tab, and an "SEO & sharing" card in the post editor.

  • Scheduled post publishing

    posts can be parked at a future date and time from the editor (amber "Scheduled for …" badge, status filter in the list). The postsschedule tick publishes them once a minute with publishedAt set to the scheduled moment, audits post.autopublished and emits the post.published webhook event. Overlapping runs claim optimistically, so a post publishes exactly once.

  • White-label branding

    a single platformsettings row (brand name, tagline, logo, favicon, primary/accent colors, support email, legal name, footer credit) edited on the /admin Branding card and read everywhere through lib/branding.ts; logo and favicon are media rows with a null siteid, so a platform asset never enters a site's library or counts against an owner's storage. The footer credit on generated sites is a pure rule over the owner's plan watermark and the per-site sites.showcredit override, rendered as a rel=nofollow link; removing it is refused with a 402 while the plan still carries the badge, which is what "watermark removal" sells.

  • Header/footer/logo controls and page management

    shipped. site.logo renders at 40px in the header and footer (uploaded image or branded artwork); page.hideFromNav keeps a page live, linkable and indexed while taking it out of both navs. The Content outline's ✎ opens per-page settings: inline rename, a slug field validated against /^[a-z0-9-]{1,40}$/ plus isReservedPageSlug plus uniqueness, hide-from-nav, and Duplicate (deep clone, fresh section ids, -copy). Changing a page or post slug queues a 301 from the old path, so the rename is safe on a published site. Delivers editable-url-slugs.

  • On-site search

    /search?q= as a route family over SEARCHSOURCES, a registry filled by import side effect (lib/search-sources.ts pushes the spec-page and published-post sources; collection entries join in wave 4). Pages are scanned in memory off the spec that is already loaded, posts are narrowed by an OR of escaped LIKE patterns and then scored by the same pure scoreText, so hits from different sources rank against each other honestly; rankHits dedupes by URL. The whole surface (normalizeQuery, queryTerms, likePattern, scoreText, snippet, rankHits, searchPages) is pure and covered DB-free. The page is a native <form method="get"> plus a plain list — no client component — is noindex with the query stripped from the canonical, and is rate-limited search:<ip>:<siteId> at 60/min with a friendly notice rather than an error. A search section and a spec.header.search magnifier are the two ways in; the static export cannot answer a query and its README says so.

  • Product blocks (wave 3)

    productGrid and featuredProduct sections rendered from the live catalog rather than from the document. lib/spec/refs.ts (collectSectionRefs) is the pure "does this page reference these section types" test the shell gates the query on; lib/commerce/product-block-rules.ts holds the rest of the DB-free half — productBlockNeeds sizes the query from the sections (a newest-first grid reads exactly its count, a price-sorted grid reads a 24-row pool because sorting four rows is not a sort, a page with only featuredProduct reads none), sortProducts is the stable display order and findProductBySlug the featured lookup. lib/commerce/product-blocks.ts composes those with getStore (null when the shop is off), listActiveProducts, a by-slug fetch for featured products older than the pool, and listPublicVariantsForProducts so a card cannot advertise a price its own product page contradicts. The carousel is scroll-snap CSS, so a published page still ships no first-party JS. BriefSchema.sellsProducts drives a wizard checkbox, a GENERATESYSTEM rule (one homepage grid, never product names or prices written into other sections) and the mock generator. Still to come: collectionSlug is accepted and ignored until commerce-collections lands; pay-what-you-want and subscription products show their base price in a card; and the grid has no paging — it is a teaser for /shop, not a catalog page.

  • Collections (directories)

    user-defined content types and their entries: a field designer with 15 typed field types, per-type validation built from the collection's own fields (entrySchemaFor), paginated entry management with publish/unpublish and per-entry SEO, entry.published webhooks, an AI "Draft with AI" op (draftentry) capped to the text-like fields a structured-output grammar can carry, and a plan gate on limits.collections. Public /c/<slug> pages, the collection-list section, JSON-LD and search/export coverage follow in the collection-list slice.

  • Newsletter campaigns

    campaigns + campaignsends; CampaignBlockSchema (lib/newsletter/blocks.ts) and a pure renderCampaignHtml (lib/newsletter/render.ts, covered by render.test.ts) producing table-based HTML, a text part and the campaign's link list; startCampaign claims draft|scheduled → sending in one guarded UPDATE, refuses with 409 when resolveFrom(siteId) is null or the audience is empty, then enqueues in batches of 100 with List-Unsubscribe mailto+https, List-Unsubscribe-Post, and site:/campaign: tags; reconcileCampaign settles the ledger and the counts from the outbox; runDueCampaigns on a 30 s tick (src/lib/ticks/campaigns.ts); GET/POST/PATCH/DELETE /api/sites/[id]/campaigns (send/schedule/cancel admin-only, audited as newsletter.send / .schedule / .cancel); GET/POST /api/public/newsletter/campaign-unsubscribe resolves the per-send token; AI draftnewsletter op with mock parity, rate-limited draftnl:; Newsletter tab with the block composer, tag picker, srcdoc preview and test send.

  • AI FAQ & keyword helpers

    two ops on the generic runAiOp seam, no provider change: generatefaq uses sectionSchemaFor("faq") as its output contract so the answer is an appendable section (fresh uuid, POST /api/sites/[id]/faq, 409 at the 12-section cap, existing questions fed back so a second press adds rather than repeats), and suggestkeywords returns 5–12 {term, intent, useIn} (POST /api/sites/[id]/seo/keywords, whole-site or per-page). The SEO card applies them through pure rules — mergeKeywords (cap 12, case-insensitive), draftSeoTitle (≤60) and draftSeoDescription (≤155), both clamping on a word boundary. Both routes rate-limit 10 per 10 min per user, map InsufficientCreditsError to 402 and provider failure to 502. Prompts and mocks live in DB-free -rules.ts siblings; the mocks are org-type aware (a church is not asked its prices) and each is asserted to parse its own schema.

  • Google Search Console

    shipped. Verification meta on the home page only, a 28-day search-performance card (totals, daily clicks, top queries and pages) beside the GA4 traffic card, and an after-publish sitemap submit that logs its failures instead of raising them. Reuses the GASERVICEACCOUNTJSON key at a second scope, so there is no new credential to deploy. Not yet done: per-page and per-query drilldown, the Index Coverage / URL Inspection APIs (they need a different quota story), and surfacing search queries next to the pages they landed on in the first-party page-views table.

  • MCP site & blog packs (wave 3)

    src/lib/mcp/tools/site.ts and tools/blog.ts, two lines in TOOLPACKS, taking the server from 10 tools to 41. The site pack edits the working draft (pages, sections, regeneratesection) plus redirects, custom code and the media library; the blog pack covers posts, scheduling, the AI draft and taxonomy. The page/section rules moved into a pure src/lib/spec/edit.ts — reserved slugs refused, a page keeps one section, a partial update keeps a section's type and id, every edit re-validated as a whole document — so the editor and an assistant answer "why not" with the same sentence. createuploadurl (src/lib/uploads.ts) mints a ten-minute HMAC link that PUT /api/public/upload/[token] spends once, re-checking the storage limit and rate-limiting by IP. Scopes: content for spec and media, site for redirects and custom code; admin for anything that puts content in front of visitors. Covered DB-free in src/lib/mcp/site-tools.test.ts over the SDK InMemoryTransport.

  • Public collections (/c family)

    published collections render on both host families: families/collections.tsx lists entries in the collection's grid/list/table layout at 24 a page (/page/<n> from page 2), entry pages render field-by-field (images and galleries through SiteImage, map embeds only from the allowlisted Google host, video through parseVideoEmbed, references resolved to published entries as links) with schema.org JSON-LD typed by schemaType and filled by field-key convention. Entries join /search (a SEARCHSOURCES entry), sitemap.xml, llms.txt and the static export; per-entry noindex is honored everywhere; collections.showInNav puts the collection in the site nav. The collectionList section teases the latest entries on any page, loaded only for pages that reference it. All path/pagination/JSON-LD judgment is pure in lib/collections-public.ts.

  • Templates by industry

    twelve complete site specs (src/lib/templates/specs/.json) validated against SiteSpecSchema by registry.test.ts with distinct palettes and every font pair. /templates is a zero-JS gallery with ?industry= chips and og cards; /templates/<id>[/<path>] renders the real SiteChrome+PageSections with forms/newsletter/search stripped, so the preview ships the same zero JS as a published site. Starting from a template seeds draftSpec at once and a personalize job rewrites hero/about/services against the brief (mock parity included); BriefSchema gains templateId, free-text industry and five more pagesWanted values. templates is a reserved site slug.

  • Rich post bodies

    posts.body is a block list (PostBodySchema: richtext, captioned image, gallery, video, faq, cta — the page sections' own schemas plus one post-only image block). Legacy string[] bodies convert transparently through coercePostBody, which also salvages partially broken arrays instead of 500ing a post page. BlogPostView renders blocks through the section renderer, FAQ blocks emit FAQPage JSON-LD as <details>, RSS items carry CDATA-wrapped sanitised content:encoded (blockHtml refuses javascript:/data: hrefs), and search/llms read prose via blocksToText. The post editor gets a blocks outline driving the same PropsForm the page editor uses.

  • AI blog generation

    draftpost (heavy tier: title, excerpt, paragraphs, SEO title/description, tags) and suggesttopics (light tier, five topics that dodge the last 20 post titles) on the structured-op seam, with org-type-aware mocks. The Posts tab grows a ✦ Ideas chip row and a Generate that fills everything; both are metered and rate-limited. The autopost schedule kind (weekly/monthly, per-(site,kind) unique row) drafts a post from a fresh topic on the scheduler tick — enforcing the plan's post cap, auditing schedule.autopostran / autopostskipped, publishing immediately only when the schedule says so.

  • Newsletter automations

    automations / automationsteps / automationruns; enrollment fires on subscriber confirm, on markPaid (via ONPAIDHOOKS) and on tagging, and is idempotent per (automation, subscriber). A 60 s tick claims due runs optimistically, stops when the subscriber is no longer subscribed, renders through the campaign block renderer and enqueues kind automation from the verified sending domain (runs defer 15 min when the domain is not yet verified). The Newsletter tab's Automations card ships a welcome preset (immediate + 3 days) and an AI draft.

  • Email open/click analytics

    mails carry a per-send token; /api/public/nl/o/<token> answers a no-store GIF (the same one for unknown tokens) and stamps first-open, /api/public/nl/c/<token>?l=<i> 302s only to campaigns.links[i] — a tampered index 404s — tagging on-site destinations with utm parameters. Events land in emailevents with anonymised ip prefix + coarse device; Brevo delivered/bounce/spam correlate through emailoutbox.providermessageid (indexed, migration 0020) and flip campaignsends to bounced. getCampaignStats serves the Engagement card with per-link counts and "opens are approximate" copy. Automations deliberately have no send ledger, so they are untracked.

  • Mail inbox

    Brevo Inbound posts to /api/public/mail/inbound?key=<per-site secret>; messages dedupe by Message-ID, thread by In-Reply-To/References then normalized subject+participants (lib/mail/threading.ts, pure), and store only allowlist-sanitised HTML (lib/mail/sanitize-html.ts, hand-rolled: scripts/styles/forms dropped, event handlers and javascript: URLs stripped, remote images removed). Spam ≥ 5 auto-archives new threads. Replies enqueue kind mail from the verified domain with generated Message-ID/In-Reply-To (409 without a domain). Contact-form submissions mirror in as source=form threads. The Inbox tab is two-pane on xl, single-pane with a back button below, message HTML in a sandboxed iframe srcdoc.

  • Image variants

    uploads are measured (EXIF-aware) and encoded to WebP+AVIF at 480/960/1600 in the background; generation is fire-and-forget and can never block or fail an upload, and completion is recorded in media.variants. /media/[id]?w=&f= negotiates by Accept with vary: accept and falls back to the original on any doubt, so pre-wave rows serve unchanged. SectionCtx.mediaMeta (one request-cached query over the ids a page actually references) lets SiteImage/CoverImg emit srcset/sizes with intrinsic dimensions; patternDataUri is memoized. The static export strips variant srcsets and bundles the 960 WebP.

  • Help center, changelog, roadmap

    file-backed markdown (src/lib/content.ts: hand-rolled frontmatter, marked behind an allowlist sanitiser with entity-decoded URL checks). /help and its 15 owner-facing articles are force-static with a zero-JS "Was this helpful" form (helpfeedback, honeypot + rate limit, CSS :target thank-you); /help/search is server-rendered, rate-limited and noindex. /changelog groups by month with an RSS feed and a 14-day "What's new" dot in the dashboard; /roadmap renders this very file's ## headings as three columns. src/app/sitemap.ts/robots.ts cover the builder host (custom domains keep their per-site files via the /xh rewrite).

  • MCP commerce & directory packs

    tools/commerce.ts (13 tools; scope commerce AND site-role admin; every response scrubbed by stripPaymentFields, updatestoresettings refuses provider fields by name and re-validates through the route's own schema) and tools/directory.ts (15 tools; scope directory, plan-gated createcollection, entry payloads validated by entrySchemaFor with readable issues, publish/unpublish admin-only). 69 tools total; catalog in docs/MCP.md.

Known limits

1

Where the edges are right now.

  • Output-schema grammar ceiling — full-site generation is on prompted JSON.

    Structured outputs compile SiteSpec into a decoding grammar with a size ceiling, and the 21-variant schema is over it. Measured against the live API across all five current models: the budget is about five section variants; a 6-variant union is already refused, and trimming validation keywords, restoring const/enum discriminators, and making every property required all fail to change that. Full-site generation therefore falls back to prompted JSON (RLAIJSONFALLBACK), validated by safeParseSiteSpec plus the existing repair round. Per-section regenerate still uses constrained decoding.

Up next

7

Ideas we're exploring — order and timing may change.

  • WebAuthn/passkeys as a second factor

  • Image variants (thumbnails/AVIF) and S3/R2 storage driver

  • Post scheduling (publish at a future date) and categories

  • Multi-language sites (SiteSpec is locale-aware already; needs per-locale specs + hreflang)

  • E-commerce blocks (product grid + Stripe Payment Links)

    the natural next step for the RentMy-style host scenario.

  • Editor collaboration presence (who's editing what)

  • Automatic Lighthouse/CWV checks post-publish with a score card in the dashboard