<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel>
    <title>FormBuilder changelog</title>
    <link>https://formbuilder.com/changelog/</link>
    <description>Product updates, fixes and security changes from FormBuilder.</description>
    <language>en-us</language>
    <item>
      <title>Trust &amp; self-serve (Tier B)</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Trust &amp; self-serve (Tier B)</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>**Public status page** at `/status`: a new 10-minute cron (`*/10 * * * *`, separated from the daily 14:00 jobs in `scheduled()`) probes API, database, R2 storage, the public-forms API path and the website, stores results in `status_checks` (migration 0021, 90-day retention), and `GET /api/public/status` summarises current state, 90-day uptime %, average latency and 30-day daily bars. **Public changelog** at `/changelog/` + `/changelog/feed.xml` (RSS): build-time SSG (`generate-changelog-page.mjs`) parses `CHANGELOG.md` — 263 entries grouped by day, type badges, internal &quot;Ops to-do&quot; notes scrubbed. **API reference** at `/developers`: every v1 endpoint with scopes, params, curl examples, response shapes, hook payload and limits (verified against `v1.ts` / `form-integrations.ts`). **Accessibility statement** at `/accessibility` (WCAG 2.1 AA target, known limitations, reporting path). **Self-serve session management**: logins now mint a `sessions` row and put its id (`sid`) in the JWT; `requireAuth` rejects revoked/expired sessions (older sid-less tokens keep working until expiry); `GET/DELETE /api/auth/sessions`, `POST /api/auth/sessions/revoke-others`; logout revokes its own row; Settings → &quot;Active sessions&quot; card (device/browser, IP, last active, sign out / sign out others). Migration 0020 adds `last_seen_at`/`revoked_at`. **Owner-facing activity log**: new `activity_log` table (migration 0022) + `logActivity()`; instrumented form created/published/unpublished/deleted, API key created/revoked, sessions revoked, password changed, Stripe connected/disconnected, subscription started/cancelled/retention-accepted; `GET /api/auth/activity` (paginated) and a Settings → &quot;Recent activity&quot; card. Footer links added (API, Changelog, Status, Accessibility); sitemap updated.</description>
    </item>
    <item>
      <title>Billing &amp; retention (Tier A)</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Billing &amp; retention (Tier A)</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Six standard-SaaS gaps closed in one pass. **Annual billing:** `STRIPE_PRO_ANNUAL_PRICE_ID` (Worker) + `VITE_STRIPE_PRO_ANNUAL_PRICE_ID` (frontend, passed through `deploy.sh`) enable a Monthly/Yearly toggle on Pricing ($390/yr = 2 months free); price validation, switch-plan and webhook plan-mapping accept the annual price. **Promo codes:** &quot;Have a promo code?&quot; in the payment modal → API resolves the customer-facing code via `/promotion_codes` and applies it as a subscription discount (clear error on invalid). **Real free trial:** first-ever subscriptions get `trial_period_days: 14` (card required, charged after); modal and pricing card say so, and the &quot;30-day&quot;/&quot;no card required&quot; claims on Pricing FAQ, Security and HIPAA pages were corrected. **Dunning:** new `invoice.payment_failed` webhook case marks the subscription `past_due` and sends a &quot;payment failed — update card&quot; email (amount, next retry date, billing + invoice links); Billing page shows a red past-due banner linking to payment methods. **Usage meter + warning:** `GET /api/billing` now returns `usage` (published forms, monthly responses vs plan limits), rendered as meters on Billing with an inline upgrade prompt at ≥80%; a daily cron (`sendUsageWarnings`) emails free users once per calendar month when they cross 80% (the existing held-response email covers 100%). **Win-back:** if `STRIPE_WINBACK_COUPON_ID` is set, the cancel modal offers the coupon (&quot;Apply discount and stay&quot;); `POST /api/billing/retention-offer` applies it once per user and records `cancellation_feedback.reason='winback_accepted'`.</description>
    </item>
    <item>
      <title>Real respondent payments via Stripe Connect</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Real respondent payments via Stripe Connect</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The May payment field charged FormBuilder's own platform Stripe account, so owners could never actually get paid. Now owners connect their own Stripe account (Standard/OAuth) in **Settings → Payments** (new `PaymentsSettings` card: connect / connected-account status / disconnect) and payment fields create Checkout Sessions *on the connected account* (`Stripe-Account` header) — money goes straight to the owner, no platform fee. Migration 0019 adds `users.stripe_connect_account_id/status/connected_at`. New API `/api/stripe-connect` (status, authorize with HMAC-signed state, callback, disconnect). Submissions to a form with a payment field are refused (422 `PAYMENT_NOT_CONFIGURED`) until the owner connects — never charged to the platform. New public `POST /api/public/forms/:id/payment/confirm` verifies the Checkout Session server-side (must be paid *and* carry this response's id) before the public form shows the thank-you screen; the webhook and the confirm endpoint share one idempotent `finalizePaidResponse()` (services/payments.ts) so a response is finalized exactly once. Webhook route accepts the Connect webhook signing secret (`STRIPE_CONNECT_WEBHOOK_SECRET`) as a fallback. Public form now handles `?payment=success|cancelled` (verifying → paid / pending / error panels; cancelled shows an inline retry notice). Builder shows a Stripe-connection notice on payment fields.</description>
    </item>
    <item>
      <title>Free plan: 5 forms + webhooks/Zapier/payments listed as included</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Free plan: 5 forms + webhooks/Zapier/payments listed as included</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>`PLAN_LIMITS.free.forms` 3→5 (API create/publish caps) and `plans.ts` to match; webhooks were never plan-gated in the API, so the Free feature list now says so (Pro list de-duplicated). Copy fixed in pricing table, help center (3 places), templates page, integration pages' CTA fine-print (was claiming &quot;Unlimited forms on the free plan&quot;), and all comparison/alternatives pages (FormBuilder facts, competitor pricing analyses, FAQs).</description>
    </item>
    <item>
      <title>Comparison-page accuracy</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Comparison-page accuracy</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Feature matrix: Payments → Yes (&quot;Stripe — paid to your own account, no platform fee&quot;), Calculations &amp; scoring → &quot;Calculation field + auto-graded quizzes&quot; (quiz grading shipped July 24), Webhooks → &quot;All plans&quot;. Rewrote 12 competitor sentences that described FormBuilder payments as &quot;Stripe/Zapier workflows&quot;.</description>
    </item>
    <item>
      <title>Programmatic SEO: 26 static comparison + alternatives pages</title>
      <link>https://formbuilder.com/changelog/#2026-08-17</link>
      <guid isPermaLink="false">2026-08-17:Programmatic SEO: 26 static comparison + alternatives pages</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Engineered-visibility play for AI assistants and search: new build-time mini-SSG (`frontend/scripts/generate-compare-pages.mjs`, runs after the integrations generator) renders `/compare/` (hub), 12 `/compare/formbuilder-vs-&lt;competitor&gt;/` head-to-head pages, `/alternatives/` (hub), and 12 `/alternatives/&lt;competitor&gt;/` ranked buyer's guides. Tier-1 competitors: Typeform, Jotform, Google Forms, Microsoft Forms, SurveyMonkey, Tally, Formstack, Wufoo, Cognito Forms, Paperform, Fillout, MightyForms (acquisition-flavored). Each competitor is one rich data file (`compare-data/competitors/&lt;slug&gt;.mjs`: 3-paragraph overview, best/not-ideal-for, vendor-terms pricing table checked live on Aug 17 2026, pricing analysis, 25-row feature matrix, strengths/weaknesses, verdict + choose-X-if lists, 4-step migration, why-people-switch, 6–8 chatbot-phrased FAQs); FormBuilder facts live once in `compare-data/formbuilder.mjs`. Design per guidelines: scorecard hero artifact, charcoal verdict panel, side-by-side pricing decision tables, grouped feature matrix, S/W cards, numbered migration steps, FAQ, related pills, CTA band; verified via headless-Chrome renders desktop + narrow. JSON-LD: BreadcrumbList, Article (dateModified), FAQPage, ItemList (alternatives). `sitemap-compare.xml` + robots.txt Allow/Sitemap lines; footer gained Compare/Alternatives links. Adding a competitor = one data file + one registry line + rebuild.</description>
    </item>
    <item>
      <title>Review-queue SLA alert false alarm on banned users' forms</title>
      <link>https://formbuilder.com/changelog/#2026-07-28</link>
      <guid isPermaLink="false">2026-07-28:Review-queue SLA alert false alarm on banned users' forms</guid>
      <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The daily SLA email reported &quot;5 form(s) waiting &gt;24h&quot; while the admin review queue was empty. Root cause: ban-takedown intentionally leaves `requires_review = 1` on a banned user's forms, and the SLA queries in `abuse-sweeps.ts` counted `forms.requires_review = 1` without checking owner status, while the admin queue filters on `u.is_active = 1` — so already-handled phishing forms (all 5 belonged to banned accounts) re-alerted daily forever. Both SLA queries now join `users` and require `is_active = 1`, matching the queue's definition of pending. Verified against production D1: fixed count query returns 0. No data cleanup needed — the flag on banned users' forms is intentional.</description>
    </item>
    <item>
      <title>CTA realignment to draft-first across marketing surfaces</title>
      <link>https://formbuilder.com/changelog/#2026-07-25</link>
      <guid isPermaLink="false">2026-07-25:CTA realignment to draft-first across marketing surfaces</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>CTA audit found several pages still funneling visitors into signup/dashboard dead-ends instead of the draft-first builder. Fixed: all 138 static integration pages (nav &quot;Get started free&quot; + hero + closing-band CTAs went from `/register` to `/form-builder`), the Features page CTA band (was signup modal / dashboard, now &quot;Create a form free&quot; → builder), and all three blog CTAs (inline `---cta---` box, sidebar card, bottom band — all previously opened the signup modal). Every CTA now carries a `?src=` param and fires `create_intent_opened` with a per-surface source; the builder additionally logs a new `builder_opened` event with the `src` param on arrival, so per-page draft-conversion can finally be measured (homepage vs features vs blog vs integrations). Verified in headless Chromium: features CTA lands on `/form-builder?src=features`, blog post renders both new CTA links, no console errors.</description>
    </item>
    <item>
      <title>MightyForms banner shortened + dismissible</title>
      <link>https://formbuilder.com/changelog/#2026-07-25</link>
      <guid isPermaLink="false">2026-07-25:MightyForms banner shortened + dismissible</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The acquisition banner dropped its redundant middle sentence (&quot;Existing MightyForms customers can still sign in.&quot; — the login link already says that) and &quot;Read the announcement&quot; became &quot;Announcement&quot;. Added an × dismiss button that persists via localStorage (`mightyforms_notice_dismissed`), so returning visitors get the row back after one acknowledgment. Verified in headless Chromium including dismissal surviving reload.</description>
    </item>
    <item>
      <title>Form editor nav simplification + standout Share button</title>
      <link>https://formbuilder.com/changelog/#2026-07-25</link>
      <guid isPermaLink="false">2026-07-25:Form editor nav simplification + standout Share button</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The editor header dropped from ~10 controls to 6. The red Unpublish button is gone from the action row: the Live status pill is now a small dropdown (Live ▾ → Unpublish form), so the destructive action no longer sits next to Share competing for attention. Settings and Integrations merged into one Settings ▾ dropdown. The `email + Sign Out` text became an avatar menu (initial circle → email, Sign Out). Preview is now icon-only. Share was redesigned as the single primary CTA when a form is live: charcoal fill per design guidelines, semibold, rounded-full, shadow + ring, emerald live dot, icon micro-scale on hover — the only filled button in the bar (Publish takes that role on drafts). Also fixed: header dropdowns previously rendered underneath the Field Properties panel (header lacked a z-index). Verified with headless-Chromium screenshots (desktop + menus open). Known pre-existing issue, not addressed: the header action row overflows off-screen at phone widths.</description>
    </item>
    <item>
      <title>Publish-gate AI review dedupe</title>
      <link>https://formbuilder.com/changelog/#2026-07-25</link>
      <guid isPermaLink="false">2026-07-25:Publish-gate AI review dedupe</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The security gate re-ran a full AI review on every publish/save that carried publish state, even for identical content: one untrusted user's autosave burned 27 AI reviews in 2 minutes (41 total), and a blocked phisher's publish-hammering re-sent the review-queue email on every retry. The gate now hashes the scanned content (title, description, fields, thank-you/redirect/webhook) into KV (`pubscan:&lt;formId&gt;`, 7-day TTL) and reuses the prior verdict on a hash match — allowed verdicts skip the AI call entirely; blocked verdicts are honored only while the form is still `requires_review` so admin approvals always win. Pattern scans (cheap regex) still run every time.</description>
    </item>
    <item>
      <title>Review-queue SLA alert email actually useful now</title>
      <link>https://formbuilder.com/changelog/#2026-07-25</link>
      <guid isPermaLink="false">2026-07-25:Review-queue SLA alert email actually useful now</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The daily &quot;Review queue SLA&quot; email was a bare count with no links. It now lists each stuck form (up to 20, oldest first) with title, why it was flagged (latest scan risk level + top finding descriptions), owner (name linked to their admin detail page, email, plan), and exact wait time, plus an &quot;Open the review queue&quot; button to /admin/security and a pointer to the batch rescan. Also added `GET /api/admin/security/sla-alert/resend` (manage_security permission) to re-trigger the alert on demand by opening the URL in a logged-in admin browser.</description>
    </item>
    <item>
      <title>Integration pages redesign + main sitemap fix</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Integration pages redesign + main sitemap fix</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The 138 `/integrations` pages got a full visual upgrade per DESIGN-GUIDELINES: hero product artifact (sample contact form → Zapier bolt → destination-app card with live-feed rows), subtle grid texture, stats strip, iconed workflow cards with a &quot;Popular&quot; flagship badge, a field-mapping panel artifact (form fields → app fields), numbered setup steps, animated-caret FAQ, app-monogram related pills, and a charcoal closing CTA band. Verified by actual headless-Chrome renders (desktop full-page + narrow), which caught unsized inline SVGs rendering giant. **Sitemap fix:** `formbuilder.com/sitemap.xml` was silently serving the SPA's index.html (Pages fallback shadowed the worker route — crawlers got HTML, not XML). The generator now writes a static `sitemap.xml` with 29 marketing/help URLs plus all 304 template pages (ids extracted from the same `src/data/templates/*.js` the template pages render from), 333 URLs total; static files always beat the SPA fallback.</description>
    </item>
    <item>
      <title>Zapier integration: real REST-hooks app + public API expansion</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Zapier integration: real REST-hooks app + public API expansion</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>FormBuilder now supports a genuine Zapier app (code in new `zapier/` dir, Zapier Platform CLI) instead of the old paste-a-webhook-URL approach. New v1 API surface behind the existing developer tokens: `GET /v1/me` (auth test), `GET /v1/forms/:id/fields` (field schema → Zapier dynamic output fields), `POST /v1/hooks` + `DELETE /v1/hooks/:id` (REST hook subscribe/unsubscribe, new `api_hooks` table, migration 0018, per-user cap 100, https + anti-SSRF validation). Triggers: **New Form Response** (instant — `dispatchApiHooks` fires from the submission pipeline alongside existing integrations; held over-cap responses never fire; `410 Gone` auto-deletes dead hooks; deliveries logged to `integration_logs` as `zapier_hook`) and **New Form** (polling). New `hooks:manage` token scope; revoking a token deletes its hooks. Settings gained an &quot;API &amp; Zapier&quot; card (create/copy/revoke keys — first UI for the token system); the editor's webhook panel links to it. Free on all plans (matches Tally/Jotform/Typeform). Zapier app still needs `zapier register`/`push` from the Zapier account (see `zapier/README.md`).</description>
    </item>
    <item>
      <title>Programmatic SEO: 138 static integration pages</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Programmatic SEO: 138 static integration pages</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>New build-time mini-SSG (`frontend/scripts/generate-integration-pages.mjs`, runs after `vite build`) renders `/integrations/` (hub), `/integrations/zapier/` (pillar), and 136 `/integrations/zapier/&lt;app&gt;/` landing pages (&quot;Connect FormBuilder to Salesforce/Slack/Notion/…&quot;) from a single dataset (`zapier-apps.mjs`: unique per-app blurbs + category use-case templates + custom flagship use cases). Fully static HTML on Cloudflare Pages — crawlable without JS, editorial design system, FAQPage + BreadcrumbList JSON-LD, per-category internal linking, `sitemap-integrations.xml`, robots.txt updated (static + worker), footer &quot;Integrations&quot; link. Growing the page set = appending to the dataset and rebuilding.</description>
    </item>
    <item>
      <title>Owner-facing form analytics page</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Owner-facing form analytics page</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>New `/forms/:id/analytics` page (linked from the responses page header) finally surfaces the analytics the API was already collecting but never showing anyone: views/starts/submissions/completion-rate stat tiles, a 30-day daily views-vs-submissions trend chart (inline SVG with hover tooltips, colorblind-validated amber/emerald palette with dashed-vs-solid line encoding), a respondent funnel (viewed → started → submitted with rates), and top countries. The existing `GET /api/forms/:id/analytics` endpoint gained `funnel`, `daily` (30-day series merged from `product_events` views and `form_responses` submissions), and `quiz` fields. No schema changes.</description>
    </item>
    <item>
      <title>Quiz performance aggregates for owners</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Quiz performance aggregates for owners</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The analytics page shows a quiz block when a form has graded responses: average score, average percent, graded-response count, and a score distribution (0–19% … 80–100% buckets). Computed by new `summarizeQuizScores()` in the scoring service (unit-tested, 3 new tests) over the most recent 1,000 responses' stored `metadata.scoring.quiz`.</description>
    </item>
    <item>
      <title>Autosave + resume for respondents</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Autosave + resume for respondents</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>On forms with &quot;save progress&quot; enabled, answers now autosave to the respondent's browser (localStorage, 800ms debounce, 7-day expiry) as they type; returning respondents get the restore prompt (the previously-orphaned `ResumeFormModal`, now wired in) offering to continue where they left off, including the right page on multi-page forms. Cleared on submit or discard; server-side resume links (the existing manual &quot;save progress&quot; flow) take precedence when present. Closes the gap where leaving without clicking save lost everything.</description>
    </item>
    <item>
      <title>Quiz functionality: auto-graded questions with respondent scores</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Quiz functionality: auto-graded questions with respondent scores</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Radio, select, checkbox, and text fields can now be marked as quiz questions in the builder (correct answer + points, checkbox answers require the exact set, text matches case-insensitively). Grading runs server-side on submission via the existing scoring service and is stored in `metadata.scoring.quiz` on each response. Respondents see a score card on the thank-you screen (&quot;7 / 10 — 7 of 10 questions correct&quot;) unless the owner turns off the new &quot;Show Quiz Score&quot; toggle (settings → General, appears once a field has a correct answer; stored in new `forms.quiz_settings` column, migration 0017). Owners see the score plus per-question Correct/Incorrect badges in the response detail modal. Security: answer keys live in field settings and are stripped from all public form payloads — respondents can never fetch the key, and grading happens server-side only.</description>
    </item>
    <item>
      <title>47 new templates closing every AI-demand gap + new Logistics category</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:47 new templates closing every AI-demand gap + new Logistics category</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>The July 24 template review cross-referenced ~120 production AI-generator prompts against the library and shipped templates for every recurring theme with no coverage: *Education* 4→16 (multiple-choice quiz, exam answer sheet, class test, student assessment, assignment submission, primary school enrollment, daycare registration, college application, attendance sheet, parent-teacher conference signup, field trip permission slip, mentorship matching); *new Logistics category* (delivery note, delivery order, proof of delivery, delivery incident report, delivery driver application, courier pickup request); *audits &amp; inspections* (store audit, site survey, internal audit checklist, RCSA, workplace safety inspection, general inspection checklist); *HR* 6→13 (supervisor feedback — the demanded supervisor-selection variant, leave request, overtime request, shift swap, performance self-review, workplace incident report, onboarding checklist); *Food &amp; Beverage* 3→9 (restaurant customer review with dine-in/takeaway/drive-through split, takeaway feedback, online food order, private event booking, menu item survey, delivery driver signup); plus refund request, roadside assistance, car rental booking, equipment rental, visitor sign-in sheet, group volunteer registration, dance workshop registration, early access signup, dormant account reactivation, and training feedback survey. Every template has a distinct theme (varied palettes and font pairings). Contract test extended with the full id list. Note: quiz/exam templates collect answers only — no grading engine yet (next conversation).</description>
    </item>
    <item>
      <title>Template search analytics never logged</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Template search analytics never logged</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>`logTemplateSearch` existed and the `/api/template-search` endpoint worked, but no page ever called it, so the admin Template Searches dashboard has been empty since launch. The templates page now logs settled searches (800ms debounce) with result counts, so future template-gap decisions can run on real zero-result search data.</description>
    </item>
    <item>
      <title>Unsubscribe audit: every recurring email now has a logged-out opt-out</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Unsubscribe audit: every recurring email now has a logged-out opt-out</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Conditional-notification recipients (form-integration emails with no stored recipient row) previously had NO unsubscribe link; they now get an HMAC-signed `/unsubscribe-notifications/:payload` link (login-free, tamper-rejecting, warm-paper pages) with opt-outs persisted in KV and filtered before every send. Backfilled the one production recipient row missing an unsubscribe token. All three unsubscribe flows (marketing/lifecycle, per-form recipient, conditional) verified live and logged out end-to-end.</description>
    </item>
    <item>
      <title>Email round 2: full lifecycle coverage + deliverability hygiene</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Email round 2: full lifecycle coverage + deliverability hygiene</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>(1) *Create nudge* (code 100): signed up 24h+ ago with zero forms → starter ideas + template/AI links; completes the nudge ladder (signup→create was the funnel's biggest single drop at 47%). (2) *Weekly digest* (code 103, Mondays): owners whose forms had views/responses in the last 7 days get &quot;Your week on FormBuilder: X views, Y responses&quot; with a per-form table; quiet accounts get nothing. (3) *Unsubscribe hygiene*: all lifecycle sends now carry an unsubscribe footer to the (restyled, warm-paper) `/marketing-unsubscribe` page; List-Unsubscribe header not possible with the current EMAIL binding shape (no headers field) — revisit if the binding changes. (4) *Bounce suppression*: provider-reported permanent bounces are logged as `bounced`, and any recipient with a bounce or 3+ failures and no successes in 30d is skipped (`suppressed` log status) to protect sender reputation. (5) Deleted the never-wired `welcomeSeries` day-2/5 drip templates and `sendWelcomeSeriesDay` dead code. Admin preview endpoint now sends all five sample emails.</description>
    </item>
    <item>
      <title>Lifecycle activation emails</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Lifecycle activation emails</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The product now follows up after the two moments where funnels die. Daily cron sends (once per user, future accounts only, `marketing_emails_enabled` respected): a *draft nudge* when a real draft (3+ fields) sits unpublished for 24h with nothing else live, and a *share nudge* when a published form still has zero views after a day — including the live link, a mailto share shortcut, and a deep link that reopens the share modal. The first response a form ever receives now sends a celebration variant (&quot;🎉 Your first response&quot;) with momentum copy. All email templates restyled to the editorial design system (warm paper, charcoal CTAs — the retired blue chrome is gone from every email). Send history in `welcome_series_emails` (codes 101/102); admin preview endpoint `POST /api/admin/lifecycle/preview-emails`.</description>
    </item>
    <item>
      <title>Dashboard visibility for the zero-views problem</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Dashboard visibility for the zero-views problem</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Published form cards now show view counts, and a live form with 0 views and 0 responses gets an amber &quot;No views yet — share your link&quot; callout that opens the share modal. Publishing from anywhere on the dashboard now opens the share modal immediately (the share moment), instead of just a toast.</description>
    </item>
    <item>
      <title>Persistent Share in the editor</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Persistent Share in the editor</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>A published form's editor nav now leads with a charcoal Share button (reopens the share modal any time); Unpublish is demoted to a quiet text action. Publish/Share buttons restyled from retired blue to charcoal.</description>
    </item>
    <item>
      <title>Free plan form cap now binds at publish, not create</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Free plan form cap now binds at publish, not create</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Changed</category>
      <description>Drafts no longer count against the 3-form free cap (production data: 72 free users were locked out of creating anything, 49 of them without ever publishing; templates — the #1 creation method — hit an upsell wall). Creation is now effectively unlimited (anti-abuse ceiling of 100 total forms), and the cap is enforced when transitioning a form to published (402 `PLAN_FORM_LIMIT_REACHED`, counts other published forms only so republishing never trips it). Removed the client-side 3-form block on the templates page.</description>
    </item>
    <item>
      <title>Dashboard &quot;Get your first response&quot; alert could unpublish a live form</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:Dashboard &quot;Get your first response&quot; alert could unpublish a live form</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The alert targeted `forms[0]` blindly: its &quot;Publish&quot; button *toggled* publish state (silently unpublishing an already-live form) and &quot;Copy link&quot; could copy a draft's dead URL. It now spotlights a live form awaiting its first response (Copy link / Share / Send test) or the newest draft (Publish, which then opens the share modal), and never copies unpublished links.</description>
    </item>
    <item>
      <title>`anonymous_publish_instant` events were silently dropped</title>
      <link>https://formbuilder.com/changelog/#2026-07-24</link>
      <guid isPermaLink="false">2026-07-24:`anonymous_publish_instant` events were silently dropped</guid>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The instant-publish success event fired by the frontend was missing from the server's `allowedEventNames` whitelist, so the new flow's key metric never recorded. Also: template usage now fires `template_selected` and `form_created` product events (the #1 creation path was invisible in the create funnel).</description>
    </item>
    <item>
      <title>Held responses: the free cap stops losing data and becomes the upgrade moment</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Held responses: the free cap stops losing data and becomes the upgrade moment</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The free plan's 100 responses/month cap previously rejected the respondent's submission with an error (data lost, form looked broken, upgrade prompt shown to the respondent instead of the owner). Now forms keep collecting: responses past the cap are stored with `metadata.held = true`, locked from viewing/export/API while the owner is on free, and unlock instantly on upgrade. Held responses skip webhooks/integrations and send a &quot;New response waiting&quot; teaser notification. Owners see a warm-paper banner on the responses page (&quot;N responses are waiting — upgrade to unlock&quot;) and locked rows show a quiet lock chip.</description>
    </item>
    <item>
      <title>Visible usage meter</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Visible usage meter</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>`GET /api/forms` now returns accurate monthly `response_usage`; the dashboard Plan card shows &quot;34 of 100 responses this month&quot; with a thin progress bar (charcoal, amber past 80%), and the near-limit alert uses real monthly usage instead of all-time counts. Response notification emails carry a quiet usage footer once past 50% of the cap. Pricing page and plan catalog updated to state real boundaries (held responses, badge removal, post-submit redirects on Pro).</description>
    </item>
    <item>
      <title>Publish-first anonymous flow</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Publish-first anonymous flow</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Clicking Publish as an anonymous builder now creates the account from the email alone, runs the full security pipeline, publishes instantly, and shows the live link + share panel on the spot; the emailed magic link became the account *claim* mechanism (7-day expiry, &quot;Your form is live 🎉&quot; template) instead of an inbox gate. Existing-account emails still fall back to the magic-link flow (we never start a session on an unproven email). Abuse valves: unclaimed accounts' forms pause at 10 responses and auto-unpublish after 7 unclaimed days (daily cron, with a fresh claim link emailed). Motivated by funnel data: 118 anonymous publish clicks → only 20 completions, with 92% of magic links clicked within 2 minutes or never.</description>
    </item>
    <item>
      <title>Iron-clad scanner upgrades</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Iron-clad scanner upgrades</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>`normalizeScanText` now folds Unicode homoglyphs (Cyrillic/Greek &quot;pаsswоrd&quot;), NFKC-normalizes fullwidth/styled text, strips diacritics and zero-width chars; credential terms in 15 languages added to both the pattern scanner and the non-negotiable credential hard-block (previously &quot;Contraseña&quot;/&quot;Пароль&quot; fields sailed through untouched). New evasion-corpus test suite (`scripts/security-scanner.test.ts`). AI review now runs on EVERY publish by untrusted accounts (new/unverified/unclaimed), not just pattern-flagged ones — non-English or creatively-phrased harvesting can no longer skip Claude; AI outage on a pattern-clean publish fails open but flags the form and emails the alert list.</description>
    </item>
    <item>
      <title>Bait-and-switch defense</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Bait-and-switch defense</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Field create/update/delete and version restore on a *published* form now re-run the full scan pipeline (previously never rescanned: publish benign, rename a field to &quot;Password&quot;, stay live); suspicious verdicts auto-unpublish + flag + alert.</description>
    </item>
    <item>
      <title>Behavioral defenses</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Behavioral defenses</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Trust tiers (`api/src/services/trust.ts`); post-submit redirect URLs restricted to established/paid accounts; disposable-email rejection on instant publish; auto-suspend + full takedown on a second suspicious form per account; &quot;Report this form&quot; link in the public-form branding bar (2 distinct-IP reports auto-pause + alert); credential-pair tripwire pauses any free-tier form whose responses start looking like email+password pairs (3 strikes); admin `POST /users/:userId/ban-takedown` one-click action; daily review-queue SLA re-alert for anything pending &gt;24h.</description>
    </item>
    <item>
      <title>YouTube tutorial video pipeline + &quot;How To Create a Form&quot; video</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:YouTube tutorial video pipeline + &quot;How To Create a Form&quot; video</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>New `promo/` toolkit: `promo/record/record.mjs` (Playwright, drives production formbuilder.com with an injected visible cursor, records 1920×1080 beats with a marker manifest) and `promo/remotion/` (Remotion 4 composition: editorial intro/end card, macOS browser frame, step captions with serif-italic accents, Ken Burns zooms, cross-fade/slide transitions, Nihilore CC-BY music bed). Builder footage recorded on production; respondent clip recorded locally (prod Turnstile correctly blocks headless submits). Output: `promo/dist/how-to-create-a-form.mp4`. Demo account `demo@formbuilder.com` (pro, verified) with one live &quot;Customer Feedback Survey&quot; form remains on production for the video.</description>
    </item>
    <item>
      <title>Credential-collecting fields always block publishing — hard rule, never AI-adjudicated</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Credential-collecting fields always block publishing — hard rule, never AI-adjudicated</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Security</category>
      <description>Per policy, any text-input field whose label captures a password/passcode/PIN/OTP/CVV/seed phrase (including leetspeak and asterisk-obfuscated variants) returns a `suspicious` verdict in `aiReviewFlaggedForm` before the model is consulted, and the review prompt no longer allows &quot;internal IT password form&quot; as a benign rationale. Motivated by the backfill: the AI cleared a &quot;Change Password&quot; form collecting Old/New Password.</description>
    </item>
    <item>
      <title>Verification emails actually send now</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Verification emails actually send now</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Registration fired `sendWelcome` without `ctx.waitUntil`, so Cloudflare Workers cancelled the promise when the response returned: production `email_delivery_logs` show **zero** welcome/verification emails across all 290 signups since logging began April 30 (magic-link emails, which are awaited, sent fine). 57% of all users (455/804) are unverified as a result. The send is now wrapped in `c.executionCtx.waitUntil` (`api/src/routes/auth.ts`).</description>
    </item>
    <item>
      <title>Unverified users no longer locked out of sharing</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Unverified users no longer locked out of sharing</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The share link was withheld from unverified users purely client-side (the server never enforced it; published forms were always publicly reachable): the legacy builder showed a &quot;Published (Private) — verify to unlock the shareable link&quot; banner instead of the share panel, the publish modal warned the link was &quot;locked until email verified&quot;, and the dashboard publish toggle hard-blocked with a dead-end toast. Combined with verification emails never sending, this stranded publishers with no URL — the main cause of the create→publish collapse (only 75 of 428 form creators ever published; 215 users abandoned drafts with 6+ fields). All three blocks removed; unverified owners of a published form now see a dismissible verify-email nudge with a working &quot;Resend verification email&quot; button (`frontend/src/legacy/pages/FormBuilderPage.jsx`, `frontend/src/pages/DashboardPage.tsx`).</description>
    </item>
    <item>
      <title>Admin backfill endpoint `POST /api/admin/users/backfill-verification`</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Admin backfill endpoint `POST /api/admin/users/backfill-verification`</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Batched send of the standard verification email to unverified active users who never received one (skips anyone with a logged sent verification email, so runs resume cleanly). Supports `{ limit, dry_run }` plus targeting filters `{ min_fields, days }` (only users owning a form with &gt;= min_fields fields touched in the last N days). Built for the 455 users stranded by the dropped-email bug. Targeting dry-runs on July 23 showed the intended segment (5+ fields, 14 days) contains zero real users — everyone stranded is 30+ days stale (127 all-time, 33 within 180 days, 5 within 90) because the magic-link flow auto-verifies all recent serious builders. No live send has been fired.</description>
    </item>
    <item>
      <title>Security scanner no longer scans random field IDs</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Security scanner no longer scans random field IDs</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>`evaluatePublicPublishSafety` passed `field.name ?? field.id` into the content scanner, but `form_fields` has no `name` column, so every field's UUID was scanned as content; hex IDs containing &quot;2fa&quot;/&quot;2f4&quot; (leetspeak-normalized) flagged benign forms as high-risk phishing (~1.5% chance per field) and permanently blocked publishing. Confirmed in production: a school enrollment form was blocked because a field ID contained `-2fa8-`.</description>
    </item>
    <item>
      <title>AI form generator reliability</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:AI form generator reliability</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>Turnstile tokens are single-use but the frontend reused a consumed token on retry (guaranteed 403); tokens now reset after every attempt. Rate limit raised 2/min → 6/min (iterating users hit 429 on their third prompt). Claude call now retries once on 403/429/5xx (production 403s from Anthropic proved sporadic, not a dead key — last success July 23; same retry added to the AI publish review), model updated `claude-sonnet-4-5` → `claude-sonnet-5`, and failure events now capture the server error message. NOTE: the dominant production failure (97 occurrences through July 21) is the Anthropic API rejecting `CLAUDE_API_KEY` with 403 &quot;Request not allowed&quot; — the key itself needs attention in the Anthropic console.</description>
    </item>
    <item>
      <title>Anonymous publish flow survives cross-device magic links</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Anonymous publish flow survives cross-device magic links</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Fixed</category>
      <description>The builder draft only lived in `localStorage`, so opening the magic-link email on a different device (build on desktop, tap link on phone) silently lost the form. Drafts now ride along with the magic-link request and are stored server-side in KV (24h TTL); login returns the draft and the existing client-side save/publish flow runs from it. localStorage remains as fallback for pre-change links.</description>
    </item>
    <item>
      <title>AI second-opinion review for flagged publishes</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:AI second-opinion review for flagged publishes</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Forms flagged above &quot;low&quot; risk by the pattern scanner are now adjudicated by Claude (`api/src/services/ai-review.ts`) before blocking: benign verdicts publish immediately (scan recorded as `approved`/`ai`); suspicious verdicts or AI errors block as before and email `ALERT_EMAIL_RECIPIENTS` — which was `&quot;&quot;` in production (review emails never sent to anyone, review queue sat at 66 pending / 0 ever approved); now set to me@evangower.com.</description>
    </item>
    <item>
      <title>Admin backfill endpoint `POST /api/admin/security/rescan-blocked`</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Admin backfill endpoint `POST /api/admin/security/rescan-blocked`</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Batched re-scan of forms stuck with `requires_review = 1` using the fixed scanner + AI review; benign forms are unblocked (flags cleared, scans approved) and owners get a &quot;your form is ready to publish&quot; apology email; suspicious forms stay blocked and are listed in the response. Supports `{ limit, notify, dry_run }`.</description>
    </item>
    <item>
      <title>Post-publish share moment</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Post-publish share moment</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>Publishing from the editor now opens a share modal (link, QR, embed, social) instead of just a toast; `ShareModal` extracted from the dashboard into `frontend/src/components/ShareModal.tsx` and the previously-dead `?published=true` redirect param from the anonymous flow now triggers it too. Motivated by production data: 58 of 115 published forms had zero views ever, while forms with 5+ views got responses 70% of the time.</description>
    </item>
    <item>
      <title>Response notifications on by default for verified owners</title>
      <link>https://formbuilder.com/changelog/#2026-07-23</link>
      <guid isPermaLink="false">2026-07-23:Response notifications on by default for verified owners</guid>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <category>Added</category>
      <description>New forms created by a verified user get `email_notifications_enabled = 1` with the owner as a verified recipient; when a user verifies their email, notifications switch on for their existing forms that never had recipients configured (`api/src/services/default-notifications.ts`).</description>
    </item>
    <item>
      <title>JWT session revocation</title>
      <link>https://formbuilder.com/changelog/#2026-07-22</link>
      <guid isPermaLink="false">2026-07-22:JWT session revocation</guid>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <category>Security</category>
      <description>Tokens now carry a per-user `sv` (session version) claim checked on every authenticated request (migration 0015) · Password change, password reset, and admin suspend now invalidate all outstanding tokens; self password change re-issues a fresh session</description>
    </item>
    <item>
      <title>PBKDF2 iterations stay at 100k — Workers platform cap discovered</title>
      <link>https://formbuilder.com/changelog/#2026-07-22</link>
      <guid isPermaLink="false">2026-07-22:PBKDF2 iterations stay at 100k — Workers platform cap discovered</guid>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <category>Security</category>
      <description>A 600k raise was attempted per OWASP but Cloudflare Workers WebCrypto hard-rejects PBKDF2 above 100,000 iterations at runtime; reverted after it briefly broke production registration (caught and hotfixed within minutes during post-deploy testing). Raising further requires moving off WebCrypto.</description>
    </item>
    <item>
      <title>Rate limiting keyed on IP only</title>
      <link>https://formbuilder.com/changelog/#2026-07-22</link>
      <guid isPermaLink="false">2026-07-22:Rate limiting keyed on IP only</guid>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <category>Security</category>
      <description>Rotating the User-Agent no longer resets a client's quota; `rate_limits` table DDL moved from runtime to migration 0016</description>
    </item>
</channel></rss>
