# Changelog All notable changes to FlowWink will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Release process: [docs/operators/release-process.md](docs/operators/release-process.md). ## [Unreleased] ### Removed - **The Global Elements module is gone.** It had no skills and no tables and was hidden ("merged into Pages"); its one effect was to be switched on by accident whenever an agent edited the header. Headers and footers are unchanged — they are Pages' `manage_global_blocks`. - **Browser Control is retired.** The module was a Chrome-extension relay for reading pages behind a login (LinkedIn, X) through an admin's browser. Nobody used it, it was off by default, and agents with their own browser do that job better and hand the result in over MCP. Gone: the module, the extension and its download, the chat's relay code (never wired), and the `browser-fetch` edge function. Its skill `browser_fetch` only forwarded public pages to web-scrape, so a call to it now answers "retired, use `scrape_url`". FlowPilot's system prompt told it to read every URL with `browser_fetch` while the off-by-default module hid that skill; it names `scrape_url` now. A migration removes the module from each instance's settings and access matrix and disables the skill row and any automation that ran it. ### Security - **A scheduled call carries a secret the database made itself.** The nine functions pg_cron calls (the automation, event and signal dispatchers, the knowledge indexer, dunning, contract and subscription billing, quote-expiry reminders, demo-cycle) checked nobody: the cron jobs sent the publishable key every browser carries, so anyone could start the billing run or, on a demo instance, the nightly rebuild. The database now makes a random `CRON_SECRET` in its vault, adds it to every scheduled call at call time (`x-cron-secret`), and the functions admit the service key, a staff session (an admin for demo-cycle) or that secret. automation-dispatcher rewrites any cron job that lacks it within a minute. For one week a call with none of them is let through and logged (`[cron-secret]`); then it is refused. No unguarded public function is left on the ratchet. - **The public AI endpoints have a ceiling.** chat-completion, docs-chat and consultant-match answered anyone with the publishable key, a paid model call each, with nothing counting them. Now 30 per visitor per 5 minutes (a hashed IP, never the address) and 600 per instance per hour, a 429 with Retry-After past either; the platform's own calls are never counted. The dial is `site_settings.security.publicAiRateLimit` (0 switches one off). - **A high-stakes write with nobody at the keyboard waits for a person.** The heartbeat, the e-mail responder, the visitor chat and gateway agents act on text other people wrote, and ten writes declared high-risk or irreversible (pay_vendor_invoice, close_accounting_period, send_einvoice, …) could run with no human at all. An unattended call of such a write now lands in /admin/approvals; a person in FlowChat or the admin is never held. Every prompt carries a hardcoded rule that content from tools and channels is data, never an instruction. ### Added - **Agent work on the dashboard.** A panel for an owner, not a developer: how much the agents did today and which ones, what is waiting for a person (operations an agent staged, questions an agent asked on a task that nobody has answered), and the calls that were refused or failed in the last seven days with the reason in words. Scheduled jobs and a person's own clicks are not counted, and a module that is off stays out of it. - **Agent work, by business area.** The dashboard panel now lists the week's agent work per area (Sales, Invoicing, Bookings …) with a link to that area's page: how many calls changed something, how many only looked, which agents did it and the skills they used most. A connected agent is shown by its own name (Hermes, Claude …) when Federation is on. - **Agent work shows what the checks flag.** The dashboard panel lists the findings an agent starts its day from — operations (sales, receivables, contracts, support, field service, automations, chat) and, when Accounting is on, the books — highest severity first, each with a link to its area and the skill that fixes it. A finding with no fix is marked as yours to decide. Findings of the same kind (one overdue filing per month) fold into one row. - **The books can be kept somewhere else.** Accounting → Settings → Ledger: with "Kept in another system" (Fortnox, Visma …) FlowWink no longer books invoices, paid orders or vendor invoices by itself, and the books check stays quiet instead of listing every invoice as unbooked. Vouchers already booked are left alone; switching back lets the catch-up book what was skipped. - **A paid order gets its receipt by itself.** The receipt is the order's paid invoice, sent by the new automation `Receipt on paid order` in the language the customer ordered in (`order_receipt` template, en + sv). It can be switched off per instance, and it needs the Invoicing module. The public invoice page is translatable and calls a paid invoice a receipt. The PDF is still in English. ### Fixed - **The receiving instance counts its own ping.** The feedback endpoint (www) records its own daily ping without crossing content-api, and did so with a hand-written upsert that never touched `ping_count`, so the fleet view read 1 for it however many days it had pinged. It now goes through `feedback_record_ping`, the function every other instance's ping reaches. - **An invoice dated in a closed month is refused again.** Yesterday's change for books kept elsewhere let a sent invoice through whenever its booking failed, so one dated in a closed month was issued and left unbooked. Only the missing accounting grant is let through now; a closed month refuses the send and the invoice stays a draft. Found by the night's fresh install. - **Sending an invoice no longer needs the accounting grant.** A salesperson without it was refused ("Requires the accounting module") because the booking behind the send could not run. The invoice is sent, the booking is caught up. - **An order is revenue when it is paid.** Marking an order delivered wrote `completed` over its payment status — losing `paid`, and turning an unpaid delivered order into revenue — and the weekly briefing counted revenue on `completed` alone, so a paid order not yet delivered was missing. Delivery no longer touches the payment status, and the briefing counts what the Orders page counts. - **The daily briefing's schedule is the platform's, not FlowPilot's.** The cron health filed the briefing job under FlowPilot, so with FlowPilot off a stopped briefing was never flagged and never required — while the briefing itself runs without FlowPilot. It is judged on every instance now. - **A receipt is not unbooked work.** A paid order is booked when it is paid, and its receipt is an invoice born paid. The booking functions already skipped it, but `check_books` reported it as "a customer invoice without a voucher" (high), `month_close_checklist` would have failed the month on it and `book_unbooked_invoices` listed it every run, booking nothing. One rule, `invoice_booked_with_its_order`, now answers for all three. - **Consultant profiles are re-embedded every hour again.** The consultants module schedules its reindex at `7 * * * *` (minute 7 of every hour), a shape the dispatcher's cron parser had no branch for, so it switched the automation off on its first tick — on every instance with Consultants on, since 2026-09-04. The parser runs `M * * * *` now, a guard checks every cron expression a module seeds, and a migration switches back on what the old parser switched off (only where the skill it runs is still enabled). - **Three inbound endpoints verify their caller; River's media is not listable by anyone; what stays open is written down.** `signal-ingest` accepted the publishable key (it ships in the site's JavaScript), so anyone could create blog drafts, memories and AI work — now the service key, a member of staff or the instance's own `signal_ingest_token`. `voice-recording` played a voicemail for anyone holding the call id — staff only, the panel fetches it with the session. `voice-ingest` takes the provider's URL secret on call webhooks, and an opt-in `VOICE_STREAM_SECRET` on the AI stream. `river-media` let anon LIST every file the team posted — images still open by URL, listing is staff's. `npm run qa:open-access` (nightly) lists every table policy, storage policy and public bucket open to an anonymous visitor from the database and requires a written reason for each. Known-ungated 12 → 9. - **Every process is proven to the screen a person looks at.** The battery proved the data; nobody opened /admin/invoices to see the receipt was there, and on an instance nobody runs the processes by hand. Scenarios now name where their outcome shows (`s.shows`), and `npm run qa:process-views` (nightly, after the battery) opens each view as an admin in a real browser and requires the run's text — 17 views across 16 processes. Its first run found three real gaps, fixed here: the quote list showed "—" for every quote made without a lead (it read only the lead, never the quote's own `customer_name`); the returns list never named the customer; Webinars and Recruitment could not be linked to a tab (now `useTabParam`). - **Gmail can be connected only by an admin of the site.** The OAuth `state` was the constant `gmail_connect`: anyone could consent with their own Google account and the instance stored it as its inbox; `status`, `disconnect` and `update_settings` answered anyone too. The POST actions need an admin (or the platform), and the consent screen opens only with a one-time state an admin is handed (`begin`), accepted once within ten minutes. - **migrate-page verifies its caller** (`requireServiceOrStaff`): it fetches any public URL and spends AI tokens on it. The wizard (staff) and `edge:migrate-page` (service key) are its callers. - The public-functions guard knows a resolver RPC handed the caller's token (`resolve_document_share({ _token })`) as token possession; `document-share` was token-gated all along. Known-ungated ceiling 16 → 12. - **UB becomes IB for agents too.** The agent report read stored opening balances per year, so a later year opened at zero and an agent booked the previous closing balance as a voucher. Every reader now derives it, and only the bridge of the first fiscal year can be stored (`IB_IS_DERIVED`). - **An order is invoiced at what the customer paid.** Gross lines were read as net and VAT added again, and a paid order's invoice asked to be paid. It is now split per rate and born paid. A cancelled invoice is no longer reused for its order, so the order can be invoiced again. - **An issued invoice cannot be deleted in the admin view.** Only a draft can: the link in the customer's mail stopped answering when the invoice went. - **Internal order markers no longer reach the customer** in a new invoice's notes. - **An alarm means something is wrong now.** The bell was amber every day on every instance, because any agent run on notify trust painted it; it is amber now for a person being needed or something still broken, and a healed integration stops warning. A read is no longer "an agent acted on its own". An automation is `stale` against its own schedule, not after 48 hours (a monthly job was dead 29 days of 30), and warns while its own newest run failed, not for a week after. - **The feedback board and the daily ping write again.** #922 took anon's write policies and its EXECUTE on `feedback_record_ping` on the belief that content-api writes as the service role; it wrote with the anon client under the name `admin`, so every instance's ping answered 500 from 2026-11-08. It now is the service client, and a guard scans every edge function for an anon client calling an RPC anon has lost. - **Panels of a module that is off leave the pages they are mounted on.** FlowBox shows no chat, ticket, form or call item and no Calls tab for a module that is off; Flowwork's document panel saves to the wiki only with Wiki on; deal and company pages carry reports only with Reports on; the Receiving tab in Inventory needs Purchasing; a lead's process flow shows only the steps (deal, quote, contract, invoice) whose module is on, and its open quotes need Quotes. `foreign-module-panels-ask` now reads the legacy dependency edges and ignores shared reference tables (people, currencies, the activity timeline): 24 findings → 8. - **A module that is off is quiet on Observability, the bell, the dashboard and analytics.** Automations, failed skill runs, MCP failures, staged writes, notify runs and pg_cron jobs of a module the operator switched off kept raising warnings and counts. They are now judged by the module that owns them (`src/lib/module-visibility.ts`, the server's `skillModuleOn`); cron jobs carry their module (`CRON_JOB_MODULE`) and are never red, never required and never "drifted" while it is off. Analytics no longer offers "Enable " prompts, Pipeline/Marketing tabs or goal kinds for modules that are off. New guards: `off-modules-stay-quiet`, `foreign-module-panels-ask`. - **Meeting polls says when it is off, and has a place in the sidebar.** The Meeting polls tab under Bookings rendered its full desk with the module off (the default) — `/poll/` answered 404 and every poll skill was refused, so the feature looked present and failed, or was never found. The tab now says it is switched off, with a door to Modules for an admin; the sidebar carries **Meeting polls** (`/admin/bookings/polls`) under its own module, so it appears when the module is on and the route gate agrees with it. A role that may use Bookings may use its meeting polls (`20261108200000`, insert-if-absent). - **One service client per isolate.** `getServiceClient()` in `_shared/supabase-clients.ts` memoises the service-role client instead of running `createClient` on each of its 118 call sites (some inside per-row loops; PR #926 counted 648 constructions in one gateway owner filter). Safe because no edge code mutates a service client's auth state; `getUserClient` stays per-request since it carries the caller's JWT. Measured: 1000 constructions ≈ 26 ms (bun, supabase-js 2.119) — small per call, paid on every hot path. - **A recurring price says its own period — on the site and at checkout (#941).** `products.billing_interval` (month | year) was set in admin and read by quotes, but every visitor surface printed "/mo" or "/month" for every recurring product, and Stripe checkout created every recurring price with interval `month`. A service contract at 14 900 kr per year read "14 900 kr/mo" on the site and, bought online, was charged 14 900 kr every month. Now one rule (`src/lib/price-period.ts`, `usePricePeriodText`) serves the shop page, the products block, the featured product, the product page and its related products, the pricing page and two admin previews, with `/year` and `/yr` visitor texts beside `/month` and `/mo`; checkout bills on the product's period (`stripeInterval`) and replaces a stored Stripe price whose interval does not match instead of reusing it (subscriptions already on the old price keep it). Guard `price-period-from-the-product` scans every file for the shape `type === 'recurring'` beside a hard-coded month. ### Security - **react-router 7.** `react-router-dom` 6.30 → 7.18 closes the two moderate react-router advisories (open redirect via a backslash in `` / `useNavigate`, constructor injection via `deserializeErrors`). The app runs one data router (`createBrowserRouter` in `App.tsx`) with absolute paths only — no splat routes, no relative links — so v7's one behavioural change (relative paths inside splat routes) has nothing to move. `npm audit --omit=dev`: 2 moderate left, both satori under `@vercel/og`. - **zod 4.** The last high advisory in the production tree (zod `<4.6.6`: memory exhaustion validating oversized constrained collections) is gone: `zod` 3.25 → 4.6, `@hookform/resolvers` 3 → 5, and the edge `ai-task` schemas from esm.sh 3.23 → 4.6. What zod 4 changed, applied: `z.record` takes its key schema (22 sites), `ZodError.errors` is `.issues` (5), a form whose schema carries defaults types its input and output apart. And one change no test would have caught: zod 4's `.uuid()` checks the RFC version bits and refuses ids the baseline seeds into every database (`aaaaaaa1-0000-0000-0000-000000000001`, `11111111-…`, `…00c0de`) — all 70 id checks use `z.guid()`, zod 3's rule exactly, and the guard `ids-are-guids` keeps the strict form out. `npm audit --omit=dev`: 4 moderate left (react-router 7, satori under @vercel/og). - **Production dependencies without a known high or critical advisory but two.** `npm audit --omit=dev` went from 18 (1 critical, 7 high) to 5 (1 high, 4 moderate). `vitest` and `jsdom` were in `dependencies` and dragged the whole vite / esbuild / rollup tool chain into the production tree — they are `devDependencies` now, and the critical one left with them. `pdfjs-dist` 5 → 6 (arbitrary JavaScript on opening a malicious PDF; the chat attachment reader, text extraction proved on a two-page PDF), `react-router-dom` 6.30.6, and patched copies of `lodash`, `ws`, `yaml`, `picomatch`, `fflate` and `postcss-selector-parser` pinned through `overrides`. Left, each its own change: `zod` 4 (high; 62 files), `react-router` 7 (moderate; a router major), and `satori` under `@vercel/og` — 1.0.3 requires `fs` in its edge build, which the edge runtime does not have, so the OG cards stay on 0.6.8 until a release that runs there. - **The method that should have caught it, repaired.** The weekly security scan had been red since its first run (2026-10-26) on `npm audit`, before the guardrails step ever ran, and nothing watched the workflow; the five tool functions sat in the public-functions guard's known-debt list for 40 days with a green badge beside them. Now the scan is two jobs (advisories and guardrails — one never hides the other), the guardrails job runs EVERY guardrail test instead of a hand list of nine (40 security-shaped guards were outside it), and each job that fails opens or updates one issue (label `security-red`) and closes it when green. The known-debt list carries a clock: a name may stay `KNOWN_UNGATED` for 60 days, then the guard is red with the two ways out named — gate it or prove it visitor-facing by design. A CodeQL workflow (javascript-typescript, security-extended) runs on every PR and weekly; it is one more reader, not the method — Deno's request sources are not modelled there, which is why the repo's own guards exist. - **Outbound URLs are judged before they are fetched (SSRF, CWE-918).** An independent researcher (Riddhish Lichade) reported that `extract-pdf-text`, `web-scrape` and `browser-fetch` were reachable without a caller check and fetched a caller-supplied URL as given, returning the body — and an edge function runs inside the project's network, where `SUPABASE_URL` is the internal gateway on a self-hosted instance. Confirmed on a local stack: the runtime fetched `http://kong:8000/rest/v1/` with a 200. Now one rule, `_shared/net/outbound-url.ts`: http(s) only, no credentials, no single-label or local hostname, every resolved address outside the private, loopback, link-local, CGNAT, multicast and reserved ranges (IPv4 and IPv6, mapped forms included), redirects followed by hand and re-judged, at most five; a name that cannot be resolved is refused (`URL_NOT_ALLOWED`). Applied at every content-URL sink: the web layer's reader (so Firecrawl and Jina never read a private target on our behalf either), web-scrape's sitemap and site detection, extract-pdf-text, browser-fetch, process-image, the image and résumé handlers, media import from URL, site-scrape and migrate-page. The five tool functions on paid keys (`web-scrape`, `browser-fetch`, `web-search`, `extract-pdf-text`, `process-image`) now verify their caller (`requireServiceOrStaff`: the service key or a staff session) and left the public-functions ratchet. An admin's own configured endpoints (webhooks, A2A peers, a local STT server) are not judged — they are the operator's network by design; `OUTBOUND_FETCH_ALLOW_PRIVATE=true` lifts the address check for an intranet deployment. Guard `outbound-urls-are-public` holds the rule and scans every `fetch` of a caller-URL identifier. `SECURITY.md` is new. ### Changed - **A sweep asks whether there is work before anything wakes.** Four automations fired every fifteen minutes on every instance (qualify new leads, score visitor intent, webinar reminders, the social post scheduler) and mostly found nothing — about 380 function invocations a day per instance. Each now declares a probe the database answers; with no work, neither the dispatcher nor the skill is woken, and the automation still reads as alive. The moment a lead, a scheduled post or a registration appears, the next minute's pulse runs it. An automation whose module is off is skipped by the dispatcher too. - **Two more sweeps ask first.** The SLA sweep (every 15 minutes) runs only when an SLA policy is enabled, and the hourly consultant reindex only when a profile is waiting to be embedded. - **agent-execute's pipeline is importable.** The 20 500-line executor moved from `agent-execute/index.ts` to `_shared/execute/core.ts` as `handleAgentExecute(req)`, byte-identical but for the import prefixes and the wrapper; `index.ts` is the HTTP door, `serve(handleAgentExecute)`. Nothing behaves differently. It is the first step toward running a skill in-process from the ReAct loop, the gateway and the dispatchers instead of over an HTTP hop inside the same project (one invocation per tool call, not two; one auth gate, not two). The 169 guards, scripts and docs that read the pipeline's source read the new path; the edge type-check baseline carries the file under its new name. - **The executor runs in-process for FlowPilot and the gateway.** The step after the move: the core answers `executeSkillRequest(body, auth)` → `{ status, body }` instead of `handleAgentExecute(req)` → `Response`, and the HTTP door keeps only what the wire needs (the auth header, CORS, the `async: true` handoff). The request contract is `_shared/execute/types.ts` (no imports). The MCP gateway's `executeSkill` calls it directly, and the ReAct loop runs it through `ReasonConfig.runSkill`, which the heartbeat, workspace-chat and agent-operate pass, instead of `fetch(.../functions/v1/agent-execute)`: one fewer edge invocation per tool call (a heartbeat with ten tool calls was eleven invocations on a 500K/month budget), no second auth gate, the JSON once. Measured on a local stack (`browse_blog`, n=40): through the gateway p50 68 ms before, 59 ms after — the hop's ~10 ms gone; direct unchanged at ~20 ms; what remains above direct is the gateway's own per-call work (key lookup, catalog). Behaviour is a move, not a change — the request still takes one JSON round trip at the door, so a handler never mutates the caller's own arguments. The dispatchers that rely on `async: true` and the other HTTP callers are unchanged. The 165 guards that read the executor as text read the core. ### Fixed - **The gateway pays for its own work once.** `/rest/execute` measured 220 ms where native `tools/call` measured 32 ms for the same skill on a local stack, and the database explained ten of those milliseconds: every REST call and every dispatch tool (`search_skills`, `read_skill`, `execute_skill`) re-read the whole exposed register (650 rows with schemas) to find one name, and the owner filter built a Supabase client per skill — 648 `createClient` calls — before asking whether the owner is an admin. The exposed list is remembered a minute per (groups, peer ceiling) — the same minute the native handler cache gives, and the run is still judged by agent-execute's own gate on every call; one service client per isolate; the admin verdict first and once per filter; a non-admin owner's module verdicts remembered a minute (`gateway-pays-once` guard). - **The executor pays for its own statements once.** Measured on a local stack with pg_stat_statements: of a skill call's 5–8 SQL statements, 4–6 were agent-execute's own — the lookup of the whole skill row (`instructions` included), the modules row, FlowPilot's trust RPC, an objectives select on every call though 9 in 10 skills have no objective keywords, and the journal insert; the handler was the minority. Now the lookup names its eight columns, the modules row is read once per call and started beside the lookup, the trust RPC overlaps the module gate, and objective tracking asks the map before the database. One statement fewer on most calls, two serial round trips overlapped; locally −17 % p50 on an RPC read (`executor-round-trips` guard). - **Open doors without visitors (security pass).** With the advisor's own questions against a local database: the public feedback board's anon write policies (the board writes through content-api; the DELETE policy's `true` let anyone delete every vote), a SECURITY DEFINER view of RMAs readable by anon, three definer views that handed staff data to any signed-in portal customer, and a definer RPC content-api calls as the service role but anon could call too. Migration `20261107130000` drops the policies, makes the views read as their caller, revokes anon, and gives the ping RPC to the service role. Everything else the lints raised is the architecture (public intake inserts, token-based public reads, service-role-only tables). - **The read surface knows nine more of its reads.** The local skill sweep (648 exposed skills called read-only through agent-execute; every read passed, every write was refused as designed) found reads whose names carry no read prefix — `read_partner`, `validate_address`, `estimate_delivery_date`, the `export_*` reports, `summarize_candidate_pipeline`, `read_sie_file`, `verify_document_integrity` — so FlowWork and a read-only key refused them. Each handler verified (selects only, writes nothing); they are `READ_EXTRAS` now. `fetch_ecb_rates` (writes rates) and `inspect_rendered_page` (drives a browser) stay staged. - **A module states a need by capability, not by product.** Site migration required Firecrawl by name and warned on a site that read the web fine through Jina or the built-in reader; the newsletter required Resend on a site that mails through SMTP. `requiredCapabilities` (`email`, `web:read`, `web:search`) is satisfied by any provider of it (`CAPABILITY_PROVIDERS`, the built-in reader always counts), the module card says "Needs an email provider (Resend, SMTP or Composio)" instead of "Missing: resend", and a guard refuses a requiredIntegration that has alternatives. - **A module that is off is off for visitors too.** Only the Knowledge Base page and the navigation asked the modules row; `/feedback` took posts with the feedback module off, `/shop` listed products with e-commerce off, `/jobs` took applications with recruitment off. `PublicModuleGate` in the router's root layout answers the site's own 404 for a public route of a module that is off (`public-route-modules.ts`, with or without a language prefix), waits for the row on a mapped route instead of serving the page, and a guard reads every public route out of `App.tsx` so a new page is mapped or declared module-less. - **The view sweep judges a denied page's late answers as warnings too.** A response whose body could not be read because the page had already navigated to Access Denied landed after the denied-page downgrade and kept its `error` — one false red per sweep. Findings are now judged on a snapshot when the visit ends. And the report header's badges sit in a `div`, not a `p` (React's nesting warning on `/admin/reports/:slug`). - **A module that is off is off for the browser too.** The sidebar hid a disabled module's pages, but the route gate asked only the role matrix, so an admin who typed `/admin/webinars` with Webinars off was served the page — and the first write there woke the module behind the operator's back. `moduleOffForRoute` reads the modules row exactly as the sidebar does; both gates (`AdminLayout`, `AdminRouteGate`) answer with the module's name and, for an admin, a door to Modules that opens that module's sheet. ### Added - **`npm run qa:quiet`** — a fresh stack must be quiet: the heartbeat answers `skipped`, the briefing answers `quiet` and writes its row without a report version, a chat message or a mail. The nightly fresh install runs it before anything is seeded, the one place a virgin instance exists. Its first run found two things that alarmed a newborn: every daily job was "unhealthy" because its hour had not come (`enrichCronHealth` now gives "never ran" a day's grace by pg_cron's own history, the pulse profile), and the language proof of `20261026080000` left a "Proof" lead in every instance's CRM — the migration cleans it now, and `20261107120001_bevisordern-lamnar-ingen-lead` removes the one already there. ### Changed - **`internal:` handlers are a registry, not a chain.** The executor's 77 `else if (handler === 'internal:…')` arms (`_shared/execute/core.ts`) are one typed `INTERNAL_HANDLERS` map (`InternalCtx` names what a handler may read of the request) and one dispatch line; the four long arms (`read_sie_file`, `comment_on_task`, `set_performance_mode`, `set_skill_trust`) are named functions. A guard now discovers every `internal:` handler in the seeds and requires a registry entry (or a response adapter, or the reconciliation prefix) — the 19 guards that each pinned one arm by name read the registry key instead. - **Developer has three doors, Skills one page.** `/admin/developer` is MCP Keys · Content API · Webhooks, each introduced by who it is for; the Content API explorer runs as the signed-in person (so "requires auth" endpoints answer) and its docs link exists. Skill control (enable, MCP exposure, definition) moved from Developer to `/admin/skills` → **Manage** beside **Browse**, with a sidebar entry under System beside Modules; `?tab=mcp-skills` redirects. Removed: the mock "Dev Tools" tab, the n8n template brochure, the explorer's third copy of the MCP client config (the wizard has it). ### Fixed - **A read-only key is read-only over REST too.** `/rest/execute` ran outside the gateway's request context, so the `read_only` scope was never seen there and such a key could write over REST; the context is now opened in the auth middleware for every route. The verdict is the platform's own read rule (`_shared/skills/read-surface.ts`), not the tools/list verb heuristic, which did not know `browse_` / `query_` / `count_` and refused a read-only key `browse_blog`. A `manage_*` call with a read action passes. - **Quiet when there is nothing to say.** A fresh instance was alarmed every morning. The daily briefing now asks which modules are on before every section and action item, has no health score without a signal (no more yellow 50/100 on an empty week), and on a day with no action item and no figure writes its row and sends nothing — no report version, no chat message, no mail. `check_operations` judges an area only when its module is on (automations always) and answers `areas`; `check_books` accuses nothing before `books_started_on()` (the earliest of the first declared fiscal year and the first posted journal entry) and says so. The "Set up weekly digest" starter objective is gone (completed where it exists): the digest is an automation, and the always-active objective was what kept the heartbeat's idle short-circuit from ever firing on a fresh instance. - **One module switch.** Switching a module on or off is four writes that must agree — the row, the skills (both flags, one write), the automations that run them, the decision record — and there were two implementations and three more writers of the row alone. `_shared/modules/module-switch.ts` is now the one implementation: `manage_modules`, the Modules page (single and bulk toggles, awaited, refusals shown), `manage_site_settings key=modules` and `install_template` all run it. A module switched off in the admin and on again gets its automations back (the record carries their ids); an automation that runs the module's skill stops whatever group seeded it; a template's enable takes the parents along; the browser template installer runs the same switch, the last writer outside it. - **A module that is off is off for agents.** Skills were gated by category (open when any of a category's modules was on; `commerce` lists 24), so a module that was off but whose rows had leaked stayed visible and ran. The gate is now the skill's owning module (`_shared/skills/module-gate.ts`): the MCP gateway and FlowPilot hide its skills, and agent-execute refuses one with `MODULE_DISABLED` — a refusal that names the module and the way on, where a hidden row used to answer "Skill not found". The skill sync heals leaked rows for every off module, not only the ones with a teardown record; the wake-on-write of an untouched module (Law 4) remains for a row that is still enabled, which the heal ends — in practice a module is switched on under Modules or with `manage_modules`. - **The native MCP transport answers for the caller.** The McpServer was built outside the request context and cached per `?groups=` alone for five minutes, so every key saw the same `tools/list` — whatever its peer ceiling, its owner's reach or the modules switched off meanwhile — and a native `tools/call` skipped the key-scope and ceiling checks REST ran. The handler is now built and run under one store, the list is the owner's reach and the key's scope, the cache is keyed by the caller's reach and lives a minute, and every native tool handler runs the same three checks as REST. - **One skill description reaches every surface, and `?groups=` works.** `search_skills` ranked by the row description ("Use when: / NOT for:") while native `tools/list`, `/rest/tools` and FlowPilot's pre-narrow sent the function description, which lacked the markers in 555 of 657 exposed skills; all three now send the rankable one, and a ratchet keeps every exposed skill marked and under 2000 characters (the baseline may only shrink). A `?groups=` token narrows through the owner map — it used to resolve through a ten-module name heuristic, so `?groups=leads`, `blog` or `hr` returned nothing. ### Added **Reports — what an agent worked out, as a page** - Scheduled reports: the daily briefing, the weekly digest and the month close are published as versioned pages (`daily-briefing`, `weekly-digest`, `month-close--`); the briefing's chat message and mail link to them. - "Mine" on Reports — the shared ownership lens over the first requester; `list_reports mine: true` over MCP. Reports stay shared within the role matrix; the module is the wall, the lens a filter. - The Reports module: `publish_report` turns an agent's result into a versioned page under `/admin/reports/` — markdown with KPI tiles, bar / stacked / line / pie charts, notes, details, or a sandboxed HTML page. Every publish is a version, identical content adds none, `expected_version` refuses a stale write. The platform stamps who published (FlowPilot, Flowwork, the MCP key's agent) and who asked; archive, restore and rename are a person's. - Live blocks: figures fetched when the report is opened, as the reader, reads only — a report of live blocks is a dashboard. "The state of things" / "Läget just nu" is the standard one, published by FlowPilot in the site's language and kept present by a daily automation. - A report about a company, a deal or a project is shown on that page; the accounting workspace lists the reports written for the books. **Workspaces** - Accounting, HR, Sales and Support get their own left menu; the FlowWink sidebar folds to a rail and the person's gesture is remembered — no setting. Employees reach their own pages (expenses, timesheets, leave, payslips) under My account. **Users and the platform** - The agent's day: **Activity** per connected agent on the Agents page (calls, outcomes, skills, reports, steps — an admin's or the owner's read) and `agent_day_report` over MCP. The activity log carries the agent's key id as a column now, set on every logged call and backfilled. - The MCP door is judged on its body: the admin's address probe, the Connect-agent wizard's **Test the connection** and the nightly MCP regression (`MCP_PROBE_SITES`, every site's `/mcp`) all make a real `initialize` and read the decoded answer — a header probe had passed a `/mcp` that answered plain JSON labelled gzip for a week. - Last sign-in and last activity on the Users table, and in `users_list`. - Module automations follow the edge deploy: the first admin visit inserts the ones a module is missing, by name, once — a retuned schedule or a deleted automation is left alone. ### Fixed - The nightly view sweep no longer goes red on a role refusal whose body the browser could not read: a refusal by the role matrix is a warning by its shape, an unreadable body is retried and named. - Reports published before #891 with a capitalised subject ("Company") are normalised, so they show on their company, deal or project page. - The scheduled reports and the overview dashboard are stamped "FlowWink", not "FlowPilot", when the platform's automation publishes them; the agent-day lookup escapes % and _ in an agent's name; users_list's role filter is capped. - Three pages that failed for the role they are for (newsletter and docs RPC grants, the web layer's state, `propose_bookkeeping` gating); HR can use Shifts, Compensation, Training and Disciplinary. - An admin save of the modules page writes its change, not its tab's copy. ## [4.0.0] - 2026-10-28 The "operated in production" release. v3.0.0 made the platform agent-operable; since then nine instances have run it for real (Nordbrygg, optic, www, the forks), with Hermes, OpenClaw and Claude as operators, and almost everything below was found by an operator or a process battery, not by a plan. Three architecture arcs land here — the partner spine, the language overlay model and the agent envelope — and one transport is removed, which is why this is a major. **Upgrading:** a fork or a self-hosted instance takes the four layers in the usual order (schema → edge functions → skills → frontend). Migrations are idempotent and forward-dated. Federation peers that used the A2A transport must move to the MCP gateway (`mcp-server`, `?mode=dispatch`); nothing else is breaking. ### Added **Partners — the customer spine (Odoo's res.partner, not our own form)** - One `partners` register behind leads, companies, customers, vendors and our own company: a lead gets a party, the document chain (quote → order → invoice) carries one, the ledger books on the commercial party, bank accounts belong to the legal person, "invoice A, deliver to B". - Commercial terms are inherited from the company; the party's language is not. Jurisdiction on the party, rules in the locale package. A party is archived, never deleted. - Customer 360 and the partner directory read the same register. **Language — the overlay model (pages, blog, products, UI text, mail)** - A page, a post or a product is written ONCE in its source language; every other language is a published overlay in `content_translations`, filled by `translate_page` / `translate_content`, kept per translation unit so an edit only re-translates what changed. `convert_translation_groups` migrates the old sibling-row model with a dry run. - Products and categories in every language, variant labels (attribute names and values) as site-wide overlays, category slugs per language, block links that follow the page's language. - `//` addresses, hreflang, paired sitemap entries, a language switcher that shows only when needed, the visitor's language carried into customer mail, chat answers and the footer. A site declares its languages and can change its default; adding a language copies the site. - The visitor-text catalogue (`ui_text`) with an editor, and a scanner that ratchets hard-coded English down — the number may only shrink. - Company locale: where the company is (three list choices, one truth), the company's clock, calendar, currency and amount decimals written the same way in the database, the agent, checkout and every admin page. **The agent envelope and the operations reads** - Every skill answer carries `{ error, code, retryable, hint }` on failure, a refusal that is not a failure (`status: refused`), and `risk` / `reversible` / `reversal` declared on writes; the MCP gateway exposes them as annotations. - `check_books` (what needs attention in the books, each finding with its fix), `check_operations` (sales, receivables, contracts, support, field service, automations, chat), `statutory_deadlines` and the register of filed declarations, `month_close_checklist`, `agent_context` on every finance answer, `manage_automations list` with a `state` judged against each job's own schedule. - A read-only API key, `set_skill_trust`, `invite_colleague` (trust `approve`), `list_objectives` / `manage_objective` (closed on evidence only), `search_skills` / `read_skill` / `execute_skill` dispatch mode on both MCP and REST. - My agents: connect your own agent with one key, one owner; it acts as its owner. Claude connects through Connectors with `X-API-Key`. **Accounting** - Broken fiscal years, the seven-year archive (document hash, retention lock), voucher approval, cash forecast, consolidation, the rule loop for bank events (a rule is proposed after the decision), sales and purchases booked in the company's currency, orders and POS day-ends reaching the ledger. - UBL 2.1 / Peppol BIS 3.0 e-invoices: export, validate, dispatch — never pretends sent. **FlowBox — one queue** - Mail, chat, tickets, forms, callbacks and voicemail in one inbox; FlowPilot drafts first and the draft waits in the queue; Routing as its own page; the mailbox as a mail client with an AI responder behind the trust dial. Live Support is retired into it. **WebMeet and presence** - Video calls that get through the firewall, a waiting room, screen share, "here now" presence with one tap to video, calls started from River and FlowWork. **Modules and surfaces** - Meeting polls (invite on several times, a rule takes the first that works), newsletter mailing lists and segments with bounces reaching the ledger, purchasing blanket agreements and call-offs, per-staff booking calendars and video-meeting links, HR performance reviews and the salary revision round, expense advances and receipt currency, project task cards with brief, checklist, dependencies across projects and one thread for people and agents, wiki grouped by tag, Odoo import (`import_from_odoo`, CRM over the API, re-runnable) with an Odoo lens on the CRM. - The feedback module, on by default: boards bug / feature / template / question, votes, comments; an instance sends feedback and a daily usage ping (instance id, site URL, skills sha, schema head, enabled modules — nothing else) to its endpoint, `www.flowwink.com` unless repointed; the receiving instance has the public board at `/feedback` and the fleet under Admin → Feedback. One switch turns the ping off (`FEEDBACK_SHARE_USAGE=false` or Admin → Feedback); `docs/operators/telemetry.md` says what is sent. - Design dials an agent can see: surface, theme, heading colour per surface, the inverse band, the pixel gradient, image treatments, a section divider, a 3D model block, the footer's surface, the automatic band alternation — all branding settings, no stylesheet in `custom_scripts`. One HTML document for every reader (`api/html.ts` fills the head per page view, no User-Agent list). - Site export carries the knowledge too (wiki, documents, media); templates install consultants, souls and objectives; FlowPilot has a soul wherever it is on. - Admin: search and filters on contacts, automations and the icon picker; twelve pinned pages in the header; the collapsed rail; footer link columns that say where they come from; Pages is the editor's, Branding the admin's. ### Changed - **Process battery and nightly fresh install.** Every business process in `docs/processes/` runs end to end against a stack built from zero every night (migrations, template, battery, smoke, MCP regression, view sweep); a green run stamps the pulse and a stale stamp turns every PR red. Parity scorecards are re-scored against it. - **Generated artifacts are the bot's.** Skill, automation and locale bundles, the instance manifest and the skill→module map are rebuilt by CI and committed on main after every merge; a PR that carries them fails. - The skills layer follows the edge deploy: `sync_skills_from_code` runs on every admin page load against the bundled artifact. - Migrations are forward-dated by a command (`npm run migrations:redate`); edge functions type-check in CI; the edge-function count is budgeted at 90. - Guards discover instead of enumerate: hard-coded visitor text, hard-coded shapes, hand-rolled role policies, swallowed reads, unconditional public inserts, ghost block fields, stale block schemas — each a scanner with a ratchet. - ESLint runs as a ratchet (findings may only shrink); fork syncs run once a night; the Docker image builds on release tags only. ### Fixed - Money follows the order: returns capped, POS discount and drawer, VAT on credit notes, one reservation per order, pick before ship, stock movements carrying their reference, manufacturing seeing the inventory, depreciation counting months, reports reading the whole ledger (the 1 000-row cap cut every report). - Bookings: rules on the table (opening hours, blocks, past dates, overlap under lock, state machine, time zone), confirmation born on, reminder born at all, one reader for free slots. - Contracts: the signed document is final and the signature creates the service; appendices render; an agreement is sent only when finished. - CRM: prospect is not lead, triage outside the owner lens, the editable ledger, research on the company, prospects pruned after 24 months, leads say their company, a deal starts at its first stage. - Chat: no more 150 s death on its own receipt, a throwing tool does not end the answer, sources under the answer, tables as tables, markdown links as links, the answer in the visitor's language on every channel. - Pages and blocks: dozens of block fixes (panel radius and status tokens everywhere, hero on iPhone, true parallax, typewriter descenders, gallery lightbox, article grid, CTA, forms, contact), stale chunk after deploy reloads instead of 404, the overlay header announces its height, the cookie banner off the mobile menu. - Install: a fresh instance is born with its currency, its language declared, the first page as start page, role defaults seeded, FlowPilot on and silent until it has objectives; the heartbeat skips without an AI provider instead of failing; the cadence quota holds at the call site. - The agent surface: skills that could never run (wrong parameter names, missing branches, a comma that emptied a module's skill list), the staged approval envelope naming the order that works, PGRST202 self-correction on the generic CRUD path, `manage_page` honouring menu fields, `manage_product` answering delete, task ids that self-correct. - Operator rounds on optic and Nordbrygg: FlowWork answers carry their receipt, slugs keep å/ä/ö, the renewal cron's lost clock, a paused run that rang for a month, invoice due dates from the terms, currencies upper-cased and defaulting to the business currency. ### Security - 30 unguarded staff RPCs closed to anonymous callers; anonymous reads of equipment, maintenance, budgets and delegations closed; the role request comes from `app_metadata` and "authenticated" is not staff; the admin guard fails closed; orders are not born anonymously. - The CMS's raw-HTML and href surfaces closed: sandboxed embeds, one href rule, a serializer that never interpolates stored HTML. - Inbound provider webhooks carry a secret; 46elks credentials travel only to 46elks; `send-webhook` is the platform's to call; a public edge function that carries the service key verifies its caller (guard with ratchet). - Browser-hardening headers on Vercel, the chat lead oracle closed, DOMPurify / Tiptap / PostCSS patched, a weekly dependency security scan; agent API keys never at rest in clear text; invitation links survive email scanners (our page, a hashed token). - The quote signature hashed an empty line list and called it proof — fixed. - Customer-scoped RLS mirrored to `authenticated` where the visitor could see more than the signed-in customer. ### Removed - **The A2A federation transport.** MCP stays and is the one way in for an external agent. Peers on A2A must reconnect through `mcp-server`. - Portal (customer self-service) skills leave the MCP surface — they are reached through the customer portal's own identity, never a gateway key. - Live Support as a separate module (folded into FlowBox); the `contact.showForm` and `chat.showSidebar` block fields nothing ever read; the Lovable-managed dev instance and the old Agent Bridge. ## [3.0.0] - 2026-07-19 The "agent-operated business" release. Since 1.3.0 the platform has completed the turn from AI-assisted CMS to a Business Operating System run by agents — internal (FlowPilot) and external (any MCP operator) — with the safety architecture to match: identity-scoped customer self-service, evidence-bound autonomy, and a hardened fleet. (Version note: this release supersedes both the stale `v2.0.0` git tag from the early CMS era and the 1.x changelog line — the two numbering tracks merge here.) ### Added **Identity ladder — customers talk to the business, safely** - Customer portal assistant (`/account/assistant`): signed-in customers get an AI assistant scoped to their OWN account (rung 2) — orders, invoices, returns — with identity server-injected from the verified JWT, never from a body claim. - Customer-scoped self-service returns: `request_return` resolves the order only among the caller's own orders; ownership enforced by construction. - **B2B company scope (rung 3)**: `company_contacts` membership table, server-injected company identity, and eight company-scoped skills — reads (orders, invoices), writes (`request_company_return`, `reorder_company_order`, `request_company_quote`), commitment (`approve_company_quote`) and admin (`manage_company_contacts` with invite-on-signup) — role-gated viewer < buyer < approver < admin, enforced in the handler. Cross-company isolation proven adversarially: a contact of company A can never see or act on company B's data. Staff "Company Contacts" admin surface included. - Pay-your-own-invoice via the payment rail: the assistant resolves the company's invoice and hands out the secure payment page — it never moves money. - Company context dial: the assistant sees the active company's open items alongside the personal account, with explicit disambiguation between the two. **FlowPilot 2.0 — the Hermes arc** - Follow-through: staged and multi-step operations are resumed, not forgotten (`flowpilot-followthrough` sweep). - Pipeline-collapse composites for chained flows, with dial inheritance — a composite never bypasses a stricter inner-skill gate. - Skill Curator learning loop: evidence-driven proposals that improve skill instructions over time. - Cost dials: heartbeat cadence and reasoning tier are per-instance configuration. - **Evidence-bound objectives**: progress notes are stamped with machine-derived evidence (real skill outcomes from the activity log), and an objective can only be completed when its plan's work is actually evidenced — the agent's say-so is not proof. **Agentic accounting (BR1)** - Bank ingest → `propose_bookkeeping` → human review queue ("Händelser att bokföra"): the agent proposes, staged postings await approval, money never moves on its own. - Opening balances (IB) management and Swedish VAT return preparation (SKV 4700 box mapping) via `prepare_vat_return`. **Platform & operations** - Outward MCP gateway connection profiles: `?groups=` specialist surfaces and `?mode=dispatch` (2-tool `search_skills`/`execute_skill`) for external operators, plus the `/rest/*` compatibility layer with distributed locking. - Skill Relevance Engine as a platform primitive (shared by FlowPilot and the gateway), with IDF-weighted name matching. - Self-hosted distribution: prebuilt multi-arch frontend image on ghcr (`flowwink-frontend`), Easypanel-ready. **Releases now publish pinnable `:vX.Y.Z` and `:stable` image tags; `:dev` remains bleeding edge.** - Edge-function tier model (core vs optional) with core-verify, and the align-down rule: the skill surface always mirrors what is actually deployed per instance. - Observability: `cron_health_report` makes silent scheduled-job failures loud; pg_net response codes as the source of truth for cron health. ### Fixed - Automation cron parser: monthly expressions (`0 5 1 * *`) no longer silently fall back to hourly runs; weekday ranges (`1-5`) no longer run Mondays-only; the unsupported-expression fallback now logs. - `newsletter-dispatch-scheduled` self-references its own instance (was hardcoded to the dev project fleet-wide); `publish-scheduled-pages` calls the DB function directly instead of a nonexistent edge function. - Automation executor semantics (`flowpilot` executor requires the module on) and the `isModuleEnabled` site-settings gate. - `prepare_vat_return` declares its period as required (`anyOf` in the tool schema) so agents pass it correctly. - Visitor-intent lead trigger self-heals to its own instance. ### Security - Fleet hardening sweep: RLS on all tables, `SECURITY DEFINER` functions pinned with `search_path`, the `agent_automations` permissive-UPDATE control-plane hole closed, service-role guards for agent-callable admin functions. - Trust architecture: staged operations (`approve_pending_operation` handshake), per-skill trust levels that survive resyncs, and role/scope gates enforced server-side — never by the model. ## [1.3.0] - 2026-05-28 ### Added - **Demo Data Platform**: Safe module reset & simulation data seeding (`seed_module_demo`, `reset_module_data`) with `demo_runs`/`demo_run_items` tracking - **Order Tracking**: Public `/track/:id` page + admin fulfillment view with full event audit log - **Platform Tests**: Unified `/admin/platform-tests` replacing smoke tests with module-aware suites, CI guardrails, and instance health dashboard - **User Role Guardrails**: Automated test suite preventing role assignment regressions in `handle_new_user` trigger and edge functions - **Demo Mode**: Toggle in `/admin/settings/general` controlling hourly `demo-cycle` reset job; demo login banner on auth page - **Workspace Chat**: Internal RAG-powered chat over documents/contracts/knowledge base with citations - **Expense Procure-to-Pay Loop**: Full lifecycle (draft→submitted→approved→booked→paid) with BAS 2024 defaults - **OCR Bank Statement Import**: Vision-based preview→commit flow via `reconciliation-import-image` edge function - **POS v2**: Barcode support, split tender payments, session-based batch journal on close - **Stock Event Listener**: Automatic inventory movement on POS sales via `stock.movement` platform events - **Invoice-driven Subscriptions**: Manual subscription billing with daily cron invoice generation - **Staged Operations Envelope**: Human-in-the-loop approval for sensitive skills (journal entries, expense booking, period close) - **Voucher Integrity**: Auto-assigned voucher numbers with gap detection and explanation tools - **Year-End Readiness**: 6-point checklist RPC for accounting period closure - **Contract Templates**: 4 seeded templates + `create_contract_from_template` with anti-hallucination guards - **Unified Sales Lead Loop**: Firecrawl-enriched lead scoring with normalized status aliases - **Agent Document Upload**: MCP-exposed skill for peers to upload text/PDF to the document vault - **Document Shadow Markdown**: Extracted text shadow for full-text search in workspace chat - **Marketing Claw Department Pattern**: Composite MCP groups for external marketing agent operation - **Federation Directional Connections**: Multi-channel peer architecture (MCP, A2A, /v1/responses) - **HR Auto-Contract Loop**: `hire_application` RPC — application→employee + draft contract + onboarding checklist in one transaction - **Timesheet Period Lock**: Accounting period close also locks time entries - **HR Vacation Auto-Allocation**: Yearly vacation day allocation with audit logging - **Unsplash Config Hint**: Admin alert when image API key is missing during block editing - **Integration Status Truth**: `resolveIntegrationStatus` single source of truth; backend verification errors surfaced in UI ### Changed - **Removed Clawable module**: Simplified architecture — single FlowWink instance, no multi-agent chat surface - **Federation cleanup**: Reduced operator missions to 5 core categories aligned with external-agent-as-operator pivot - **Smoke tests**: Skip disabled modules instead of false failures - **Database migrations**: Consolidated baseline for cleaner fresh installs - **Trust levels**: Outbound communication skills now require `approve` instead of `auto` ### Fixed - **check-secrets edge function**: 403 errors for multi-role users resolved by explicit `admin` role filtering - **handle_new_user trigger**: No longer assigns redundant `writer` role when `signup_type: "admin"` is specified - **Demo template**: Fixed demo email credential display (`demo@flowwink.com`) - **Fresh install UX**: Default modules disabled by default (opt-in); fewer confusing empty states - **Edge function timeouts**: Fire-and-forget pattern for long-running operations bypassing 60s limits - **CI**: `skill-linter` exits gracefully without DB URL on GitHub Actions ## [1.2.0] - 2026-02-20 ### Added - **Live Agent Chat Avatars**: Agent profile photos now display in chat widget for personalized support experience - **Sentiment Detection**: Real-time AI-powered sentiment analysis during conversations - Automatic frustration detection (caps, repeated questions, negative words) - Configurable threshold (1-10 scale) for human handoff triggers - Visual sentiment indicator on Live Support page (green/yellow/red) - **Human Handoff Improvements**: Enhanced escalation with explicit "speak to human" detection - **Live Support Dashboard Widget**: New admin dashboard widget showing: - Active and waiting conversations - Online agent count - Average sentiment metrics - Quick access to support queue ### Changed - Support agents table now has public read access for chat widget avatar display - Improved agent presence detection in chat conversations ### Fixed - Agent avatars now correctly display for anonymous chat visitors (RLS policy update) - Profile avatar URL properly fetched from profiles_public view ## [1.1.0] - 2026-01-16 ### Added - **Self-Hosting Setup Script**: Complete CLI-driven setup (`./scripts/setup-supabase.sh`) - Automatic Supabase login with interactive prompts - Project selection with numbered list - Deploy all 33 edge functions automatically - Run database migrations - Create admin user with proper role assignment - Output environment variables for deployment - `--fresh` flag for agencies setting up multiple sites - `--env` flag to only display environment variables - **Secrets Configuration Script**: Interactive secrets setup (`./scripts/configure-secrets.sh`) - Resend (email) - Stripe (payments) - OpenAI (AI features) - Google Gemini (AI alternative) - Firecrawl (web scraping) - Unsplash (stock photos) - Local LLM (self-hosted AI) - N8N (workflow automation) ### Changed - Auth page branding updated to FlowWink - Setup documentation rewritten for CLI-first workflow - Improved error handling in setup scripts ### Fixed - Environment variables now fetched via `supabase projects api-keys` - Admin user creation uses Supabase Admin API directly - Project selection handles deleted projects gracefully ## [1.0.0] - 2026-01-09 ### Added #### Core CMS - Block-based page builder with 20+ block types - Visual drag-and-drop block reordering - Block animation controls and spacing settings - Page versioning with restore functionality - SEO meta settings per page - Scheduled publishing - Global header and footer blocks - Menu ordering system #### Blog Module - Full blog engine with posts, categories, and tags - Featured posts support - Author profiles with avatars - Reading time calculation - RSS feed generation - SEO-optimized blog pages #### Newsletter Module - Subscriber management with GDPR compliance - Email campaigns with tracking (opens, clicks) - Double opt-in support - Export functionality - Unsubscribe handling #### CRM Modules - **Leads**: Lead capture, scoring, AI qualification, status tracking - **Deals**: Kanban board, pipeline stages, activity tracking - **Companies**: Company profiles, domain enrichment, lead association - **Products**: Product catalog with pricing (one-time and recurring) - CSV import/export for leads and companies #### Knowledge Base - Hierarchical categories with icons - FAQ-style articles - AI Chat integration with context - Helpful/not helpful voting - Featured articles #### Integrations - Webhook system with event triggers - N8N workflow templates - Unsplash image picker - AI text generation (expand, improve, translate, summarize) - Brand guide analyzer #### User Management - Role-based access control (Writer, Approver, Admin) - User profiles with avatars - Activity logging #### Public Website - Responsive design with dark/light mode - Cookie consent banner - Contact forms with submissions - Booking forms (Cal.com integration ready) - Chat widget ### Security - Row Level Security (RLS) on all tables - Secure authentication flow - GDPR-compliant data handling --- ## Upgrade Instructions See [docs/UPGRADING.md](docs/UPGRADING.md) for detailed upgrade instructions. ## Contributing See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for contribution guidelines.