Changelog

What's new

Every shipped change, newest first. Subscribe to /changelog.json for an always-current view.

v2.0.0

WordPress & WooCommerce plugin, BYOK, free file parsing, hardened security

## New: official WordPress & WooCommerce plugin A first-party WordPress + WooCommerce plugin now ships with every Blengi deployment. Drop it on any WordPress 6.4+ install running PHP 7.4 or newer, paste a workspace API token, and the streaming chat widget loads on every public page — with content sync, product cards, coupon application, order lookup, and lead mirroring out of the box. **Widget embed.** One asynchronous `<script>` tag injected into `wp_footer`, scoped to the post types you opt into. Hidden on `wp-admin`, login, AJAX, REST, XML-RPC, and cron paths. Emits `data-page-dir` and `data-page-locale` attributes so the bar mirrors correctly on Arabic, Hebrew, Persian, and Urdu locales. **Content sync.** Bulk + delta ingest of posts, pages, custom post types, and WooCommerce products as Blengi knowledge sources. SHA- 256 content hash short-circuits re-indexing on unchanged content, so re-running "Sync now" against a stable site is effectively free. **Page-builder rendering.** Elementor, Beaver Builder, Oxygen, and Bricks pages are rendered with the builder's own native API before sync, so the synced HTML reflects what visitors actually see. Divi posts route through `the_content` with `setup_postdata` primed so third-party filter callbacks see a valid `$post` global. A `pitchbar_post_content_html` filter lets sites post-process the final HTML (strip nav chrome, force a custom template, redact a section) without forking the plugin. **Resumable sync.** Each sync pass enforces a 20-second wall-clock budget. When a large site can't finish in one tick, the plugin persists a resume marker as a WordPress transient and schedules a WP-Cron continuation 30 seconds out. Shared hosting with a 30s `max_execution_time` cap no longer fatals mid-sync. The Plugins admin shows a soft notice while a chunked sync is in flight. **WooCommerce shopper context.** When a logged-in WC customer visits a page, the plugin attaches a signed `data-shopper-token` to the widget script tag. The widget forwards it to `/widget/init`; Blengi verifies it and bakes `wp_user_id` + `email_hash` (never plaintext) claims into the chat session. **Order lookup tool.** EcommercePreset's `order_status` capability maps to a real tool. When the visitor asks about orders, the LLM calls `lookup_order(limit, order_number?)`, which POSTs to `/wp-json/pitchbar/v1/orders/lookup` over HMAC-signed callback. Returns id, number, status, total, currency, items, tracking URL (AfterShip / ST tracking / generic meta), and the customer's order view URL. **Coupon emission & apply.** The plugin's `CouponSyncer` snapshots publish-status `shop_coupon` entries after every product sync (max 50), filters expired / over-limit, and posts them to Blengi. The LLM emits `<coupon code="WELCOME10" .../>` markers in chat replies; the widget renders them as cards with Copy and Apply buttons. Apply stages the code in a 15-minute transient that auto-applies on the visitor's next cart load via the `woocommerce_load_cart_from_session` hook. **Abandoned cart re-engagement.** A new `BehaviorRule.kind = "abandoned_cart"` lets you re-engage a visitor whose cart has been idle. The plugin's `cart-state.js` mirrors every WooCommerce `added_to_cart` / `removed_from_cart` event into `localStorage`; the widget polls every 30 seconds and fires when the cart sits past the rule's `idle_minutes` threshold. **Lead push-back.** Every Blengi lead is mirrored to WordPress as a WC customer (when Woo is active) or WP subscriber, with `pitchbar_lead_id` + `pitchbar_conversation_id` in user meta so the store owner can correlate. Idempotent on email — re-pushes update, never duplicate. **Workspace API tokens.** New `/settings/api-tokens` admin page issues workspace-scoped bearer tokens with granular abilities (the plugin uses `wp:integration`). Blengi stores only the SHA-256 hash of the plaintext; revoke instantly invalidates without deleting audit history. Cross-workspace revoke is forbidden. **HMAC signed callbacks.** Blengi → plugin calls (order lookup, coupon apply, lead push) are signed with HMAC-SHA256 over the raw body using a per-token `shopper_signing_secret`. 5-minute replay window. The plugin verifies via constant-time `hash_equals`. **Super-admin plugin distribution.** New `/admin/integrations/wordpress` page lets super-admins build and download the install-ready zip directly from the Blengi admin. Backed by the new `php artisan pitchbar:build-wp-plugin` Artisan command. ## Improvements **Plugin-load-order race fixed.** v1.x of the plugin trusted `class_exists('WooCommerce')` at `plugins_loaded` priority 10, which silently disabled WooCommerce features when alphabetical plugin order put `pitchbar` before `woocommerce`. v2.0.0 defers every WC-dependent registration to the `woocommerce_loaded` action (with a `did_action` fallback for the priority-20+ case), so the race is eliminated. **Coupon enumeration via `shop_coupon` CPT.** Replaces the v1.x reliance on `wc_get_coupons()` (not public WC API on every release) with `get_posts(['post_type' => 'shop_coupon'])` + `new WC_Coupon($id)`. Works on every WC version since 2.0. **`abandoned_cart` admin trigger.** Backend already supported it; the agent Behavior triggers admin page now exposes it in the kind dropdown. **Comprehensive WordPress docs.** Seven new documentation pages under the "WordPress & WooCommerce" group: Overview, Install & connect, Content sync, Page builders, WooCommerce deep links, REST API reference, Troubleshooting. ## Fixes **Page-builder content reaches the RAG store.** Pre-v2.0 the plugin called `apply_filters('the_content', $post->post_content)` without priming `setup_postdata`, which made plenty of `the_content` callbacks early-return on `is_null($post)`. The Elementor / Bricks / Oxygen path was worse — those builders don't store layout in `post_content` at all, so the synced HTML was empty. The fix detects each builder via postmeta and calls its native renderer. **Sync survives shared hosting timeouts.** The 30-second PHP execution cap on shared hosts no longer fatals mid-sync on large catalogs. The resumable sync pattern (transient + WP-Cron continuation) lets a 5,000-post sync finish over multiple ticks. **Conversation cookie lifecycle.** The widget writes `pitchbar_conv_id` on init success so the plugin's `CartCouponController::applyPendingCoupon` hook can locate the correct staged coupon. Pre-v2.0 the cookie was only set when `?pitchbar_conv` arrived as a query parameter, which left chat→cart flows without a correlation key. ## New: BYOK (Bring Your Own Keys) Each workspace can now pay its own Cloudflare / OpenAI / OpenRouter / Qdrant bill. Super-admin flips a global toggle in Settings → System; customers paste their keys at Settings → AI keys. Per-user override (force-on / force-deny / inherit-global) lets you grant or block individual users independent of the global flag. Strategic for operators reselling Blengi as a SaaS — the more end-customers scale, the less you owe upstream. Workspace-level credentials are encrypted at rest via `APP_KEY` (`workspaces.byok_keys` is an `encrypted:array` cast). Five layers of tenant isolation guarantee workspace A's keys never leak to workspace B's container resolution. See `/documentation/byok` for the full matrix. ## New: file parsing routed through Cloudflare's free `toMarkdown` PDF, DOCX, DOC, XLSX, XLS, ODT, ODS uploads now flow through Cloudflare Workers AI's `toMarkdown` endpoint instead of the in-process Smalot / PhpWord parsers. Cost: zero (toMarkdown for document formats consumes 0 Neurons). Quality: structured markdown out — headings, lists, tables preserved, which feeds the chunker much better than Smalot's best-effort plain text on Word-exported or scanned-with-text-layer PDFs. CSV / MD / TXT stay on the local parsers; HTML / SVG uploads are rejected at the validator. Falls back to Smalot when Cloudflare credentials are absent (BYOK OpenAI customers, fresh installs) or when the Cloudflare call fails. ## New: sitemap fan-out + 500-page cap The default `services.crawl.max_pages_per_source` bumped from 25 → 500. Buyers adding a 100-URL sitemap silently lost 75 pages. `SitemapDiscoverer` now handles three input shapes correctly: domain root (probes `/sitemap.xml` + `/sitemap_index.xml`), direct sitemap URL (fetched verbatim — previous code appended a second `/sitemap.xml`), and `<sitemapindex>` recursion. CMSes that emit a sitemap-index by default (WordPress, Shopify, Webflow) get every page indexed instead of just the homepage. Override the cap via `CRAWL_MAX_PAGES_PER_SOURCE` in env. ## New: always-visible "Talk to a human" button The widget header carries the button at all times for any agent whose vertical includes the `ticket_escalation` capability (every preset ships with it enabled by default). Plus `HumanIntentDetector` matches ~40 intent phrases ("talk to a human", "connect me to an agent", "I need to speak with someone", …) in chat messages and short-circuits the LLM straight into the handoff flow. ## New: 2-minute wait for human handoff + notifications Clicking "Talk to a human" used to fall straight to "no one's around" when no operator was actively monitoring the dashboard. Now: the conversation ALWAYS queues and notifies, the visitor waits up to 2 minutes (`WAIT_TIMEOUT_SECONDS = 120`), and every workspace member with `live_chat_available=true` gets a database + mail notification (`HumanRequestedNotification`). Operators who had the dashboard closed still see the queued conversation in the sidebar on next sign-in. ## New: public KB articles Curated answers can now be published as public knowledge-base pages at `/kb/{workspace.slug}/{article.slug}`. Each curated answer row carries `kb_title` + `kb_published` toggle and an inline publish pill in the admin grid. KB pages render operator-supplied markdown through a hardened converter (`App\Support\SafeMarkdown`) — raw HTML stripped, `javascript:` / `vbscript:` / `data:` URIs stripped from links. ## New: Google Sheets as a knowledge source Pick a sheet tab, the crawler ingests every row and treats each as a chunk seed. Mirrors the Notion / Google Docs OAuth path. ## New: multi-currency + lifetime-deal pricing Visitors on the marketing pricing page can pick their currency; Stripe Checkout uses the matching price. Lifetime-deal plans render as a separate section under the subscription tiers. ## Security hardening **Stored XSS on public KB pages — blocked.** `Illuminate\Support\Str::markdown` defaults to `html_input='allow'` and `allow_unsafe_links=true`, which would make `<img onerror=...>` and `[click](javascript:...)` in a KB article execute as live JS on every visitor browser. New `App\Support\SafeMarkdown` helper swaps in `html_input='strip'` + `allow_unsafe_links=false`. Ten unit tests pin every payload. **SSRF guard on the crawler.** A workspace admin pasting `http://169.254.169.254/...` (AWS metadata), `http://localhost:6379/` (loopback Redis), or `http://10.0.0.5/admin` (intranet) used to walk straight into the server's internal network on the `PlainHttpCrawler` fallback. New `App\Support\UrlSafetyGuard` blocks every private / loopback / link-local CIDR, refuses non-http(s) schemes, and (on crawl jobs) DNS-resolves hostnames to catch rebind attacks where `attacker.com` points at `127.0.0.1`. `PlainHttpCrawler` also re-validates every redirect hop and disables auto-follow. **Widget Origin re-check on every privileged endpoint.** New `VerifyWidgetOrigin` middleware re-validates the request Origin against the JWT-bound agent's `allowed_origins` on every `/v1/widget/*` endpoint except `init` (which enforces inline before issuing the JWT). Defence-in-depth against stolen JWTs replayed from an unlisted origin. **Browser-level defence headers on every web response.** CSP (`default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'`), HSTS on HTTPS requests (1 year + subdomains), `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`. Scoped to the `web` middleware group — the widget API is excluded because buyers embed cross-origin. **Upload MIME allowlist.** `UploadController` requires `mimes:pdf,docx,doc,xlsx,xls,csv,md,markdown,txt,odt,ods` on every uploaded file. `.html` / `.exe` / `.svg` / `.zip` uploads get a 422 at the validator before the parser sees a byte. **Mass-assignment cleanup on `User`.** `byok_enabled` and `default_workspace_id` removed from `User::#[Fillable]`. Both are privilege-bearing — sanctioned admin paths use `forceFill` after authorisation. ## Improvements **Crawler retry policy.** `CrawlPageJob` now distinguishes rate-limited (release-with-fresh-backoff, no retry slot burned), permanent (DNS error, 4xx) — short-circuit via `fail()` — and transient (5xx) failures. Stops the `MaxAttemptsExceededException` flood that used to fill production logs whenever a customer indexed a domain with a few dead URLs. Per-job timeout 90s, `failOnTimeout=true` so timeouts still flip Source rows to `failed` with customer-readable errors. **Cloudflare Worker cron driver hardened.** `TickCommand` `--max-time` bumped 25 → 55s (the loop exits before the 60s tick boundary), `--job-timeout` default 120s, queues processed: `crawl,index,default`. Resolves the `MaxAttemptsExceededException` flood reported in production. **PDF reindex works for uploads.** `UploadController` now persists parsed segment text under `storage/app/private/uploads/{source_id}/segment-N.txt`. The Reindex button reads it back without re-uploading the original. Pre-fix, "Reindex" was a no-op on file uploads because the controller short-circuited on a null `document.url`. **PDF parser sanitization.** Strips PDF stream artifacts, control bytes, and "/Type /Page" leakage that some encoders dump into `getText()`. Falls back to full-document text when per-page extraction returns empty (common for Word-exported PDFs). Drops segments with <75% printable characters as "no extractable text" instead of indexing garbage. **`pages_total` in source preview reads `services.crawl.max_pages_per_source`.** The progress indicator on the Sources preview now reflects the current cap (500 by default) instead of the hard-coded 25 that was stale after the bump. **Cloudflare AI Gateway support.** Set `CLOUDFLARE_AI_GATEWAY_URL` to route Workers AI calls through Cloudflare's gateway — observability, caching, optional per-account rate limits. **Documentation refresh.** New `/documentation/byok` page; security, knowledge, live-chat, allowed-origins, env, and deployment pages all updated to reflect the v2.0.0 changes. ## Fixes (operator-reported during v2.0.0) **"AI keys" link surfaced to customers.** After a buyer enrolled BYOK from the admin dashboard, their customers had no way to find the `/settings/byok-keys` form because no nav entry pointed at it. Fixed by adding a conditional sidebar entry driven by the `byok.unlocked` Inertia shared prop. **"AI keys" link hidden from super_admin.** Super-admins were seeing the customer-facing BYOK menu. They manage platform-wide AI credentials at `/settings/system` instead; the workspace BYOK form is customer-only. The controller hard-404s for super_admin too as belt-and-braces. **Token-count cap precision.** Replaced naive `mb_strlen / 4` heuristics for the max-tokens budget with a Unicode-aware estimator that distinguishes ASCII (4 chars/token) from CJK (~1 char/token). The plan's `max_tokens` ceiling now applies correctly to non-English visitor messages. **Sources errors are sanitised before reaching the customer UI.** Raw Cloudflare 401 JSON envelopes used to bleed through to the Sources list. `SourceErrorPresenter` now rewrites them to short friendly lines.

v1.2.0

Smarter follow-ups, multi-CTA cards, and behavior-trigger reliability

## New features **Suggested follow-up questions.** After every answer, the widget now shows up to three follow-up prompts the visitor can tap to keep the conversation moving. Reduces the dead-end "I don't know what else to ask" drop-off. Generated automatically from the answer's context — no setup needed. **Stage-aware typing indicator.** Instead of a generic "..." dot, the widget now tells the visitor exactly what's happening: "Searching your site…" while we pull relevant pages, then "Thinking…" while the AI writes the reply, then the streamed answer. Makes the wait feel deliberate instead of frozen. **Unread badge + soft chime when minimised.** If a visitor walks away from a half-finished reply, the widget pops a small red badge on the closed bar and plays a one-time soft tone. They come back, see "1 new message", click, and pick up where they left off. The chime respects the system's "reduce motion" preference and never plays more than once per session. **Up to three CTA cards stack per reply.** Earlier the agent could only show a single call-to-action card after an answer ("Book a demo"). Now configure up to three CTAs with priorities — all matching ones render as a clean stack so a visitor asking about pricing sees Pricing, Demo, and Docs together. Pre-fix only the top-priority CTA shipped. **Lead-form trigger strategy.** Every agent now has four ways to decide when the inline lead form appears: <strong>Engagement</strong> (default — fires once intent is detected or after a few turns), <strong>First message</strong> (every visitor sees the form on turn one — best for sales-led agents), <strong>Keyword only</strong> (only when the visitor signals real intent like "pricing" / "demo"), and <strong>Never</strong> (chat without lead capture). Pick the one that matches your funnel. **Self-host installation guide.** New <code>/documentation/installation</code> page walks operators from a fresh server to a running Blengi deployment — requirements, environment variables, database setup, asset build, Octane/Horizon/Reverb processes, cron, first admin, smoke test, troubleshooting. Covers both host cron and the one-click Cloudflare Cron Worker option. **Simplified Chinese for the WordPress plugin.** The Blengi WordPress companion plugin (v2.0.5) now ships with a complete Simplified Chinese (zh_CN) translation pack — 53 strings covering settings, sync buttons, status messages, and admin notices. WordPress automatically loads it when the site or user locale is set to 简体中文. **Bundled standalone documentation in the WordPress plugin.** Every plugin zip now includes a single-file <code>documentation.html</code> buyers can open without an internet connection. Mintlify-style reference covering installation, content sync, page builders, WooCommerce, REST API, troubleshooting. **Connected WordPress sites surface per agent.** The Integrations page in the dashboard now shows every WordPress site that connected the companion plugin to one of your agents — site URL, plugin version, WooCommerce status, "Last seen X ago". Confirms the integration is live without having to log into each WP install. ## Improvements **Existing agents no longer answer "I don't have enough information" with sources indexed.** The default similarity threshold for newly created agents drops from 0.78 to 0.5 to match how Cloudflare's embedding model scores real matches (it scores lower than OpenAI's by design). Visitors now get answers from the content you indexed instead of the fallback line. <strong>Note for existing agents:</strong> agents created before this release may still be on 0.78. If you see the "no information" fallback, open the agent's settings and lower the threshold to 0.5. **Analytics page survives stale-schema deploys.** Earlier a single broken column on the analytics dashboard would return a generic "Internal Server Error" page. The dashboard now degrades gracefully — missing data shows as zeros, a yellow banner explains "some metrics couldn't be loaded — the most likely cause is a pending database migration", and the rest of the page renders normally. **Customer-safe wording on crawl errors.** When a crawl failed — service hiccup, missing page, blocked site — the Sources list used to show the raw upstream message (sometimes including JSON envelopes from Cloudflare with status codes and error bodies). The customer-facing column now shows short friendly lines like "We couldn't reach this page" or "The crawl service is busy right now — we will retry automatically." Operators still see the full raw message under <strong>Show details</strong>. **PDF, TXT, DOCX uploads now reliably index.** A queue worker configuration was missing the indexing lane for file uploads, so PDFs and text documents could sit in "pending" indefinitely on some deployments. Knowledge uploads from now on flow through correctly and finish indexing within a minute. ## Fixes **Behavior triggers now actually fire.** The widget's behavior triggers — exit-intent, idle, scroll-depth, time-on-page, abandoned-cart — were silently disabled in earlier releases. Rules configured in the admin saved fine and showed up in the live init payload, but a teardown bug detached the listeners milliseconds after they attached, so they never ran. Buyer Dovydas's "behavior triggers don't work" report turned out to be real and a critical regression. Fixed; every trigger kind now fires as documented. No reconfiguration needed. **Three configured CTAs no longer collapse to one.** A buyer reported that adding three CTAs to an agent only ever rendered the top-priority one. The selector now returns up to three matching CTAs and the widget stacks all of them. Existing agents already configured with multiple CTAs see them all without any setup change. **No raw Cloudflare 401 JSON leaks into the Sources list.** Auto-indexed sources from visitor pages would sometimes show the raw upstream error envelope (Cloudflare Browser Rendering JSON with status codes, error codes, and authentication messages) directly in the customer-facing column. The customer now sees a sanitized "the crawl service is temporarily unavailable on your workspace" message instead; the raw message stays available under the operator's Show details toggle. **Analytics dashboard no longer 500s on stale schema.** Companion to the Improvements section above — the actual cause of every "Internal Server Error" report on <code>/app/analytics</code> we'd seen in the last few weeks.

v1.1.0

Visual workflow editor, customizable lead form, and widget polish

## New features **Visual workflow editor.** Build branching chat flows on a drag-and-drop canvas. Add trigger nodes, message bubbles, ask-and- capture questions, conditional branches, lead tags, and outbound webhooks. Branches fan out visually and reconnect cleanly. If you prefer a keyboard-first view, every workflow round-trips to a linear form editor — same data, two ways to edit it. **Conditional logic in workflows.** Three new step types let your flows do real work: branch on a captured answer (equals, contains, starts-with, is-empty, default), tag the lead so your CRM can route it, and POST conversation state to any external URL. Match modes any / all / exact let keyword triggers fire on the right intent instead of every loose match. **Custom lead-form builder.** Each agent now has its own lead-form schema. Pick from text, email, phone, long-text, dropdown, and checkbox fields. Reorder them, mark which are required, set placeholders and length limits. Four presets ship out of the box (Classic, B2B SaaS, Support, GDPR-friendly). The same schema renders in both the inline mid-conversation form and the new pre-chat gate so visitors see a consistent form everywhere. **Pre-chat lead gate.** Optional per-agent toggle. When on, visitors see your lead form before the chat surface unlocks — the conversion pattern Intercom and Drift have used for a decade. Once submitted, the chat opens on the same panel with no reload. Returning visitors don't see the gate again. **Widget position picker.** Three placement options for every agent: centered bar across the bottom (the default), floating bubble in the bottom-right corner (the classic Intercom / Drift layout), or bottom-left (mirrored, useful when the right edge of the page is busy with other widgets). **Restricted paths.** Per-agent list of URL patterns where the widget should not appear. Drop in `/admin/*`, `/checkout`, or `/account/*` and the widget stays out of those flows without touching your site code. **Annual billing.** Pricing page now has a monthly / annual toggle that surfaces the savings percentage based on each plan's configuration. Stripe Checkout uses the matching price. **PayPal and Razorpay payment gateways.** Stripe is no longer the only checkout option. PayPal works in every market PayPal serves (huge for buyers outside the US/EU); Razorpay covers India + South Asia with native support for UPI, cards, and net-banking. Both gateways slot into the existing plan + subscription model — admins toggle each gateway on or off from Settings → System and visitors pick at checkout. Webhook signature verification on both ends; the captured-lead and receipt emails inherit the same white-label cascade as Stripe. **Per-plan AI controls.** Rate limits and max-tokens are now configurable per plan from the admin console, so you can match each tier's generosity to your actual cost. **Live lead notifications.** A toast appears in the dashboard the moment a lead lands. Workspace members can opt in to browser-push notifications too, so the alert reaches them even when the dashboard tab is in the background. **Marketing site testimonials and FAQ.** The homepage gains an auto-advancing testimonial carousel and a 10-question FAQ accordion. Both are editable from Settings → Marketing — replace the shipped defaults with your own quotes and questions. **Search-engine optimization.** Every public page now ships proper SEO out of the box: per-page title and description, canonical URL, Open Graph card for social sharing, Twitter summary card, and structured data (Organization, SoftwareApplication, FAQPage). New `/sitemap.xml` lists every public route plus your published changelog versions. Updated `/robots.txt` allows crawlers on public pages and blocks them from admin / API surfaces. **White-label cascade everywhere.** Your brand name and logo now flow through outbound billing emails (Stripe, PayPal, Razorpay receipts), the captured-lead email, and the widget's "Powered by" footer attribution. **Lead-email pipeline test.** Settings → System → Mail gains a "Send test lead email" button that sends the actual lead-captured notification through your queue worker — catches the missing- worker bug class instantly. The existing raw SMTP test stays for checking mail-server connectivity. **Public marketing site toggle.** New "Public marketing site" switch in Settings → Branding. Turn it off and unauthenticated visitors hitting `/`, `/pricing`, `/changelog`, `/documentation`, etc. are redirected to `/login` — useful for private / internal SaaS deployments. `/privacy`, `/terms`, and the auth flows stay accessible always; search engines are told to skip the entire domain. ## Improvements **Workflows page redesigned.** The workflows list now matches the rest of the admin: search bar, status filter, sort menu, column visibility toggle, and pagination. Same shape as Agents, Leads, and Conversations. **Customizable widget defaults.** Three-tier merge for widget appearance: shipped defaults → platform admin defaults → workspace overrides → per-agent. Set a sensible house style once; customers override only what they want. ## Fixes **Widget no longer appears on signed-in admin or customer pages.** A two-layer fix: the application's root layout suppresses the demo widget for authenticated users (server-side, can't be misconfigured), and the new Restricted Paths feature gives buyers fine-grained per-page control. **Mobile homepage hero text no longer overflows** on narrow viewports (especially iPhone SE). **Settings → Widget** is back to a single layout wrap with a two-column form and a live preview that matches the real omnibar. **Brand consistency on the dashboard.** The "Set up Blengi in a few steps" sidebar card and the widget's demo pill no longer hard-code the source brand name — both follow your install's configured site title. **"What's new" banner.** Fixed a layout bug where the leading "What's new in" text could be clipped behind the sidebar on wide screens. Banner is now mounted inside the content column so the sidebar can't overlap it. **Changelog survives `migrate:fresh`.** Release notes now persist to `storage/app/private/changelog-entries.json` instead of a database table, and auto-seed from version-controlled markdown files at `database/changelog-entries/v*.md` on first read. New deploys pick up shipped release notes automatically; existing entries are never overwritten.