Changelog
All notable changes to Compass land here. Format follows Keep a Changelog and the project loosely follows SemVer.
When something ships to production, move it from Unreleased into
a dated release block. git log is the source of truth for
attribution and commit hashes; this file is the human-readable
summary of what users care about.
[Unreleased]
The next release is taking shape below.
Changed
Availability schedules read as a tidy list. With more than one schedule, each now shows as a collapsed row (name, Default badge, and a day summary like "Mon, Tue, Wed, Thu, Fri") instead of every schedule's full week-grid being open at once. Click a row to expand and edit it; the only schedule stays open by default.
T79 (part of) — vibrant stage colours. The pipeline stage colours on the Client Tracker pills (board, list, record peek, cockpit) are now punchier and more saturated, matching the locked design-elevation direction: New reads as a vivid indigo, In conversation a vivid blue, In production a vivid purple (Won stays green; Completed, Lost, and parked statuses unchanged). The calendar's lead-status dots now read from the same single colour source (
STAGE_PILL_COLOR), so the tracker pills and the calendar can never drift apart.
Fixed
Workflow reminders stop piling up in your bell. The daily "N workflow tasks due" reminder used to add a brand-new notification every run, so the bell filled with identical rows. It now keeps a single rolling reminder (each run replaces the previous unread one); read notifications stay as history. (Detaching a workflow already removes its tasks, so once you stop chasing a workflow, the reminder clears on the next run.)
Client Tracker list columns no longer sprawl. The Event (venue) and Last activity columns clamp to a maximum of two lines (a long venue like "St Nikola Macedonian Orthodox Church" no longer pushes a row to three lines).
Stage pills no longer wrap to two lines. On the Client Tracker list, a longer stage name (e.g. "Consultation done", "Discovery call") could break across two lines in the narrow Stage column. The pills (list table, mobile card, and record peek) now stay on one line.
B10 — "This page couldn't load" flash when opening the old Enquiries/Jobs/Calendar URLs. The T126 retirement redirects were in-page (
redirect()from a rendered route), so the browser rendered an intermediate page and Safari briefly flashed its native "This page couldn't load" error before following the redirect. Moved them to edge 308 redirects innext.config.ts(Location header only, no intermediate render), so/enquiries,/enquiries/board,/jobs, and/calendarforward into the Client Tracker with no flash. Detail cockpits (/enquiries/[id],/jobs/[id]) unaffected.HIGH (B9) — a new website enquiry could silently fold into a closed or trashed lead. The webhook's 24h duplicate guard matched any lead with the same email regardless of status or trash, so if the studio had marked that person lost/cancelled (or deleted them) within 24h, a genuine new enquiry was absorbed as a "duplicate, ignored" and never appeared. The dedupe now only folds into an open, untrashed enquiry (excludes
PIPELINE_CLOSED_STATUSESand soft-deleted); a submission matching only a closed/trashed lead creates a fresh lead.cold/on_holdstill dedupe (parked-but-open, still visible).CRITICAL (B7) — quotes would not open after the E collapse. Every quote detail page (and the public client-facing quote page) 404'd because the collapse added a second
quotes<->leadsforeign key (leads.won_quote_id), making theleadsembed ambiguous (PostgREST PGRST201), which nulled the query. Disambiguated all quote->leads embeds (and the workflow-sends cron's nested one) with the explicitquotes_lead_id_fkey. Verified against the live REST API.B8 — a job's invoices could 404 when the enquiry had no client. The
/jobs/[id]/invoices/[id]view derived the expected client from the lead'sclient_idand 404'd on mismatch; a won job whose enquiry has noclient_id(a pre-collapse shape) failed its own invoices. Ownership is already proven by the lead_id match, so the redundant client cross-check is now skipped when the job has no client.
Added
Withdraw a sent quote (T122). A sent quote now has a "Withdraw" action that pulls the offer back: the client can no longer accept it and sees "this quote has been withdrawn, please reach out to the studio" on the quote page. It's a distinct Withdrawn status (not the same as expiring or deleting), kept as history, and reversible with "Reactivate". Complements Archive (which just tidies a quote off your own lists); Withdraw is the client-facing version.
Reverse a written-off invoice (T123). If a bad debt comes good, a written-off invoice now has a "Reverse write-off" action that returns it to part-paid (if anything was paid) or unpaid, resumes reminders, and logs the reversal. Written-off invoices stay un-deletable (kept as a record), Reverse is the way back, so the "reverse the write-off first" prompts on recording a payment now have somewhere to go.
Returning website enquiries re-open the original lead (T128). When someone who went quiet (a Cold or On-hold lead) submits your website form again, Compass now re-opens that same lead to New instead of creating a duplicate, and logs a clear "Re-enquired" note on their timeline (their history stays intact). Closed or trashed leads still start fresh. (The card highlight for re-enquiries follows next.)
Quotes now respect their expiry date (T120). If a quote's "valid until" date has passed, it can no longer be accepted (the client sees "this quote has expired, please ask the studio to renew it"), and a nightly job flips expired sent-quotes to an "Expired" status so the label stays honest everywhere. A quote is still acceptable through its last valid day; the studio can reopen or extend an expired quote.
Job types (T33). Set up the kinds of work you do (Wedding, Portrait, Commercial, whatever fits) in a new Settings → Job types page: add, rename, archive, restore. Tag each enquiry with its job type from the enquiry's edit form, and it shows in the Client Tracker's "Type" column (which used to show the venue). Job types are deliberately separate from your booking event types (the meeting slots clients pick). Each job type also carries a colour you pick from a palette in settings (brights included, yellows and oranges, plus a custom colour by hex), shown as a dot beside it on the Type column. This is the first slice of T33; an icon per job type arrives with the shared icon picker, and per-job-type default workflows and onboarding seeding come later.
Archive a quote. Sent quotes can't be deleted (they're kept as commercial history), but a sent quote that's gone cold used to nag on your dashboard forever with no way out. You can now Archive a quote: it stays as history but drops off the dashboard "Today's actions" and out of the active quote lists, into a new "Archived" tab on the Quotes page. Fully reversible (Unarchive). Available on sent, declined, expired, or superseded quotes; drafts are deleted instead, and an accepted quote stays visible because it backs a won job. An archived quote shows a clear "Archived" badge, and the Archive/Unarchive buttons confirm the moment you click.
T126 (part of) — Client Tracker, List view + bulk bar: a new unified
/client-trackersurface with URL-driven E3 filter tabs (All / Enquiries / Jobs / Lost) and a Board/Calendar/List view switch. The List view ships first (Client, Stage, Event, Value, Next action, Last activity), with names and amounts honouring the global present-safely toggle. Multi-select adds a bulk bar: move stage, export selected to CSV, and trash. The Board view is a full-bleed kanban over the configurable pipeline stages, drag a card to change its stage, with a board-only off-ramp to park (cold / on hold) or mark lost. The Calendar view embeds the full calendar (Year/Month/Week, financial-year toggle, events coloured by stage plus the dashed meeting bookings), now a shared component so the standalone/calendarand the tracker render the same thing. Condensed operational widgets sit above the views: Next job (with countdown), Needs doing (next actions), and Money due (upcoming/overdue invoices). Clicking a record opens a Notion-style peek with four modes (side summary, centred full cockpit, full page, new tab); the chosen side/center default is remembered per user. The side peek shows the record summary, copyable contact details, latest activity, and the Client Lounge link. On mobile there is no peek, tapping a record navigates to the full cockpit, the board stacks into vertical stage sections, the list becomes stacked cards, and the calendar shows an agenda list. Now live in the nav: Enquiries, Jobs, and Calendar collapse into a single "Client tracker", "Clients" becomes "Contacts", and Reports folds under Money as a tab. The old/enquiries,/enquiries/board,/jobs, and/calendarURLs redirect into the tracker; the record detail pages (/enquiries/[id],/jobs/[id]) are unchanged.T70 — Save edited email back to template: edits made in the enquiry email composer can now be saved back to the source template ("Save changes") or kept as a new one ("Save as new"). Tokens are preserved; if an edit drops a merge field entirely, a prompt names it before saving. Built-in templates are save-as-new only.
T53 — Multiple availability schedules: create named schedules (e.g. "Wedding season", "Off season"), set one as the workspace default, and optionally pin individual event types to a specific schedule. Migration backfills a "General" default for all existing workspaces.
- Daily digest email (T91). Opt in via Settings → Notifications and get a 9am email covering: events and shoots happening today, cold leads (no activity in 14+ days), and overdue invoices. Delivered in your workspace timezone. Toggle on or off any time.
- Deliverables checklist on jobs (T65). Each job now has a Deliverables section where you track what needs to be handed to the client: gallery link, album, USB drive, prints, and so on. Add items with an optional due date, toggle each one done (strikethrough with count), or remove them. The header shows "x of y done" at a glance.
- Reporting library at /reports (T47). Four pre-built reports in a new nav section: monthly revenue (paid invoices over the last 12 months), where leads come from (count and win rate by source), conversion funnel (how many leads reach each stage), and busiest months for events. No Excel needed.
- No-show recovery automation (T68). When a scheduled booking passes its end time without being marked completed, Compass automatically marks it no-show and sends the client a warm "we missed you" email with a rebook link. Fires 30 minutes after the booking should have ended, with a 24-hour look-back window to catch gaps.
- Superadmin tier override (T56). Admins listed in
SUPERADMIN_EMAILScan visit/adminto preview the app as any billing tier (Free, Tier 1, Tier 2, Tier 3) without changing the actual workspace plan. Override is stored in a session cookie and cleared on demand.
- Separate before/after booking buffers (T66). Event types now have two buffer settings: "Buffer before" blocks travel time before a booking starts (so the previous slot ends early enough), and "Buffer after" keeps the post-booking gap. Both default to what you had before, no setup needed.
- Review-ask tracking on completed jobs (T62). Each completed job now has a Review card in the sidebar. Record when you sent the review request (timestamps the date), mark it as received, or undo either action. Three clear states: never asked, asked with date shown, review received.
Delivery deadline health badges on the Jobs list (T61). Set a delivery deadline on any job (new date field in the edit form) and the Jobs list shows a colour-coded badge: green for on track, amber for due within 30 days, red for due within 7 days. No badge shown when no deadline is set.
Jobs list now shows upcoming bookings with a time-horizon filter (T50). The Jobs page defaults to "Upcoming" and lets you filter to the next 30, 60, 90 days, or all upcoming. Status filter pills switch between Upcoming, All, In production, Completed, and Cancelled. All filters are URL-driven (shareable, bookmarkable).
- Cold-lead staleness signals on the Enquiries list (T63). Active enquiries with no update in 14 or more days now show a subtle "Cold" badge next to the name. A new "No activity (14d+)" filter in the toolbar surfaces all stale leads at once so you can follow up, mark lost, or close them.
- Lost reasons breakdown on the Enquiries list (T60). When you filter the list to "Lost", a "Why we lose deals" panel now appears above the table showing each recorded reason, how often it appears, and a relative bar. Reasons are sorted most-common first. No new query needed -- the data was already loaded for the list.
- AI connector writes now run the same logic as the app (T115). Every MCP mutation routes through the shared domain core: status changes on jobs call the booking-status handler so the enquiry mirror fires, winning an enquiry via the connector requires the proper booking flow (not a raw status set), and payments go through the payment ledger. An AI-driven change can no longer leave the CRM in an inconsistent state.
- Social link previews now show the right content for every page (T75). Marketing pages (home, feature tree, changelog, pricing, privacy, terms) emit page-specific titles and descriptions when shared on social. Client-facing token pages (quote, portal, contract, questionnaire) show a safe studio-branded card with only the studio name and a generic prompt -- no client name, quote number, or document content is ever exposed.
- A connected AI assistant can now work with the File Library (T83). Five new tools: browse files (
list_files), inspect one with a download link (get_file), upload by URL (upload_file, server-side fetch -- bytes never pass through the model), create a shareable reference link (attach_file), and revoke it (revoke_reference).
- A connected AI assistant can now answer "how is the studio doing?" and look up invoices and jobs directly (T118). Five new read tools:
get_metrics(bookings YTD/MTD, revenue YTD, pipeline value),get_job(one booked job in booking vocabulary),list_invoicesandget_invoice(with all recorded payments and outstanding balance), andsearch(find any client or enquiry by name or email in one call).
- A connected AI assistant can now move enquiries, jobs, and invoices to the Trash and restore them (T117). Four new verbs:
trash_enquiry,trash_job,trash_invoice, andrestore. Each mirrors the app's 30-day Trash exactly, same guards (paid invoices can't be trashed; only booked leads respond totrash_job), and the calendar event is removed/restored alongside a lead.
- A connected AI assistant can now accept a quote on the client's behalf (T116). The third and final MCP verb in the hardening set: "accept this quote" marks it accepted, creates the auto-invoice if configured, moves the enquiry to won, and supersedes any other open quotes, running exactly the same path as doing it in the app. For Pick and Choose quotes, the assistant also passes the chosen package. The full MCP action set (book enquiry, record payment, accept quote) now covers every key job milestone.
Changed
- Privacy mode also masks the signer's name on a signed contract (T89). The "Typed name" in a contract's signature-evidence block now blurs/samples with the names layer, so a signed agreement can be shown on a screen-share without revealing who signed. (The contract body and the IP/device evidence are unchanged, they're the legal record.)
- Privacy mode now masks your costs and margins (T89). With Privacy mode on and the "costs" layer enabled, the gross-profit and margin figures on an enquiry/job (and the package margin) are blurred or swapped for sample numbers, so you can screen-share or present without exposing what a job actually costs you or what you make on it. Previously the costs toggle masked nothing on the enquiry page. Display-only, as always: it never touches stored data and nothing client-facing reads it.
- An enquiry and its job are now one and the same record (E redesign, full collapse). Winning an enquiry no longer creates a separate "booking" record behind the scenes, the enquiry itself becomes the job and carries it through to completion. The Jobs list, the job page, the calendar, client pages, the client portal, contracts, questionnaires, invoices and the AI assistant all read and write that one record, so nothing can ever drift out of sync. Deleting a job now moves the whole thing (and its quotes, contracts, questionnaires and invoices) to the 30-day Trash where you can restore it, keeps the client, and is refused if a card (Stripe) payment was taken (refund it first). A job with only manual/test payments can always be cleared. Your existing data was migrated in place.
- An enquiry and its job are now truly one record (E redesign). A job no longer keeps its own separate copy of the couple's name, event date and venue, those live on the enquiry as the single source of truth, so the two can never fall out of step. Editing them on the job updates the enquiry and vice versa. Everything reads from the enquiry now (the calendar, job pages, client pages, the client portal, contracts, questionnaires, invoices and the AI assistant). Existing records were unaffected. (For the AI assistant: creating a job now links it to an enquiry, matching how the app already works.)
- Fixed: invoices created by the AI assistant showed "NaN" in the Qty, Unit price and Amount columns (B6). When a connected assistant made an invoice from amount-only line items, those columns rendered "NaN" on the invoice (the invoice total was still correct). Assistant-created invoices now use the same line format as the app (quantity 1 at the line price) so they display cleanly, and the line-total calc no longer produces NaN for any older or malformed line.
- Fixed: editing a quote's line items and sending it no longer drops your changes (B5). The quote editor now has a "Save & send" button that saves your current line items first, then emails the client the accept link, so a quote can never go out as the previously-saved (or, on a brand-new draft, an empty) version. The separate "Send quote" button that sent the last saved version is gone, sending a draft always saves it first now. If the client has no email on file, the edits stay saved and you get a clear prompt instead.
- Fixed: a Customisable quote could be accepted without picking a package (E redesign, phase 3). Accepting a "pick one of these" quote with no package selected used to go through and create a $0 booking and invoice. It is now refused (on both the client's accept and the studio's "accept on their behalf") with a prompt to choose a package first.
- AI-assistant (MCP) actions now keep your records in sync, the same as doing them in the app (T115). When a connected AI assistant changes a job's status, the linked enquiry now updates with it (and a job created as completed marks its enquiry completed too), matching what happens when you do it yourself. The assistant also can no longer mark an enquiry "won" out of thin air, that only happens by actually booking it (which creates the job), exactly as in the app. (Part of routing every assistant action through the same rules the app uses, so the two can never drift apart.)
- A job's progress now shows on its enquiry record (E redesign, phase 3). When you move a job to "In production", "Completed" or "Cancelled", the linked enquiry now reflects that same stage, so the one record tells the whole story from first enquiry through to finished job. Existing in-progress and completed jobs were brought into line automatically; your booking counts and revenue totals are unchanged.
- An enquiry and its job now stay in sync (E redesign, phase 3). When a quote is won, the enquiry becomes a job. Until now the job kept its own copy of the name and event date, so renaming the couple or moving the date on one side left the other showing the old details. Now editing either side updates both, so the name and event date always match across your enquiry, your job, and the calendar. (Groundwork toward merging the enquiry and job into one record.)
- Editing an invoice with a payment plan now keeps the schedule in balance, and a settled invoice is locked (E redesign, phase 2). When you add or remove a product on an invoice that already has a payment schedule, the change flows into the unpaid instalments only, so the plan always still adds up to the new total. Payments you have already recorded are never touched. A fully-paid or written-off invoice is read-only: the Edit button is disabled with a note, and the change is refused server-side too. Lowering an invoice below what has already been paid is held back for now (it would leave a credit), with a clear message instead of a broken total.
- You can now reopen a fully-paid invoice to bill more (E redesign, phase 2). A paid invoice shows an "Add a charge" button in place of Edit. You give it a short description, an amount and a due date, and it adds that as a new line, flips the invoice back to part-paid, and shows the new amount as owing. Nothing you have already been paid is touched. If the invoice runs on a payment schedule, the charge is added as a new unpaid instalment so the plan still adds up. A written-off invoice stays locked (no reopen).
- Lowering an invoice below what has already been paid now shows a credit instead of being blocked (E redesign, phase 2). Previously, editing an invoice down past the amount paid was refused. Now it goes through: the invoice reads as fully paid with an "Overpaid by $X" note, and the balance card flips to "Credit (refund due)" so you know to refund the difference (manual refund for now). Money already recorded as paid is never touched, and any unpaid instalments drop to zero since there is nothing left to bill. Clients never see a negative balance, their view simply shows the invoice as paid.
Added
- A connected AI assistant can now book an enquiry and record a payment (T116). Two new assistant actions that do the real thing, exactly as in the app: "book this enquiry" (creates and links the job, moves the enquiry to booked) and "record a payment" on an invoice (updates it toward paid, re-derives its status, and books the enquiry if it is the first payment). Both run the same rules as doing it by hand, so nothing falls out of sync. (Part of the MCP-hardening set; accepting a quote via the assistant follows next.)
- You can now undo a quote acceptance (E redesign, phase 3). If a booking was made by mistake, or the couple changes their mind, open the accepted quote and use "Undo acceptance". It reverses the whole thing: it removes the job and its draft invoice, moves the enquiry back out of Booked (to the exact stage it was at before), reopens the other quote options, and lets the quote be accepted again. It is blocked the moment any payment has been recorded against the booking (refund or write it off first), and once a job is already in production you cancel it from the job instead.
- You can now accept a quote on a client's behalf (E redesign, phase 3). When a couple says yes on a call, open their quote and use "Mark accepted (verbal yes)". It does exactly what happens when the client clicks Accept online: it books the job, creates their invoice, and moves the enquiry to Booked. The acceptance is recorded as made by you on their behalf (rather than from their device), so the trail stays honest.
- Each invoice now keeps an activity trail (E redesign, phase 2). The invoice page has a new "Activity" section showing what has happened to it in time order: created, sent, payments recorded and removed, charges added on reopen, edits, refunds and write-offs, each with a timestamp. It is read-only and gives you (and your accountant) a clear history of every money event on the invoice without digging through the whole client timeline.
- Half-filled forms now show their progress, and finished documents fade into the background (E4). A questionnaire the couple has started part-way through reads "3 of 8 answered" instead of just "Sent", so you can see how far they have got at a glance. And documents that are done with, declined, void, expired, replaced or written off now sit in a quiet grey rather than a loud red. The one thing that still shouts is an overdue invoice, because that is the only one that needs you to act.
- Guided tips now cover the main screens, and stay quiet once you have read them (T80). Inline guided tips reach beyond settings to the dashboard, enquiries, jobs, clients and calendar, each a warm one-liner on what that screen does for you. And "Got it" now sticks: a tip you have dismissed stays gone on that screen instead of returning every reload, while other screens' tips still appear. (Turn tips off entirely any time under Guided mode in your profile menu.)
- Studio-only "back to the record" link in the Client Lounge (T105). When you (the studio) open a client's lounge while signed in, a "Studio view" block at the top of the sidebar (and the mobile rooms sheet) links back to that enquiry or job. Your clients never see it, it shows only to signed-in members of that workspace.
- A contract can now carry a second signature (T105). In the Client Lounge contract room, "+ Add another signature" lets a partner add their typed name alongside the main signer before signing. Both signatures are recorded and shown once the contract is signed. The main signature works exactly as before.
- The contract is now signed right inside the Client Lounge (T105, slice 3). Once a couple accepts their quote, the Contract room opens with a short summary (who it is between and for, the agreed total and deposit), the full agreement in a tall, readable panel, and a sign block underneath. The "I agree" box stays locked until they have scrolled the agreement to the end, then they type their name (which renders as a script signature, showing who they are signing as and the date) and sign. Signing updates the lounge on the spot: the alert clears, the count drops, and a "Signed, sealed, and set" confirmation points to paying the deposit and downloading the signed PDF. The signing itself is the same trusted flow as before.
- The quote now opens right inside the Client Lounge (T105, slice 2). A couple can read their full quote as a warm document (who it is from and for, the line items, the totals with the gold "you're saving" amount, and the payment schedule with a gentle "the moment you accept, your invoice is ready" note) and Accept or Decline without leaving the lounge. Accepting updates the page on the spot: the sidebar count drops, the Quote badge turns to "Accepted", and a panel points to paying the deposit and signing the contract. Tailored quotes show this inline document; pick-and-choose quotes (where you select a package) still open on their own page for now.
- The client portal is becoming the Client Lounge, a calmer roomed space (T105, slice 1). Above a full-width studio cover banner sits a sidebar that holds your rooms (Home, Quote, Contract, Questionnaire, Gallery, Payments, Files), a red "things need your attention" count, and per-room alert dots and action badges so a couple can see at a glance what is waiting on them. The Home room opens with a warm "where things stand" checklist. Rooms appear in the sidebar as they are added to the client (Home always shows), so the nav reflects what actually exists rather than a fixed template; the full journey, including what is still to come, lives in the Home checklist. Rooms also gate by lifecycle (the contract opens once the quote is accepted, the gallery after the day). On mobile the sidebar becomes a sticky "Rooms" button that opens a bottom sheet with the same room list. The accent uses the studio's own colour, never Compass red. The quote opens from its room for now; a later slice brings the full quote and Accept inline. Applies to per-job portals; the combined multi-job view is unchanged for now.
- Contact forms settings now use the shared design language (T79, slice 5). The forms list is a proper SettingsListRow list with a top-right "New form" button, Edit navigating to the builder, and Preview plus Delete (with an in-app confirm) in the overflow menu. The builder tabs (Build, Style, Configure, Embed) now use the shared Button primitive for all actions, the ds-label type scale for section headings, and design tokens throughout. The font picker (slice 3) and auto-reply Switch with template picker (slice 8) are preserved unchanged.
- New enquiry and workflow reminder emails now respect your Notification preferences (T54, slice 2). Incoming lead emails (both the hosted contact form and the webhook) and the daily workflow digest are now sent through the emit() engine, so they honour the per-user in-app and email channel settings you configure in Settings, Notifications. The two legacy "Notify me on new lead" and "Daily workflow digest" toggles in Settings, Email have been removed and replaced by a short link pointing to Settings, Notifications, where per-event control lives. A deploy migration backfills any existing opt-outs so no one starts receiving emails they had previously turned off.
Added
- Privacy mode, present and record safely (T89). A new control in your profile menu lets you hide sensitive things on screen so you can share your screen with a client, or screen-record for social content, without exposing anyone's private data. You pick which layers to hide, other clients' names & contacts, money amounts, your costs & margins, and one of two styles: Blur (frosts the values, for presenting live) or Sample data (swaps in realistic fake names and numbers, so a screenshot looks real but is no one's actual data). A chip in the top bar shows when it's on. It's view-only and per-device, your real data is never touched and clients never see any of this. Coverage is now complete across the app: every list, board and calendar, plus all the detail pages (jobs, enquiries, quotes, contracts, clients) and invoices, masks names, money amounts and costs/margins so a real screen-share or recording is safe end to end.
- Travel timezone switch (T81). When you are away from home, you can view all your times in your current location's timezone without changing a thing for your clients. A "Viewing times in" control in your profile menu lets you switch between your home timezone and where you are now (and back), and when a different timezone is detected a one-time strip offers to switch for you. While a switch is active, a small chip in the top bar shows which zone you are viewing in. Crucially, this only changes how times look to you, across every screen (dashboard, calendar, jobs, enquiries, quotes, contracts, clients and settings). Every email, invoice, booking page and reminder your clients receive always stays in your home timezone.
- A little sparkle on the compass menu (T88). Every so often the compass/star menu up top does a gentle one-second amber twinkle, a quiet "come explore" wink, never a nagging badge. It only happens while you're actually looking at the tab, never in the background, and you can turn it off any time with the new "Sparkle" toggle at the bottom of that menu. It respects your reduced-motion setting and stays off if you've asked your system to minimise animation.
- Pause payment reminders for a whole client (T85). A "Payment reminders" card on the client page, and on each of that client's invoices, lets you pause all of a client's automated payment chasing at once, either until you turn it back on or snoozed until a date (then it resumes on its own). A paused client overrides each invoice's own setting, so nothing gets chased while it's on, and reminder-eligible invoices show a "Reminders paused" badge so it is never a mystery why an invoice has gone quiet. Useful for an agreed payment arrangement or a sensitive situation. Scope is payment reminders only.
- Desktop (web push) notifications (T54, slice 5). An opt-in card at the top of Settings, Notifications lets you enable OS-level banner notifications on your current browser. Enabling the browser switch does not auto-enable any banners; you then choose which events show a banner using the new Desktop column in the notification grid. Push is strictly per-browser and per-event. The push delivery engine fans out to all registered browsers via VAPID web push, prunes gone subscriptions automatically (HTTP 404/410), and degrades gracefully when VAPID keys are not yet configured. A one-time email retry on transient network failures was also added.
- In-app notification bell (T54, slice 4). A bell icon in the nav shows your unread notification count and opens a panel with your recent notifications. Click any notification to mark it read and go straight to the relevant page. Mark all read in one tap. The badge goes quiet when you are all caught up.
- Notification preferences (T54, slice 3). You can now choose exactly what Compass tells you about and how, in Settings, Notifications. Toggle in-app and email per event (new enquiries, bookings, payments, invoices, contracts, workflow tasks, and delivery due dates), grouped by area. Changes save automatically with no button to press, a SaveIndicator shows saving, saved, or error inline. SMS and phone push are shown (coming soon) as a visual teaser. Resets to sensible defaults in one click.
- Notifications engine groundwork (T54, slice 1). The foundation for in-app and email notifications: per-user channel preferences, the notifications feed table, a single server-side emit() engine, and the canonical event catalog (new enquiry, booking, payment, invoice, contract, workflow and delivery events). No settings screen or in-app bell yet, those land in later slices.
- Set a first password inline in Settings (no email round-trip). If you sign in with a magic link and have never set a password, the password card in Sign-in & security now shows an inline "Set a password" form (new and confirm only). It uses your active session, so you set a password and stay signed in, no email needed. Accounts that already have a password keep the current "Change password" flow unchanged, and the inline set is refused server-side for them so it can never overwrite an existing password.
- Set or reset your password from Settings, no current password needed. The Change password card now has an "Email me a link to set a new password" action for when you have forgotten your current password or never set one (magic-link or one-time-code sign-ups). It emails a secure reset link to your own account email and you set the new password without signing out. The current-password change flow is unchanged for everyone who knows their password.
- "Walk me through it" guided walkthrough on Getting started. With guided mode on, a "Walk me through it" button opens a step-by-step overlay that walks you through the whole setup one screen at a time, leading with the payoff of each step (the why, not just the task) and a "Take me there" link to that screen. It is always available while guided mode is on, even if you have turned the checklist or inline tips off.
- The invoice PDF now matches the polished web invoice. Saving an invoice to PDF produces the same clean layout clients see online, your logo, a "Tax Invoice" heading, the event details, the three summary cards (Total / Paid so far / Balance due), and the same payment schedule, rendered on one page. The web invoice gained your logo and the "Tax Invoice" heading to match. Both views now show the event block (the couple, date, and venue) pulled from the linked job or enquiry.
- The card fee now shows on the client's invoice. When a client pays by card and you pass on the surcharge, the invoice now itemises a dated "Card processing fee" line, the total ticks up by the fee, and "paid so far" reflects what their card was actually charged, so the invoice reconciles with their card statement instead of hiding the fee. The fee's GST folds into the GST line. Your balance is unchanged (the fee only ever covers the card processor, never reduces what you're owed). Bank transfer and PayID payers see no fee. (Client invoice first; the studio view and PDF match next.)
- Your own invoice view now matches it. The studio invoice screen (and its payment panel) shows the same dated "Card processing fee" line and surcharge-inclusive Total / Paid, so your records reconcile with what the client was charged.
- The PDF matches too. Saving the invoice to PDF now itemises the same dated "Card processing fee" line with the reconciled totals, covering both the client "Save as PDF" link and the studio internal download.
- Unsaved-changes guard. Compass now warns before you lose edits. If you've changed something and try to leave the page, refresh, or close the tab, you get a heads-up first, plus a small "You have unsaved changes" reminder while a change is pending. Wired into the editors where losing work hurts most: the workflow builder, questionnaire builder, contact-form builder (fields, style, and settings tabs), quote/invoice template editor, email signature, dashboard layout, and pipeline-stage rename.
- Card surcharge & Buy Now Pay Later settings. The Card payments panel now lets you pass the card fee on to clients (separate rates for domestic and international cards, defaulting to Stripe's pricing) so you receive the full invoice amount, with a live example showing exactly what the client pays. You can also choose how GST applies to the surcharge, and switch on Afterpay/Zip so clients can pay over time while you are paid upfront. Settings only; the client-facing card screen that uses them is coming next, and a compliance note reminds you to check local surcharge rules.
- Disconnect Stripe. The Card payments panel now has a Disconnect button (Settings, Invoicing, How clients pay you), so you can unlink Stripe from Compass in one click. It asks you to confirm first, your Stripe account itself is untouched and past payments stay on your records, and the Pay buttons simply stop showing until you reconnect.
- Clients can pay invoices by card. Online card payments are live. Turn card on for an invoice (or flip the "Accept card by default" switch under Settings, Invoicing, How clients pay you) and your client gets a Pay button on their invoice link, "Pay this month" when there's a plan, or "Pay the full balance". They pay on Stripe's secure checkout (Apple Pay and Google Pay included), the money goes straight into your own Stripe account, and the invoice marks itself part paid or paid the moment it clears, with a receipt on the ledger. Refunds you issue in Stripe flow back automatically too. Card is off by default on every invoice, since the studio covers the card fee, so nothing changes until you switch it on.
- Payments on invoices. Every invoice now has a Payments panel with three clear figures, Total, Paid, and Balance due, a status that keeps itself honest (Part paid, Paid), and a "Record a payment" button for money that comes in by bank transfer, PayID, cash, or anything else. Partial payments are handled properly: pay $500 toward a $1,160 month and the plan shows exactly that. Your clients see the same clear summary on their invoice link, with each scheduled payment marked paid, part paid, or upcoming. The Money page's unpaid total now reflects what is genuinely still owed, not the full amount, on part-paid invoices.
- Payment methods library ("How clients pay you"). Settings → Invoicing now has a place to save the ways clients can pay you, a bank transfer, PayID, Wise, anything, each with free-form instructions shown to the client exactly as you type them. Tag a method with a currency so the right account shows on the right invoice (or leave it on "All currencies"), and mark one as the default per currency. Your existing bank transfer and PayID were brought across automatically, so nothing changes on your invoices. (The drill-in row that opens this lives on the redesigned Invoices tab.)
Changed
- Settings, board stages refreshed onto the Compass look. Your pipeline stages now sit in the same tidy list as the rest of settings. Drag the handle to reorder them (no more up and down arrows), and Rename sits right where you expect it. The status mapping each stage powers stays visible underneath, so you always know what feeds your dashboard analytics, and reordering never changes it.
- Settings, Integrations refreshed onto the Compass look. Your connection keys now sit in the same tidy list as the rest of settings, each showing the key name, a masked preview, when it was created, and when it was last used, with an Active status. Revoke moved into the row's "more" menu and still asks you to confirm first. "Create key" is now a button at the top of the list that opens a small dialog, your new key is shown once to copy, exactly as before. How keys are made, stored, and revoked is unchanged.
- Settings, Business profile and Client portal refreshed onto the Compass look. The at-a-glance cards on these pages dropped their decorative corner glyphs for the same clean, calm look as the rest of settings. Every card keeps its title, summary, and the form inside it, nothing about what you can set or how it saves has changed. Invoicing was already on the new look, so it stayed as it was.
- Clearer invoice payment screen. The "Pay this month" button is now "Pay next payment" (it pays your next scheduled instalment, which is clearer when more than one falls in the same month), and the "payment received" confirmation after paying is now much more prominent so it's not missed.
- Unsaved-changes guard now covers the tax-rate, region, and product/service editors. If you start editing one of these and try to refresh or close the tab before saving, Compass warns you first, the same protection the bigger editors already have.
- Captain plan price. Captain is now $25/mo (was $19). Fleet stays $49 and Pilot stays free, and founding members still get 50% off their first year. Nothing charges yet (billing isn't live), this just keeps the pricing page and in-app plan copy correct ahead of launch.
- Tax is now driven by your Regions and Tax rates. Invoices and quotes work out tax from the region you tag (its rate, or zero when the region is tax free), then your default tax rate, instead of a single "GST registered" switch. This fixes a real bug where a "No tax" region still added 10% GST. Nothing changes for existing workspaces (your GST setup carries over as a default 10% rate), so totals stay identical except that overcharge is gone. Set a default under Settings, Invoicing, Tax rates, and tag clients with a region under Regions.
- Invoicing settings redesign. The Invoices tab now reads as clean grouped rows that match the rest of settings: Business details, How clients pay you, Numbering & logo, Payment reminders, and For your accountant, each opening a focused editor. "How clients pay you" drills into your payment methods, and the reminder editor now shows a plain-English example of exactly when reminders would go out ("for an invoice due 14 Mar: nudge 11 Mar, due 14 Mar, then 17 Mar, 24 Mar, weekly until paid"). The business-number label follows your region (ABN, VAT, or EIN). Tax is no longer set here, it lives in the Tax rates and Regions tabs. The old "Create an invoice" card is gone.
- Invoices now show your payment methods. The "How to pay" block on the invoice detail, the client's online invoice, the PDF, and the invoice email now lists the payment methods that match the invoice's currency (default first), so a client billed in pounds sees your GBP account and a client billed in dollars sees your AUD one. Workspaces that haven't set up methods yet keep showing their existing bank/PayID details, so nothing changes until you start using the library.
- Payment schedules on the unified settings design. The Payment schedules list now uses the shared settings row (T79 design language): each schedule shows its name, a Default badge, and where it's used, with a plain-English summary of its payments underneath. Edit opens the payment editor in a dialog, and Archive moved into the row's "more" menu (with its confirm). Archived schedules are dimmed with a one-tap Restore. Same schedules, same behaviour, just tidier and consistent with the rest of settings.
- Tax rates on the unified settings design. The Tax rates list now uses the shared settings row (T79 design language): the percentage sits on the right, Edit opens the rate, and Archive moved into the row's "more" menu (with its confirm). Archived rates are dimmed with a one-tap Restore. Your rates and how they behave are unchanged, this is the look and interaction only.
- Regions on the unified settings design. The Regions (billing profiles) list now uses the same shared settings row (T79): the currency and tax label read underneath the name, Edit opens the region, and Archive moved into the row's "more" menu. Archived regions are dimmed with a one-tap Restore. Logic and data unchanged.
- Quote & invoice templates on the unified settings design. Each template row now uses the shared settings row (T79): a content summary ("3 lines", "2 packages, 4 add-ons") sits on the right, Edit opens the editor, and Duplicate, Archive/Restore and Delete are tidied into the row's "more" menu instead of a spread-out row of buttons. No change to the templates themselves.
- Contract templates on the unified settings design. Contract template rows now use the shared settings row (T79): Edit opens the same rich-text editor, and Duplicate, Archive/Restore and Delete move into the row's "more" menu. The editor and your templates are unchanged.
- Email templates on the unified settings design. Email template rows now use the shared settings row (T79), grouped under their type in one tidy list. Edit opens the same editor; Set as default, Duplicate and Delete move into the row's "more" menu. Templates and sending behaviour are unchanged.
Fixed
- The email composer's alignment buttons now light up for images too. They already showed the active alignment for text, but an aligned image left all four inactive (they were reading the paragraph's alignment, not the image's own). Now align an image and the matching button highlights, like Bold and Italic.
- New-enquiry notification and email links now open the enquiry instead of a 404.
Every push notification and owner email built the link as
/leads/<id>, but the detail page lives at/enquiries/<id>(there is no/leadspage), so clicking a "New enquiry from..." notification landed on a 404. Fixed at every source (the website-lead webhook, the lead-notification email, the contact-form and booking actions, and the workflow-reminders cron), plus a/leads/<id>to/enquiries/<id>redirect so links in already-sent emails still work. The push notification also now shows the crisp Compass icon instead of a generic one.
- Desktop push notifications now actually send (T54). The server read the public
VAPID key from the wrong env var name (
VAPID_PUBLIC_KEY) while the key is set underNEXT_PUBLIC_VAPID_PUBLIC_KEY, so emit() saw the keys as unconfigured and silently skipped every push. It now reads the key the browser and setup actually use, so banners deliver.
- Notification settings are reachable, and the desktop-notifications card now shows (T54). Two fixes: Settings now has a Notifications entry in the hub (it was built but never linked, so there was no way in), and the "Enable desktop notifications" card now renders for first-time users. The card had been stuck invisible because its status check waited on a service worker that does not exist until you first subscribe; it now resolves immediately so the Enable button appears.
- The browser-tab icon now renders reliably (no muddy or generic fallback). The SVG
app icon used heavy SVG filters (paper-grain and ink-edge) that render unpredictably
at favicon size and that Safari can drop entirely, leaving a blank or generic-looking
icon. The icon is now a flat, filter-free version of the same compass mark, so it
shows crisply across browsers, with the
.icoas the universal raster fallback.
- Signing in now lands you on your dashboard, not the enquiries board. After
signing in, confirming an email link, finishing onboarding, completing two-factor,
or resetting your password, Compass drops you on
/dashboardby default instead of the enquiries board. Deep links (an explicit destination you were headed to) are still honored; only the default landing changed. The default now lives in one place (src/lib/routes.ts), so a future route rename is a single edit.
- Card payments can't get stuck "unpaid" anymore. When a client returns to their invoice after paying by card, Compass now double-checks the payment directly with Stripe and records it if it hasn't already, so a delayed or misconfigured webhook can no longer leave an invoice showing as unpaid. It's a safety net (the webhook is still the main path) and never double-counts.
- Workflow editor no longer loses your edits. Clicking outside the workflow editor or pressing Escape while you have unsaved changes no longer silently closes it and throws the edits away, you stay in the editor (with the "unsaved changes" reminder), and Cancel now truly discards so reopening shows the saved version, not your abandoned edits. A name-only change counts as unsaved too.
- Edit dialogs close themselves after saving. Editing a client or a job and clicking Save now closes the dialog once it saves (it used to show "Saved." but stay open until you clicked Cancel). A second consecutive save closes too, and a validation error (like clearing a required name) still keeps the dialog open.
- Attaching a workflow gives instant feedback. On an enquiry, picking a
workflow to attach now shows "Attaching
…" on that row right away (and Detach shows "Removing…"), instead of a few seconds where nothing looked like it happened while the page refreshed.
[0.4.0] — 2026-06-16
Everything shipped since 0.3.0, cut as a release: payment schedules with a running balance due, multi-currency / regions, invoice write-off, native SMS (connect Twilio, manual send, templates, opt-outs), plus the docs/governance cleanup and a batch of fixes.
Added
- Manage who can be texted (opt-outs). Settings, Text messages now lists opted-out numbers and lets you add one by hand, for when a client asks to stop receiving texts (a dedicated number records STOP replies automatically; a Sender ID can't, so you add those here). Any number on the list is never texted, and you can re-allow a number with one click.
- Saved text-message templates. Settings, Text messages now has a Message templates area where you can save your common texts (a session reminder, a thank-you). When you text a client from their enquiry, a "Use a template" dropdown drops the saved message straight into the box, ready to tweak and send.
- Text a client from their enquiry. Once text messaging is switched on, a Text button appears on the enquiry page next to Email. Write a short message, send, and it goes out through your Twilio account. Opted-out numbers are skipped automatically, the opt-out line is added for you when you use a Sender ID, and every text is recorded. (Automatic texts from reminders and workflows come next.)
- Text messages: connect your own Twilio account (setup screen). A new Settings, Connections, Text messages page lets a studio connect its own Twilio account to text clients (your Account SID + Auth Token, the token stored encrypted and checked with Twilio when you save). Choose how texts are sent from, a Sender ID (your studio name, with a required opt-out line) or a dedicated number (two-way, with automatic STOP). Off by default: nothing sends until you connect a validated account and flip the master switch. Comes with a detailed step-by-step help article (including the exact answers to pick during Twilio signup). Sending wires up in the next step.
Changed
- Calendar settings now show your live Compass Leads and Compass Jobs calendars by name. When lead and job push is on, the Lead and job calendar card names the two calendars Compass created, says which Google account they live in, and explains they show up under "Other calendars" in Google's sidebar (where you may need to tick them on). This clears up the "I can see the lead in my calendar but not in Google" confusion.
Added
- Write off an unpaid invoice as bad debt. When you've given up collecting on an invoice, you can now write off the remaining balance from the invoice page (with an optional reason). It stops the automated reminders and stays on record as bad debt (it is never shown as paid), so your books and your accountant see the truth. Written-off invoices get their own filter on the Money page, a "Bad debt" flag in the accountant CSV export, and they drop out of the client portal. Cancelling a whole invoice is still coming separately.
Changed
- Payment reminders now chase the next scheduled payment, not just the whole invoice. When an invoice is on a payment schedule, automated reminders are timed to the next unpaid installment's due date and show that payment's amount plus the balance still remaining, instead of the whole invoice total on a single due date. Part-paid invoices keep getting reminders (they used to be skipped once a payment was recorded), and each reminder amount now shows in the invoice's own currency. Recording a payment, or changing the schedule, re-points the reminders to the next payment automatically.
Added
- Put a payment plan on a quote, and it carries through to the invoice. The quote editor now has a Payment schedule picker: choose one of your saved schedules (or "Pay in full") and you see a live preview of how the total splits. The client sees that dated plan on the quote before they accept, and when they accept, the invoice that's created is automatically split into those dated installments (ready to mark paid as money comes in). Quotes started from a template inherit the template's schedule. You can still add or adjust unpaid payment dates on the invoice afterwards.
- Clients now see the payment schedule on their invoice. When an invoice is split into dated installments, the client-facing invoice page, the client portal, the invoice PDF, and the PDF attached to invoice emails all show the plan (each payment, its due date, amount, and whether it's paid) with a running balance due. A part-paid invoice now also shows correctly in the portal's open invoices (it used to disappear) and reads "Part paid" everywhere its status shows. All amounts render in the invoice's own currency.
- Client, job and portal money totals separate by currency (multi-currency, step 6). A client's and a job's "X paid of Y invoiced" summary, and the client portal's running total, now show one line per currency instead of adding different currencies together, and every invoice amount in those lists shows its own currency. A single-currency client or job reads exactly as before (one line).
- Change a quote's currency from the editor (multi-currency, step 5). When you have regions set up, the quote editor now shows a Region picker just like invoices: pick one and the quote's currency and tax switch together, with the live totals updating as you choose. Quotes still inherit the client's region by default, this just lets you override it on a single quote. Studios with no regions keep the plain tax-rate picker, unchanged.
- Quotes follow the client's region, and Money totals separate by currency (multi-currency, step 4). A quote raised for a client tagged with a region now starts in that region's currency and tax automatically (e.g. a UK client's quotes come out in GBP with VAT). On the Money screen, the Unpaid and Paid totals are shown per currency instead of being added together at a made-up exchange rate, and each invoice in the list shows its own currency. Single-currency studios see no difference.
- Pick a region on an invoice, and set a client's default (multi-currency, step 3). When you have regions set up, the invoice editor shows a "Region" picker: choose one and the invoice's currency and tax follow it (e.g. a UK client billed in GBP with VAT). You can also tag a client with their region on the client's Edit screen, and new invoices for that client start in the right currency automatically. An invoice created from a booking keeps that booking's currency. Studios with no regions see the usual tax-rate picker, unchanged.
- Regions: bill clients in more than one currency (multi-currency, step 2). A new Settings → Regions screen lets a studio set up regions that each pair a currency with the tax that applies there (e.g. "United Kingdom = GBP + VAT" alongside "Australia = AUD + GST"), reusing the tax rates you already defined. Mark one as the default new clients and documents assume. This is the model and management screen; tagging a client with a region and the document currency picker that uses it land next.
- Invoices now carry their own currency (multi-currency, step 1). An invoice created from a quote or job inherits that document's currency, so an accepted GBP quote produces a GBP invoice, and every place the invoice is shown (the invoice page, its payment schedule and balance due, the PDF, the emailed copy, and the client's private link and portal) renders the amounts in that currency instead of always assuming Australian dollars. The tax line is labelled to match the currency too (VAT for GBP/EUR, GST for AUD/NZD, Sales tax for USD/CAD), so a UK invoice no longer reads "GST". Existing invoices keep exactly what they showed before. This is the foundation for full region/currency profiles, per-client defaults, and per-currency reporting landing next.
- Merge a duplicate client from its page ("Duplicate of…"). When a client is a duplicate (e.g. someone who enquired twice under two emails), open it and use "Duplicate of…" to pick the client you want to keep. Any detail the keeper is missing (phone, address, a partner name) is copied across; a different email or phone is saved as a note on the keeper so nothing is lost; then the duplicate moves to the 30-day Trash. If the duplicate still has a live enquiry, booking, or invoice, it can't be removed this way (you'll be told to move or close those first), so real work is never thrown away. Pairs with the new "No enquiry" filter for finding duplicates and the fold_client_into connector tool.
- fold_client_into tool on the Compass connector (de-duplicate clients). An AI assistant can now collapse a duplicate client into the one you keep: it copies any contact detail the keeper is missing (phone, address, a partner name) from the duplicate, preserves a conflicting email or phone as a note on the keeper (never overwrites or discards it), then moves the duplicate to the 30-day Trash. It refuses when the duplicate still has a booking, quote, invoice, contract, questionnaire, or an open enquiry, so real work is never thrown away, and never folds a client into itself. (The in-app version comes next.)
- delete_client tool on the Compass connector. An AI assistant can now move a client to the 30-day Trash through the connector, with the same safety guard as the in-app delete: it refuses when the client still has a booking, quote, invoice, contract, questionnaire, or an open enquiry (it tells you exactly what is blocking), so real work and money are never silently removed. Lost/won or already-trashed enquiries are trashed alongside the client and restored together. The in-app delete and the connector now share one implementation, so they can never drift.
- Find clients with no enquiry ("No enquiry" filter). The Clients list has a new "No enquiry" chip that surfaces clients with no live enquiry and no booking, the ghost rows the everyday views hide (usually a double form submission or a leftover after an enquiry was trashed). The default views stay clean; this is a deliberate filter for tidying up. Open one to merge it into the right client or remove it.
- Payment schedules on invoices. An invoice can now be split into dated installments: pick a saved schedule (or build a custom one), see each payment's due date and amount, mark each one paid as it lands, and watch the balance due update. Only paid installments lock, you can edit, add, or remove the unpaid ones any time, and the plan always adds up to the invoice total. The invoice status moves Unpaid to Part paid to Paid on its own. (Showing the plan on the client portal and PDF, and on quotes, comes next.)
- Invoice emails now carry the invoice PDF. When you email an invoice,
and on the automated due / overdue / paid reminders, the branded invoice
PDF (the same document as "Save as PDF") rides along as an attachment named
Invoice-
.pdf. If the PDF can't be generated, the email still sends without it, so a missing document never holds up a client email.
- Guided mode helps new studios find their way around (T80). New accounts start in guided mode: a slim banner shows it is on with a one-tap switch to turn it off, the getting-started checklist calls out the next step and explains the payoff of each one ("set your prices once and never retype them"), and a short tip appears on each setup screen telling you what to do there and why it helps. You can keep just the parts you want, the checklist, the tips, or both, from a control in the profile menu, which is also the permanent place to switch guided mode back on later. Turn it off and the whole layer vanishes for a plain interface. Same look in light and dark.
Changed
The "+ New" button now sits on the list itself, top-right, on every settings collection. Packages, payment schedules, tax rates, expenses, lead sources, templates (contract, questionnaire, quote and invoice), workflows and contact forms all share one consistent add button instead of it floating in the page header or hiding at the bottom. On a phone it shows a compact "+ New"; on a wider screen the full label ("+ Add package", "+ New workflow"). Same look in light and dark.
On/off settings now use the same toggle switch everywhere. The "I'm GST-registered" setting and the "create the invoice automatically on acceptance" option on a quote template are now the same iOS-style switch used across the rest of Compass, instead of a bare checkbox. Same look in light and dark.
Merge fields now show as chips in every email and template editor. A field like {{name}}, {{studio_name}} or {{event_date}} reads as one tidy chip while you write instead of raw braces, so it's obvious at a glance what gets filled in. The actual field is unchanged, it still fills in with your client's details when the message sends.
Fixed
- The new/edit invoice editor's live total now uses the region's tax rate. When you pick a region (or a tax rate) on an invoice, the running totals box now recalculates tax at that rate as you type, instead of always using the workspace's default rate. Previously the preview could show, say, 10% while the saved invoice correctly used 20%, now they match. The saved invoice was always correct; this fixes only the live preview.
- Compass's own Leads/Jobs calendars can never be mistaken for a bookable calendar. The conflict-check setup now explicitly skips the two calendars Compass creates for the lead/job mirror, so they can't be offered as a "blocks my availability" calendar or auto-picked as a booking destination (previously they were left out only by luck of how Google lists calendars).
- Multi-currency polish on the client page, quotes, and the invoice form. A foreign-currency invoice now shows its own currency symbol in the client page's invoice list (it was showing the workspace symbol), a quote raised for a client tagged with a region now remembers that region in the editor (the picker no longer reads "No region"), and the invoice Tip helper text no longer hardcodes "GST".
The Money "Unpaid total" now counts what's actually still owing on a part-paid invoice. When an invoice is on a payment schedule and some (but not all) installments are paid, the Unpaid total used to ignore it entirely and the Unpaid filter hid it. It now appears under Unpaid, the Unpaid total counts only the balance still due (total minus what's been paid), and the amount already collected counts toward the Paid total, so the two cards add up. Each part-paid row also shows the balance due beneath its total, and its status reads "Part paid". Single-currency studios are unaffected; the per currency split is unchanged.
The invoice PDF attachment is now named to match the invoice. The branded PDF that rides along on invoice emails is named after the invoice's displayed number (Invoice-0002.pdf for sequential numbering, Invoice-20260615.pdf for date-based), instead of the raw internal number (Invoice-2.pdf).
Editor menus no longer get cut off by the body box. The text-colour picker and the Insert from Files panel open downward from the toolbar, and the lower part used to be clipped by the email body. They now show in full.
The Files library help article now describes sharing accurately. It said client sharing was "coming next", but sharing files (links and attachments in emails, and the portal Files tab) has shipped. The article now explains how controlled, revocable sharing works.
Added
Save an invoice as a clean PDF. A "Save as PDF" button on your invoice view and on the client's portal invoice generates an elegant A4 document in your brand type (the Compass serif + sans), with a tinted header band, your logo, line items, totals, and payment details, not a screenshot of the screen. It's titled "Tax Invoice" automatically when you're GST-registered (plain "Invoice" if not), the grand total sits in a matching accent panel, and multi-line item descriptions (a package title with its inclusions beneath) keep their line breaks instead of running together. First of the document PDFs; quotes, contracts, and questionnaires follow.
Choose your document colour, in Settings, Business profile. One accent colour tints every document the same way (the header band, the line beneath it, the total panel, and the brand dot), across quotes, invoices, contracts and questionnaires. Pick from a small palette or any custom colour, with a live preview; the default is the Compass red.
Invoices now show the event, a PayID, and per-line tax. When an invoice is tied to a booking, the PDF shows the event (couple, shoot, date, venue) above the line items. You can add a PayID (mobile, email or ABN) in Settings, Invoicing, Getting paid, and it appears alongside your bank transfer details. Each line also shows its tax treatment (e.g. "10% incl.").
Save a quote as a PDF. A "Save as PDF" button on your quote view and on the client's quote page generates the same elegant branded A4 document as invoices (your colour, the event details, line items, GST, validity date, terms, and an accepted stamp once it's signed). Second of the document PDFs.
Save a contract as a PDF. A "Save as PDF" button on your contract view and on the client's signing page generates a branded A4 copy of the agreement (your colour and logo, the full contract text, and the signature stamp once it's signed). Third of the document PDFs.
Save a completed questionnaire as a PDF. A "Save as PDF" button on the questionnaire view and the client's questionnaire page generates a branded A4 copy with every question and its answer (unanswered questions read "No answer"). Fourth of the document PDFs, the set is now complete: invoices, quotes, contracts, and questionnaires all share one branded look.
Upload your logo once, in Settings, Business profile. It becomes the single source of truth for your mark and shows top-left on your documents, emails, and the client portal. A live preview shows exactly how it appears (or your studio name, if you have no logo yet). Replaces the old buried "paste an image URL" field; your existing logo is carried over.
Attach a file from your library to automated, template, and booking emails. "Insert from Files" now works in workflow email steps, your saved email templates, the manual "send this step" dialog, your email signature, and your booking emails (confirmation, reminder, cancellation, reschedule), not just the one-off composer. Because one template can go to many people, each send mints its own fresh, version-pinned link, so a file in a template is shared safely and stays individually revocable from Settings, Files.
Start a contact form's auto-reply from one of your email templates. On a contact form's Configure tab, the auto-reply now has a "Start from a template" picker that fills the subject and body for you (your enquiry auto-reply templates are listed first). Edit it afterwards as you like. The on/off control is now the standard toggle switch, and "Enquiry auto-reply" is now a category you can give a template so it shows up here.
All four booking emails are now customisable per event type. The set is complete: Confirmation, Reminder, Cancellation and now Reschedule each have their own editor in Settings, Booking pages. Write the message in your words; the time, location, join link and reschedule details are still added automatically where they apply. Leave any untouched and it reads as before.
Booking cancellation emails are now customisable too. Each event type now has a "Cancellation" email editor alongside Confirmation and Reminder, so the note your client gets when a booking is cancelled can be in your own words. Leave it untouched and it reads as before.
You can now customise the reminder email your clients get before a booking, not just the confirmation. Each event type now has separate "Confirmation" and "Reminder" email editors in Settings, Booking pages. Write the message in your own words with merge fields like
{{invitee_first_name}}and{{event_datetime}}; the time, location, join link and reschedule button are still added automatically, so a custom reminder can never accidentally drop the join link. Leave it untouched and reminders read exactly as before.The public roadmap (/tree) now shows Starter content and the Product catalogue. Two capabilities that shipped recently are now reflected on the roadmap: example content for new accounts, and organising packages into categories with archive and duplicate.
Editing settings lists feels more responsive: inline actions, a save highlight, and centered popups. On Packages & products, Edit, Duplicate and Archive now sit right in each row (no need to open the editor first), and every button shows what it's doing the moment you click it (Duplicating, Archiving, Restoring, Saving). When you save an item, its row briefly highlights so with similar names you can see exactly which one changed. The editor popups now open centered on screen instead of tucked in the top-left corner. The same save-highlight and centering apply to tax rates and payment schedules too.
That same button feedback now runs across every Settings list. Contract templates, questionnaire templates, quote and invoice templates, workflows, event types, pipeline stages and lead sources all got the treatment: every archive, restore, duplicate, delete, pause and save button shows what it's doing the moment you click it, and saving an item briefly highlights its row so you can see which one changed. No more wondering whether a click landed.
New accounts start with example content instead of blank screens. When you finish setup, Compass fills your Packages, email templates, contract and questionnaire templates, payment schedule and a follow-up workflow with ready-made examples tailored to your industry, each clearly marked
[Sample]so you know they're starting points. Edit them to make them yours, or delete the ones you don't need. Your tax rate is set to the right default for your currency too. Nothing is locked in, it's just a running start so the app feels alive from day one.Organise packages & products into categories, archive ones you don't sell right now, and duplicate to make variations fast. Give each package or add-on a category (Weddings, Headshots, whatever you like) and the list groups by it, so a long catalogue is easy to scan. Archive an item to hide it from new quotes without losing it (it moves to a new Archived section you can restore from any time), and Duplicate clones an item so you can tweak a copy instead of starting over. The same categories also group the catalogue dropdown when you build quotes and invoices.
A gentle nudge when a new version is ready. If you have Compass open in a tab while we ship an update, you will now see a quiet, dismissible message: "A new version of Compass is ready. Refresh to update," with a Refresh button. Nothing reloads on its own, and it never interrupts what you are doing. This prevents the occasional "something failed to load" hiccup from an out-of-date tab.
Discarding a draft panel now uses an in-app prompt, not a browser pop-up. The floating draft panels ask "Discard this draft?" with Compass's own dialog, the last spot that still used a native browser confirm. Every prompt in the app is now consistent, and a build check keeps it that way.
Logging an expense is a quick dialog now, and that was the last settings page still showing a permanent open form. Across settings, every section now shows a tidy list with an Add button that opens a dialog, instead of a form sitting open on the page. Consistent end to end.
Adding a lead source is a quick dialog now. The "Add a source" box no longer sits open on the page; it is a button in the Your sources header that opens a small dialog. Your existing sources stay exactly as they were (drag to reorder, click a name to rename).
Tax rates and payment schedules are tidy lists now too. Like Packages, each tax rate and payment schedule is a compact row instead of an always-open form. Tax rates edit in a small dialog; payment schedules expand inline to the full payment editor when you want them. "Add tax rate" and "New schedule" are buttons that open a form, not a permanent block on the page.
Packages & products is far easier to scan and edit. Each package and add-on is now a compact row (image, name, price, profit margin) instead of a tall always-open form, so a long list reads at a glance. Editing opens a tidy dialog, and "Add package" / "Add add-on" buttons sit in each section header. You can now upload a package image (drag a real file in) instead of pasting a URL, and prices show a proper currency symbol.
Pull down to refresh on your phone, and pull a little further for a surprise. On a touch device, pulling down from the top of any signed-in page now refreshes it, with a small compass dial that follows your finger. Pull past the refresh point and three faint dots appear: the only hint that you can keep going. Pull all the way and the needle snaps to true north with a little confetti and a one-line note (which speaks in whichever voice you've picked). It is touch-only and never shows on desktop, and it steps aside for anyone who prefers reduced motion.
The Tree of Compass now shows progress at a glance. The public roadmap (
/tree) gained an honest, segmented progress bar under the intro ("29 of 40 shipped", counted live from the roadmap data so it can never drift), a per-category "shipped" mini bar on every branch, and a gentle pulse on whatever is being built right now. Each feature can carry an optional completion figure shown in its detail popup. No numbers are typed by hand; they are all derived from the curated roadmap, which stays the single safe public layer. Every feature description was also rewritten in plainer, friendlier language with a little emoji, so the tree reads like a person wrote it.Hand your accountant a clean spreadsheet in two clicks. Settings, Invoicing has a new "Export for your accountant" card: pick a date range (it starts on your current financial year), choose whether drafts come along, and download a CSV of your invoices. Every invoice line gets its own row with quantity, price, discount, tax name and rate, net, tax and totals, plus the client and dates, so a bookkeeper or any accounting tool (Xero, QuickBooks, MYOB, a spreadsheet) can take it as-is. Trashed invoices stay out. A direct accounting connection is the next stage on the roadmap; the CSV works everywhere today.
Compass now has a voice, and you can change it. All the small feedback moments (the Saved tick, the undo notice when something moves to the Trash, the apology when a save fails) now come from one carefully written voice: calm, clear and on your side. And in Settings, Appearance there is a new Voice section where you can swap it for something less sensible: Ultra-optimistic, Sarcastic, or Unhinged. The extra voices unlock as you finish your setup steps, the same way themes do, and each card shows a sample line so you can hear it before you commit. Whichever voice you pick, the facts stay intact (what happened, what to do next), it applies only to you, and your clients never see it.
Write the email now, send it later. The composer has a new Schedule button: pick a quick option (In 2 hours, Tomorrow 9am, Monday 9am) or an exact date and time, always in your workspace timezone. Queued emails show on the enquiry in a Scheduled emails card where you can cancel or move them any time before they go. Everything resolves when the email actually sends, so merge fields, your signature, attachments and the recipient address use the latest details, and a scheduled email lands in the portal Files tab exactly like one you sent by hand. If a send fails (say the address bounced in the meantime), it shows as failed on the enquiry with the reason, one click away from retrying.
The email composer grew up: text sizes, smarter spacing, recognisable icons, image alignment, and a toolbar that stays put. You can now set text size (Small, Normal, Large, Huge) anywhere in an email or signature. Paragraph spacing finally matches what real email clients show: pressing Enter reads as a normal new line, and a blank line is exactly one blank line, in the editor and in the sent email alike. The alignment and list buttons use standard icons, selected images can be aligned left, centre or right, recently used text colours appear at the top of the colour picker, and the toolbar stays visible while you scroll a long message. What you compose now arrives looking the same (one shared font and size across the message and signature).
Every file link is now visible and controllable from your Files library. Each file row in Settings, Files shows its links: where each one went (emailed or shared to a portal, and to whom), which version it serves, whether the client has opened it and when last. You can revoke any single link (it stops working immediately, nothing else is touched) or give it an expiry date. A new Versions view lets you download any older version and, when you want to, update every live link to the newest version in one click. Deleting a file that is still linked somewhere now warns you first.
Small files now attach to the email itself; big ones become links, automatically. When you insert a file from your library, Compass checks whether it fits email size limits: files that fit travel as real attachments (you'll see them as removable chips under the message), and anything too large is inserted as a secure link instead. The picker tells you which way each file will go before you choose it. Attached files respect the same portal tick as linked ones, so they also land in the client's Files tab.
The client portal now has a Files tab. Files you email with the new "show in their portal" tick appear there automatically once the email sends, and every enquiry now has a Portal files card where you can share (or remove) any library file with one click. Removing a file from the portal never breaks links the client already received by email. Files stay private by default; nothing appears in a portal unless you emailed it with the tick on or shared it deliberately.
Email any file from your Files library. The email composer's toolbar now has an Insert from Files button: pick a file from your workspace library (or upload a new one on the spot, it is filed against that enquiry) and Compass drops a secure download link into your message. Each link keeps serving the exact version you sent, even if you upload newer versions later, and links stop working if the file is moved to the Trash (restore brings them back). No more re-attaching the same PDF to every email.
Fixed
Large files now upload to the Files library. Uploads go straight to storage instead of passing through the app, so files all the way to the 25MB limit work. Before, anything over about 4.5MB failed with a generic "upload failed" (a hosting request-size cap the app could not raise), so a normal pricing-guide PDF would not go up. The upload error message is also more readable now (it was a faint, semi-transparent toast).
Editing the wedding or event date on an enquiry now updates the calendar, date-clash check, and booking. Previously, saving the date from the enquiry detail page only wrote the legacy column the form field uses, while the column the rest of the app reads stayed stale. The two columns are now kept in sync on every save, so a date change is immediately reflected everywhere.
Pages no longer load unstyled when you navigate from a content page. Leaving a page that carries its own large stylesheet (the Help section, Pricing, the Tree, the changelog, docs) into the app could briefly land on an unstyled screen, because the browser occasionally skipped loading the next page's styles. Those pages now do a full reload when you click into the app, so the destination always arrives fully styled. (Same fix already used when leaving the Waypoint calculator, now applied everywhere.)
The Compass icon shows in the browser tab everywhere. The classic
favicon.icowas an old version, so some browsers and contexts showed a stale icon in the tab even though the rest used the compass mark. It's been regenerated from the current compass (16/32/48px), so the tab icon is consistent across pages and browsers.The event-type editor behaves on a phone. Saving an event type now reliably shows "Saving…" then "Saved" before the dialog closes (it matches the booking-email editor exactly). Tapping into the email body no longer makes the phone zoom in (the rich-text editor now uses a tap-safe text size on mobile, like the other fields). And the dialogs freeze the page behind them while open, so dragging inside one doesn't move the page. (Follow-up to the event-types redesign.)
Today's actions line up neatly. The list item titles now start at the same point on every row, instead of shifting left and right depending on how long each status label ("Overdue 2 days" vs "Overdue 18 days") was. The labels keep their natural sizes; only the titles are aligned.
The home-screen launch screen is more reliable (no more black flash). The branded iOS launch image is now pre-built for every iPhone size instead of being generated on the fly. A slow on-the-fly render could take a few seconds, which is exactly when iOS gives up and shows black on launch. Now it's an instant file, so adding Compass to your home screen reliably shows the branded screen while it loads. (Note: iOS caches the launch image when you add the icon, so delete and re-add the icon once to pick up the new images.)
Less code loads up front on the dashboard. The ⌘K command palette, the install banner, pull-to-refresh, and the new-version watcher now load just after the page appears instead of being part of the initial download, so the dashboard becomes usable a little sooner on a cold launch. Nothing changes in how they behave.
The dashboard loads faster, especially the installed home-screen app. Opening the dashboard used to re-check your sign-in and re-fetch your profile and workspace several times in a single load (once in the page frame, again in the page itself, plus a separate check for the email banner). Those are now resolved once and reused, and the dashboard's data queries run together rather than one after another. Fewer round-trips means the dashboard appears noticeably sooner after launch.
The Sign in / Sign up buttons no longer land on an unstyled page. From the Waypoint calculator (including the "you need an account" gate you reach when reopening the signed-out home-screen app), tapping Sign in or Sign up occasionally loaded /login or /signup with no styling until you refreshed. Those pages carry the large standalone calculator stylesheet, and an in-app navigation away from them sometimes failed to load the next page's CSS. The two buttons now do a full page load on Waypoint/Roadmap, so the destination always arrives fully styled. (Extends the earlier full-load fix, which only covered the signed-in nav links.)
The dashboard now shows its loading layout instead of a black screen on a cold launch. Opening the app (especially the installed home-screen version) used to sit on black for several seconds while the dashboard loaded everything before drawing anything. The page shell + a loading skeleton now paint quickly, then your numbers and lists fill in, so you see the app taking shape rather than a black void.
Less black screen when launching the home-screen (installed) app. The branded splash now caches hard so iOS shows it reliably during the cold launch instead of a black screen, there's a catch-all splash for devices that don't exactly match a known size, and the app paints its background the instant it loads rather than flashing black first. (The remaining launch delay is cold start + loading your dashboard; reducing that is a separate performance pass.)
Contact forms: clearer message when you're over your plan's form limit. If you had more forms than your plan allows (after a downgrade), the notice read "you're using all 1 form" even with 2 in use. It now says exactly how many you're over and what to do, and the wording is correct whether you're under, at, or over the limit (singular and plural).
Contact form Preview now has a way back. Previewing a form opened a bare standalone page with no navigation, a dead-end on an installed (home-screen) app where there's no browser back button. The preview now shows a sticky "Back to forms" bar. The live, embeddable form stays completely bare.
Dates and times across the app now show in your timezone, not the server's. Anything with a time on it, "last synced," "created," "cancelled at," booking times, activity timestamps, and the times in booking emails to clients, used to render off the server's UTC clock, so they could read the wrong day or time depending on where you are. They now use your workspace timezone (set in Business profile), and client emails use your business's home timezone so clients always see a consistent time. Plain calendar dates (wedding dates, due dates) are unchanged, those were always correct.
The dashboard greeting now shows the right day after midnight. It used to read the date off the server's clock (UTC), so in the early hours it could still show yesterday's date (e.g. 2am Saturday in Australia showed "Friday"). It now uses your workspace timezone.
The changelog page reads better on a phone. The big title now scales down to fit narrow screens instead of crowding the edges, the page uses the full width on mobile, and long file paths in the text wrap instead of forcing the page to scroll sideways.
The dashboard's "60-second walkthrough" now actually walks you through getting started. It used to link to a recap of the signup steps you'd already finished. It now opens a new "Getting started with Compass" guide: get a lead in, move it across your board, send a quote, turn a yes into a booking, and get paid.
Onboarding now lands you on your dashboard. Finishing setup used to drop you straight onto an empty board; it now opens the dashboard, your home base.
Stopped naming other CRMs in the app, and dropped the "kanban" jargon. The CSV import hint no longer names a specific competitor (Google Sheets and a generic spreadsheet stay), and the places that called your board a "pipeline kanban" now just call it your board, in the dashboard, onboarding, settings labels, help, the pricing page and the public roadmap.
The default funnel stage is now "Discovery Call Done" for everyone. New workspaces were being seeded with a stage named after one studio's own "Chemistry" branding. The default is now the neutral "Discovery Call Done" (the underlying analytics status is unchanged), and existing workspaces still on the old default name are updated too. You can still rename it to anything during setup or in Settings.
Removed the em dashes from the backup-code welcome email. Replaced them with plain punctuation, in line with the house style.
Changed
Picking a font for your contact form is now visual. Instead of typing a CSS font name and pasting a Google Fonts URL, you pick from a grid of fonts, each shown in its own typeface, grouped into System, web-safe (load instantly), and Google Fonts (load from Google, no URL needed). The live preview updates as you choose. Typing a custom font name / URL is still available under "Use a custom font (advanced)". (Design-language slice 3.)
On/off settings get a consistent switch. Introduced one shared iOS-style toggle used for single on/off settings (the client portal's "ask visitors to confirm their email" and "new clients start with a combined portal" settings), so toggles look and behave the same in light and dark across the app. More single-setting checkboxes move onto it next. (Design-language slice 2.)
Signed-out navigation points to Pricing. When you're not signed in, the homepage now shows just a Pricing link (plus Sign in / Sign up), and every other public page shows the app sections (which open the sign-up prompt when tapped) alongside Pricing. The sign-up prompt's second button is now "Show me pricing" (it opens the pricing page) instead of "Maybe later". Signed-in navigation is unchanged.
Event types have a calmer, consistent row. Each event type used to cram Edit, Copy, four email buttons, Pause and Delete onto one line. Now the row shows the name, key details, one Edit button, and a "⋯" menu (Copy link, Pause, Delete). The four booking emails (confirmation, reminder, cancellation, reschedule) move into an "Emails" section inside the Edit dialog, where you set up the rest of that event type. (Design- language rollout, wave 3.)
Lead sources now use the same clean row layout as Packages. Each source keeps its drag handle for reordering, leads with one Rename button, and tucks Archive and Delete behind a single "⋯" menu, so the actions stop crowding the name and slug. Sources are grouped into tidy cards per category, and the Add buttons match the rest of the app. (Design-language rollout, wave 2.)
Packages & products rows have a cleaner, more consistent layout. Each row now leads with one Edit button and tucks the rest (Duplicate, Archive) behind a tidy "⋯" menu, instead of spreading every button across the row. On a phone the row stacks neatly (name and price, then category and margin, then the actions) so nothing gets squashed or cut off. This is the first step of a shared design language: the same row will roll out to the other settings lists so they all look and behave the same.
Onboarding buttons now respond when you click them, and you can step back from the last screen. Continue and "Open my board" show a pending state (and a subtle press) the moment you tap, so a slow save never feels like a dead click, and the final summary screen now has a Back link in case you want to tweak your stages before finishing.
The top navigation now folds away smoothly as you scroll on a phone. When you scroll down, the row of links (Dashboard, Clients, and so on) eases shut instead of vanishing in a snap, and eases back open when you scroll up or reach the top. The compact bar with the logo and your avatar stays put the whole time. It used to disappear and reappear abruptly, which read like a glitch.
Public copy now aims at freelancers, creative professionals, and service-based business owners (was "freelancers and agencies"). Updated the homepage title/eyebrow/hero/features copy, the footer taglines, the pricing product description, and the SEO keywords (dropped "agency CRM", added "CRM for creative professionals", "CRM for service businesses", and "CRM for consultants"). Drops the unbuilt "agencies" framing in favour of audiences the product serves today.
Booking a lead by hand is now one deliberate button, and it always creates the job. You can no longer drag or set an enquiry to "Booked" from the board, the list dropdown, or the enquiry's Status picker (so you can't accidentally mark something booked, and you can't end up with a "booked" lead that has no job behind it). Instead there's a single explicit "Mark as booked" button on the enquiry, for deals you closed without a quote or contract. Booking still happens automatically too when a quote is accepted, a contract is signed, or a payment is recorded.
The "Won" stage is now called "Booked". The default label for a secured enquiry reads "Booked" instead of "Won" across the app (the board, dashboard, reports, help), and the win-rate metric is now "Booking rate". You can still rename the stage to anything you like in Settings. (Wording only for now; how a lead becomes Booked is unchanged, the auto-trigger rewire comes next.)
Homepage and pricing now speak to freelancers and agencies, not just photographers/studios. The hero, the "what you get" section, the pricing intro and the page metadata previously led with "creative professionals" / "creative studio" / "creatives". They now position Compass as a toolkit for freelancers and agencies running any kind of client work, with the creative and event trades used as varied examples rather than the default. The pricing FAQ that asked "What if I'm not a photographer?" now answers what kinds of work Compass fits: it lists the real built-in industry setups and explains that anyone else can pick "Other" and rename everything to match how they work.
Examples across the app are now varied, and no longer photography-first. Business-name and quote placeholders, the onboarding examples, the pricing FAQ, the SEO keywords, and the help and Tree example lists were photography-led. They now spread across trades (design, coaching, DJ, copywriting, celebrancy, florals, planning, hair and makeup, and yes photography) and never lead with photo or video. Added broader search keywords (freelancers, designers, copywriters, agencies). Cleared a few stray em dashes in the quote screens along the way.
The word "Pipeline" is retired from the interface. It now reads in the terms Compass actually uses: the section is "Enquiries", the kanban view's default name is "Board" (still renamable in Settings, Workflow/Funnel/etc.), and the columns are "stages". Updated everywhere it showed: the homepage feature + meta, the OG image, the command palette, the enquiry detail card ("Stage"), the settings labels, pricing, the help articles, onboarding, and the dashboard metrics ("Enquiry value", "Enquiry drop-off"). Searching "pipeline" in the command palette still finds the board. (Internal code names are unchanged; this is the visible wording only.)
Added
The Files library: upload once, reuse everywhere (T23, part 1). New Settings, Files area for the documents you send often (pricing guides, brochures, checklists). Files are versioned: replacing one makes a new version while anything you already shared keeps the exact version it got, so a sent pricing guide never silently changes. Storage is private (no public links; you download through your account), deleting goes to the 30-day Trash like everything else, and search-friendly names, descriptions and tags keep the library tidy. Coming next on top of this store: pick files in the email composer, share them on client portals, and smart attach-versus-link.
Deleting feels right-sized now: one click + Undo for everyday deletes, type-to-confirm for the irreversible ones (T20 complete). Moving an enquiry, quote, invoice, contract, questionnaire or contact to the Trash no longer interrupts you with a dialog; it happens in one click and a toast with an Undo button appears for ten seconds (Undo puts everything back, including a contact's enquiries). The truly irreversible actions gained friction instead: "Empty trash" and permanently deleting a contact now ask you to type a short phrase before the button unlocks.
Deleting a contact now uses the Trash too, and the whole Trash story is complete (T20). Removing a contact moves them to Settings, Trash for 30 days along with their remaining enquiries; restoring the contact brings back exactly the enquiries that left with them (anything you trashed separately stays in the Trash). A trashed contact disappears from every list and picker, their portal link stops working, and a new enquiry from the same email address creates a fresh contact instead of resurrecting the old one. The existing safety rules still apply: a contact with bookings, live documents or open enquiries can't be deleted at all. Empty trash and the nightly purge cover contacts as well.
Contracts and questionnaires now go to the Trash too (T20 trash complete for documents). Deleting an unsigned contract or a questionnaire moves it to Settings, Trash for 30 days with restore and delete-forever. Trashed documents disappear from their lists, the client portal, public signing and fill links, the dashboard and the AI connector. Signed contracts remain protected and can never be deleted.
Draft quotes now go to the Trash instead of vanishing (T20). Deleting a draft quote moves it to Settings, Trash for 30 days with restore and delete-forever, same as enquiries and invoices. Trashed quotes disappear from the quotes list, client, job and enquiry pages, the portal, public quote links, the dashboard and the AI connector, and a trashed quote can never be superseded or accepted. Quote numbers are not reused while a quote sits in the Trash. Sent and accepted quotes still can't be deleted at all (they are commercial history).
Invoices now go to the Trash instead of vanishing (T20). Deleting an invoice moves it to Settings, Trash for 30 days, where you can restore it or delete it forever; the nightly cleanup purges anything older. While an invoice sits in the Trash its number is never reused, so a restore can never collide. Trashed invoices disappear from Money, the client, job and enquiry pages, the client portal, public links, payment reminders and totals. "Empty trash" now clears trashed invoices too. (Paid invoices are protected records and can never be deleted at all.)
Changed
- Paid invoices and signed contracts can no longer be deleted (T20 protected records). A paid invoice is financial history and a signed contract is a legal record; both now refuse deletion at the server (even from stale buttons) and the Delete control is replaced with a short explanation of why it's gone and what to do instead (for a signed contract that no longer applies, void it so it stays visible with a 'void' label).
Fixed
Activity times now show in your timezone, not the server's. The enquiry Activity feed stamped entries with UTC times (a note logged at 11:37 pm in Perth read "3:37 pm"), and its Today/Yesterday day headers could bucket evening entries under the wrong day. The in-app Calendar's booking time labels and the dashboard's Today list had the same fault. All three now format in your workspace timezone.
Enquiries created through the AI connector now reach your synced calendar. An enquiry created by a connected AI assistant (for example from a screenshot of a text conversation) appeared in Compass and on the in-app Calendar, but never on your own synced calendar: the connector's create and update tools skipped the calendar push that every in-app path performs. Both now push, and status changes made by the assistant move the event between your Leads and Jobs calendars just like in-app changes. Already-created enquiries sync on their next in-app edit.
Added
Empty the trash in one go. The Trash page gains an "Empty trash" button that permanently deletes everything in it right now, instead of waiting out the 30-day window. It asks for confirmation first and tells you how many items are going. (First slice of the safe-deletion work, T20; the automatic 30-day purge already ran nightly.)
Undeliverable and unsubscribed contacts now wear a badge, with one-click resume (T19 complete). When an address bounces or a contact unsubscribes or reports spam, the enquiry header and the client profile show a clear badge explaining exactly what still sends and what is held, with a "Resume sending" control that lifts the block once you have fixed the address (the audit trail stays). The enquiry Activity feed also gains a small "?" legend explaining how reliable each email signal is: Sent and Delivered are confirmed, Opened is approximate (mail filters and link previews can trigger it), Clicked is reliable.
Compass now knows what happens to the emails you send (T19, part 1). Client-facing emails (the composer, workflow sends, quotes, contracts, questionnaires, invoices, payment and booking reminders, booking confirmations, AI-connector sends) are now tracked end to end: the enquiry timeline shows when an email was delivered, opened (marked approximate, since mail filters and previews can trigger opens) and clicked. Problem signals are handled per address: a hard bounce (dead or mistyped address) pauses automated emails to that address and emails you an alert, while emails you send by hand still go through; a spam complaint or unsubscribe blocks only general emails, your documents and payment reminders for active work still send. Soft bounces (full inbox) are transient and change nothing. The "Resume sending" control on the contact and the undeliverable badge land in part 2.
Quote and invoice templates, part 2: Save as template and Start from template (T18 complete). Every quote now has a "Save as template" button (and every invoice one in its action bar) that copies the structure into a new template and drops you in its editor to rename and trim. Creating a new quote offers a "Start from a template" picker (the draft arrives pre-filled with the template's content, terms, tax treatment and attached contract, using the template's quote type), and the new-invoice form gets the same picker (lines, notes and tax rate pre-filled, swappable before you save). Templates carry structure only; client details never travel with them.
Quote and invoice templates: save your standard quote once, reuse it forever (T18, part 1). New Settings, Templates, Quotes & invoices area: a workspace library of reusable starting points for both quote types (Tailored and Customisable) and for invoices. A template carries the structure (line items, packages and add-ons, prices, tax rate, terms, the attached contract, auto-invoice behaviour), never client details, dates or discounts. Each template has its own full editor (catalog pickers included), plus duplicate, archive and delete from the list. Comes with a help article. The other two entry points (Save as template on a quote/invoice, Start from template when creating one) land next.
Fixed along the way: unticking "Create the invoice automatically when this quote is accepted" now actually saves. The quote editor read the checkbox in a way that meant an unticked box could never persist as off (the same bug class as the email toggles fixed in #144), so auto-invoice was effectively always on. Unticking it now sticks.
Invoices now have due dates, and Compass chases them for you. Set "invoices fall due N days after issue" once in Settings, Invoicing (or pick a due date per invoice) and every sent invoice knows when it's due: the Money list shows "Overdue since ..." in red, and the invoice page shows the due date. Automated payment reminders then do the chasing: a friendly nudge before the due date, one on the day, and an overdue sequence that keeps going until the invoice is paid, all timings configurable, sending at 9am your time, linking the client straight to their invoice. Reminders are on by default per your choice, and any single invoice can be paused outright or snoozed until a date (for an agreed arrangement) right from its page.
The client portal gets its own page colour, and one click matches your app's look. Settings, Client portal now has a page-colour picker next to the accent, with a "Match my app look" button that copies your current Compass theme's background and accent onto the portal (a one-time copy, changing your app theme later won't silently restyle what clients see). Cards stay white for legibility. The live preview now paints the real backdrop and shows sample tabs and a sample document card on both the desktop and mobile views, so it reads like the actual portal.
The migration toolkit is complete: contracts, workflow attachment, and job edits via the connector. Three more connector tools: record an already-signed contract from your old system (with the original signer and date, clearly marked as an imported record, never re-signed, never emailed), attach a workflow to an enquiry with the already-done steps pre-ticked and automation starting PAUSED (so no mid-engagement client gets a surprise email), and update a job's details. Together with the earlier tools, a whole client history, contacts, enquiries, jobs, paid invoices, signed contracts, and half-finished workflows, can now move from another CRM in one conversation.
An AI assistant can now build entire workflows in Compass. Six new connector tools behind the new building-blocks switch: list and fetch workflow templates, create a complete workflow (phases, steps with anchors and chained timing, composed per-step emails, questionnaire and contract sends), update one (existing enquiries keep their own copy; pushing changes to them stays a deliberate action in the app), and create the questionnaire and email templates a workflow references. The tools validate everything with specific, fixable error messages (which step, which field, what's allowed), check that referenced templates actually exist, refuse looping step chains, and spell out the timing rules so an AI can't get the date maths backwards. Making a workflow the default stays a deliberate, clearly-warned choice. This is what lets you say "read my old CRM's wedding workflow and rebuild it in Compass" in one conversation.
A third AI safety switch: building blocks. Settings, Connect to AI gains "Allow building blocks (templates & workflows)" alongside the existing changes and sending switches. It governs whether a connected AI can create or edit workflow templates and questionnaire/email templates, the things that shape how future enquiries are handled. Off by default, off until the disclaimer is accepted, and existing AI connections keep working without re-authorising.
Workflows can now anchor on "after job booked". A new timing anchor for workflow steps that fires when the enquiry is booked, whether that happened by quote acceptance, contract signing, or the first payment (the same trinity that books a job automatically). Until now post-acceptance chains could only anchor on "after quote accepted", which never fired for contract-first or payment-first bookings. Available in the workflow editor's timing picker, the due-date badges, and the automated send and reminder schedules.
An optional email check on client portals. Portal links stay friction-light by default (the unguessable link is the key). If you want a second layer, switch on "Ask visitors to confirm their email" in Settings, Client portal: anyone opening a portal first enters the email the link was sent to (the client's address or any extra recipient on the enquiry), no password, and their device stays signed in for 30 days. Wrong guesses get a friendly nudge and reveal nothing, and rapid guessing is throttled.
The form builder shows a live preview. Building or styling a contact form now shows the real thing beside the editor, rendered exactly as visitors will see it (same page background, same card, same fields), updating with every keystroke. You can type into it to feel it out; it never submits. On the Build tab it previews your unsaved fields; on the Style tab, your unsaved colours and fonts.
Contact forms now have plan limits: 1 on Pilot, 3 on Captain, 10 per brand on Fleet. The forms page shows how many you're using; at the limit the New form button makes way for a plain explanation with a link to plans (and the server enforces it regardless). The pricing page's comparison table carries the new row.
The AI connector can now build your client base, jobs, and invoice history. Four new connector tools: create or update clients (re-running an import safely matches existing people by email), create jobs (including completed past work for migrated history), and create invoices as records, including already-paid ones, with your numbering kept sequential or an explicit unused number, and the original system's reference in the notes. Nothing in these tools ever emails a client. Combined with enquiries (already supported), this is the first slice of migrating an old CRM into Compass by just asking your AI assistant.
Every dashboard card can move now, not just the metrics. Today's actions, Next actions, Upcoming events, Recent enquiries and Where they come from all join the same drag-and-resize grid as your metric widgets: hit Edit layout and arrange the whole dashboard exactly how you want it, per person. The standing cards can be moved and resized but not removed (so nothing important can vanish), and your arrangement is remembered like the rest of the layout.
Invoicing settings reorganised into four clear sections. Business details, the GST toggle, Getting paid (bank + terms), and Numbering & logo each show as an at-a-glance summary with edits in a focused window (the GST toggle just saves the moment you flick it). Each section saves on its own, so updating your bank details can never touch your numbering, and every save confirms inline.
Email settings now save in three independent pieces. Sender details (name + reply-to) show as a summary and edit in a focused window; the signature keeps its editor with its own Save; the four preferences (BCC me, tracking, new-lead notifications, daily digest) save the moment you flick them. Each piece saves on its own, so saving one can no longer overwrite another.
Three more settings join the new save patterns. Your name in Profile now saves itself when you leave the field. Calendar preferences (date format, time format, week start) save the moment you pick an option, no more Save button at the bottom. And the Public profile card shows your live page link and welcome message at a glance, with edits in a focused window. All three confirm with the same inline Saving…/Saved feedback. A full audit of every other settings page confirmed the rest already behave correctly for what they are (lists, builders and editors), with two larger conversions (email sender details, invoicing) queued for their own passes.
Business details now edit in a focused window. On Settings, Business profile, your studio name, brand, industry, timezone and currency show as a tidy read-only summary; Edit opens a focused window with one Save, and the card confirms with the same Saved feedback as everywhere else. This is the editing pattern the rest of Settings is moving to: see your settings at a glance, change them in a focused window, one obvious Save.
The new-enquiry window now floats, drags, and follows you around the app. Adding an enquiry no longer locks the page behind a blocking popup: it opens as a floating panel you can drag anywhere, keep using the page underneath, and minimise to a pill in the corner. Your half-typed draft survives navigating to any other page, and restores exactly where you left off. Closing a draft with typed content asks first; pressing Escape just minimises (it never throws your draft away). On phones it opens as a full-screen sheet. This floating-panel system is a new app-wide building block; more windows move onto it over time.
Rearrange your dashboard right on the dashboard. A new "Edit layout" button on the dashboard switches the metric cards into edit mode: drag a card to move it, tap S, M or L to resize it on the grid, give any card its own time window (this month or year-to-date instead of following the financial-year picker, shown as a small tag on the card), or remove it with one tap. Changes save as you go with the same Saving…/Saved feedback as Settings. Your layout is now personal too: it's saved to your account, so when teammates arrive everyone arranges their own dashboard without trampling each other's. The settings page keeps the metric library for adding cards; the old up/down arrows are retired.
Your theme now follows your account, and every theme is editable. Until now your theme choice lived only in the browser, so a new device or a reinstall reset you to Light. Now it's saved to your account and synced: pick a theme on one device and it's waiting on the next (existing devices keep their choice; it becomes the account's automatically). Once you've finished setup you can also edit ANY theme's colours (background, text, accent; the rest of the palette derives to match), reset a theme back to its defaults, and save your mixes as named themes that join the gallery. System gets a proper pair picker too: choose which light theme and which dark theme your device's appearance switch flips between. Changes save with the same Saving…/Saved feedback as the rest of Settings. The red dot stays put.
Refer a friend. Every workspace now has its own referral link, in Settings, Refer a friend (with a help article to match). Share it and the page tracks everyone who signs up through it. The rewards are two-sided: when a referral starts a paid plan they get 10% off, and you stack 10% off your own plan per active referral, so ten active referrals make Compass free. Rewards switch on automatically when paid plans launch; the link and the tracking work today.
A real waitlist for paid plans, with a founding-member deal. "Join the waitlist" on the pricing page no longer opens your email app; it takes you through the free signup, and inside the app a slim bar (and a card in Settings, Billing) lets you join with one click. Joining locks in the founding-member rate: 50% off your first year when Captain and Fleet launch. The bar only shows for free workspaces, disappears the moment you join (the Billing page then shows you're on the list and since when), and goes away for everyone when paid plans go live.
Templates now have their own home in Settings. A new Templates section in the settings sidebar and on the settings home groups everything you reuse: Packages & products (renamed from Packages), Contracts, Questionnaires, and Emails, each as its own page, with quick tabs across the top of every one so you can hop between them. Packages sit alongside the other templates because they're the reusable building blocks your quotes are made from.
Slow settings saves now say what they're doing. Changing your financial year used to quietly grey out for a few seconds with no confirmation. It now shows "Saving…" then "Saved" right next to the setting (and a clear message if it couldn't save). First step of a consistent save-feedback pattern rolling out across Settings.
The enquiry page is redesigned around your workflow. On desktop it's now three columns: the workflow sits front and centre in a wide middle column (with the activity feed below it), the client portal link, enquiry details and recipients live in a left rail, and quotes, jobs, invoices, contracts and questionnaires live in a right rail. Both rails stay frozen on screen while you scroll a long workflow, and each rail scrolls on its own when it's taller than the window, so the documents you're referencing never disappear off the top. On phones and tablets everything stacks in one column as before, with document cards above the activity feed.
New enquiries now pick up your default workflow automatically. If you've marked one workflow as the default, every new enquiry gets it attached the moment it's created, whether you add it yourself, an AI assistant creates it over the Compass connector, a client submits your contact form, or it arrives from your website as an inbound lead. The workflow comes in held: nothing sends on its own until you open the enquiry and arm its automation, so an overnight enquiry never fires an email before you've looked at it. Before this, the workflow engine sat idle until you attached a workflow to each enquiry by hand. (Safe by design: the attach never blocks creating the enquiry, and a held workflow is skipped by the send engine.)
Invoices now show the same dated lifecycle line as your other documents, finishing "drop the badge, keep the facts". On the Money list, the Status column now reads "Created...", "Issued..." or "Paid..." with the date and time (in your workspace timezone) instead of an Unpaid/Paid tag, so you can see at a glance how long an unpaid invoice has been outstanding, and paid ones turn green. (The Unpaid / Paid / Draft filter buttons are unchanged.) On an invoice's own page, a paid invoice now clearly shows "Paid 5 Jun 2026, 2:39 pm" under the total (and the total reads "Total" rather than "Total due"); before this a paid invoice looked identical to an unpaid one. And the quote page drops its redundant "Status" line, the dated heading and the Sent/Accepted/Declined rows already say everything.
The Waypoint pricing calculator now covers freelancers, professional services, and trades. The "What kind of work do you do?" picker has two new groups: Freelance & Professional Services (consulting, bookkeeping & accounting, marketing & social, virtual assistant) and Trades & Home Services (cleaning, handyman & trades, gardening & landscaping). Each new trade has its own tailored business and per-job cost starting points (the trades use a vehicle/tools/materials cost shape rather than the desk-work one). So a consultant, bookkeeper, cleaner or landscaper sees their own work instead of only weddings, fitness, beauty and design. (More sub-types can still follow.)
A signed contract or a recorded payment now books the job automatically. Until now only accepting a quote turned an enquiry into a booked job. Now when a client signs a contract, or you mark an invoice paid, the enquiry is booked and its job is created too (named after the enquiry), if it isn't already. Whichever happens first creates the one job; the others quietly do nothing. (If the booking ever fails it's logged and the sign/payment still goes through.)
Every document now shows when it last moved, with the time. On the Quotes, Contracts and Questionnaires lists, each one now shows a plain dated line in place of the old status tag, "Created 5 Jun 2026, 2:39 pm", then "Sent...", "Accepted...", "Signed...", "Submitted..." as it progresses, so a brand-new one reads "Created..." instead of looking blank, and a finished one tells you exactly when it happened. Times show in your workspace timezone. Now on the lists, on each document's own page, and on the document cards down the side of an enquiry, a job, and a client. (The invoice pages and the client portal follow next.)
Contracts and questionnaires now show in the client portal the moment you create them. Before, a quote or invoice appeared in the client's portal as soon as you saved it, but a contract or questionnaire only appeared once you pressed Send, so a freshly attached contract was invisible to the client. Now all four behave the same: the moment you create one it shows in the portal, with its open action ready (review a quote, read and sign a contract, fill a questionnaire, view an invoice). The client still needs the private portal link, which only you can share. (Each document quietly gets its own private link the moment it's created.)
A brand-new document just exists, no "Draft" label anywhere. Following on from hiding the draft pill on the document lists and detail pages, the same rule now applies on the enquiry, job, and client pages (the Quotes, Invoices, Contracts, and Questionnaires cards in the sidebar) and on the client portal. A just-created quote, invoice, contract, or questionnaire shows no status label until something meaningful happens to it (sent, accepted, signed, paid, completed), which still shows as before. On the client portal this means your clients never see an internal "Draft" word on a quote or invoice. (Step 2 of "drop the badge, keep the facts".)
Mark a date clash as intentional. Sometimes two events on the same day are on purpose (you shoot one wedding, a second shooter covers another). Open either the enquiry or the job, click "This is intentional, hide it", and the clash warning clears for that date (with a "show the warning again" link to undo). A genuinely accidental third booking on that date would still flag, so you only silence the ones you mean to.
Date clashes are flagged automatically. When two enquiries (or an enquiry and a booked job) fall on the same event date, Compass now warns you: a "Date clash" tag appears next to the date in the Enquiries list and board, and opening an enquiry shows a banner listing the other enquiries/jobs on that date, with links. So a double-booking risk is evident the moment two land on the same day. (Lost enquiries and cancelled jobs are ignored.)
Edit a contract's wording right from the quote, before you save. Attach a contract to a quote and an "Edit contract wording" section appears immediately (no need to save first): tweak the text with the same formatting toolbar, and those edits become the contract when you save the quote. No detour to the contract's own page. Switching the attached template re-loads that template's wording. (Names and dates fill in automatically when you save.)
Contract bodies can now be formatted. The contract body editor (when you create or edit a template in Settings, and when you edit a contract on a job) is now a proper rich-text editor: bold, italic, underline, bullet and numbered lists, alignment, and text colour, instead of plain text. The formatting shows through on the client's signing page and on your own contract view. Existing plain-text templates and contracts keep working untouched, and pick up the new editor with their wording intact the next time you open them. (Images aren't included, contracts don't need them.)
Clients can update a submitted questionnaire. After submitting, there's now an "update your responses" option to change an answer and re-submit. Each submission is kept as a version (the previous answers are archived), so nothing is lost.
Questionnaires save as your client types. A client filling out a questionnaire now has their answers auto-saved as they go (with a "Changes saved" note), so they can stop and pick up later, even on another device, without losing anything. Submitting still finalises it as before.
Clients can open their invoice from the portal. The portal's invoice was text-only with no way to see the detail. Now each invoice has a private link to a clean invoice view (line items, totals, and how to pay). Online card payment is still coming; for now it shows your payment instructions.
Quotes are usable the moment you save them. A saved quote is now live and shareable straight away: copy its link from the quote page and send it however you like, or hit Send to email it. The client can review and accept from that link without you having to "send" first. Saved quotes also stay editable until the client accepts (after that they lock, and trying to save shows a notice). ("Draft" now reads as "Ready", a saved quote isn't an unfinished draft.) This is step 1 of a larger quote revamp.
Attach a contract to a quote and it appears right away. When a quote has a contract template attached, the contract is now created the moment you save (as a draft, on the enquiry), instead of only when you send the quote. Sending the quote still emails the contract for signing. Change the attached template and the old draft is cleaned up; delete the draft quote and its draft contract goes too. (Step 2 of the quote revamp.)
Quote discounts can now say what they're for. When you add a discount to a quote, there's a "What's this discount for?" field, and the reason shows to the client on the quote (e.g. "Returning client"). (Per-line and itemised discounts are coming in a follow-up.)
The client portal now has tabs. A job's portal shows Home plus a tab for each document type it has, Quote, Contract, Invoice, Questionnaire, each with its own shareable URL. A small red badge on a tab counts what's waiting on the client (a quote to review, a contract to sign, an unpaid invoice, a questionnaire to fill), so they can see at a glance what needs them. (Combined portals keep their grouped-by-job overview.)
Each job now has its own client portal link, ready to share. Open an enquiry and the "Client portal" card at the top of the sidebar has a private link. Send it to the client and they see one page with everything for that job, the quotes, invoices, contracts, questionnaires, and bookings, all in one place. The link is created automatically (no setup) and you can copy it or open it to preview exactly what the client sees. (The older per-client portal link on the client page has been removed in favour of this per-job link.)
Optional "combined" portal for repeat clients. On a client's page there's now a "Combined portal" toggle. Leave it off (the default) and each job keeps its own link. Turn it on for a client who has more than one job with you and they get a single link that lists every job, grouped by job, with that job's own quotes, invoices, contracts, questionnaires, and bookings under each. Every job shows its own section (one with nothing outstanding says so), and finished items collect in a History section at the bottom. Copy or open the combined link right from the toggle.
Brand your client portal. Settings → Client portal now has a Branding section where you set an accent colour, a custom welcome message, and a header banner image (recommended 1600 × 400px, a 4:1 strip), with a live desktop and mobile preview as you go. Your accent shows on the portal, the banner runs full width across the top, and the welcome message replaces the default intro. (Fonts and full palettes will come later with the brand kit.)
Set a workspace-wide default for new client portals. A new Settings → Client portal page lets you choose what new clients start with: one link per job (the default) or a combined link. It only affects clients created from then on, and you can still switch any individual client on their page.
New workspaces start with a ready-to-use email template for each type. Brand-new accounts are seeded with a friendly starter template set as the default for General, Quote sent, Invoice sent, Payment reminder, Contract sent, and Questionnaire sent, so you have something to send (and tweak) from day one instead of a blank list. (Existing accounts keep their own templates, nothing is overwritten.)
The same merge variables now work in automated workflow emails. Workflow send-steps offer (and fill in) the same grouped set as the composer, client phone and email, event location, and your business email / phone / address, not just name and date. Automated emails resolve them through the same shared logic as your hand-written ones, so they stay consistent.
"Lead source" variable in the composer. When you write an email by hand, you can insert the lead's source (how they found you). It isn't offered in workflow steps for now (it would need a per-workspace lookup the automated sender doesn't do yet), so it can't come out blank there.
More merge variables in client emails, grouped by type. The "Insert variable" menu in the email composer now offers client phone and email, event location, and your business email / phone / address (pulled from your Invoicing settings), alongside the existing client name, partner, studio name, and event date, organised under Client / Job / Business headings. Every variable listed actually fills in when the email sends (nothing that would come out blank is offered). The quote/invoice link tags are temporarily not listed while the client portal they'll point into is being built.
Link text to a Compass merge link (quote link, invoice link). The email editor's link button now opens a small panel where you set the display words and the destination, and you can pick a merge link (like Quote link or Invoice link) as that destination with one click. So "view your quote" can link straight to the client's quote, with the real link filled in when the email sends. Makes migrating templates that use link variables straightforward.
Every settings page now has a Help article. Filled the remaining gaps (Lead sources, Tax rates, Marketing & business costs, Trash, and the contact form builder, which had a Help link pointing at an article that never existed). From now on this is a rule the build enforces: a settings page can't ship without a Help article behind its "?" link.
Help articles for everything shipped recently. The Help centre gained new sections and guides covering Quotes, Contracts & e-signatures, Questionnaires, Invoices, Workflows (including automatic sending and its safety limits), emailing clients, booking pages & event types, connecting Google/iCloud calendars, and Appearance & themes, so the newer parts of Compass are actually documented.
A fuller email formatting toolbar. The rich-text editor used for emails and templates now has underline, clear-formatting, paragraph alignment (left, centre, right, justify), and a text-colour picker, on top of the existing bold, italic, link, lists, and inline image. All formatting is written inline so it survives in the recipient's email client. (Text size in px is a small follow-up.)
Email templates are now grouped by purpose, with a default for each. Settings, Email lists your templates under clear headings (General, Quotes, Invoices, Payment reminders, Contracts, Questionnaires), and you can mark one template as the default for a purpose using "Set as default" (a "Default" badge shows which one is set). Defaults are remembered per specific purpose (for example "Invoice sent" separately from "Invoice reminder"). This is the groundwork so the "Email this client" composer can soon show only the relevant templates and pre-pick your default. (Part of the email-templates-by-type work.)
Push a workflow edit to the leads already using it. Editing a workflow used to only affect new leads (each lead keeps its own copy from when it was attached). Now, in Settings, Workflows, a workflow that's on active leads shows "on N active leads" and a "Push to N leads" button that updates them all to the current steps in one go. Their progress (done / stopped) is kept, and finished workflows are left alone.
One-tap reminders for unanswered questionnaires and contracts. When a sent questionnaire or contract is still waiting on the client, it shows in Today's actions with a "Remind" button, one tap re-sends it, no need to open the document. It disappears on its own once the client fills it in or signs (the list only shows ones still outstanding).
Workflows can auto-send questionnaires and contracts, not just emails. A workflow step set to Send questionnaire or Send contract and to "Send automatically on the due date" now creates the document from its template, drops a live link into the step's email (where you put the link variable), and sends it to the included recipients, all on its own, with the same guardrails as automatic emails (daily cap, civil hour, no double-sends, per-lead pause). The document is created once per step, so a failed send never makes a duplicate contract. Stays off until you arm such a step.
A "To send" list across all your enquiries. A new page (linked from the dashboard's Today's actions) gathers every workflow email that's due, overdue, today, or coming up soon, across all your couples in one place, so you can work down the list instead of hunting enquiry by enquiry. Each row opens its enquiry, where you review and send. Steps that send themselves are tagged "Auto"; stopped and paused ones are left out.
"Send now" opens the email so you can review before it goes. Clicking Send now on a workflow step now opens the email pre-filled exactly as it will send (the step's own message, with your details and signature merged in). Tweak anything, tick which saved recipients should get it (the couple, a planner, etc.), and send. It goes out the same audited way as the automatic sends, so manual and automatic are finally identical, and it works for steps you built in the composer (no template needed).
Control automation right on the enquiry. The workflow card now shows an "Auto" tag on steps that send by themselves, a per-step "..." menu (mark done, send now, or Stop automation), and a struck-through "Stopped" look for steps you've halted (different from done). There's also a Pause automation switch for the whole lead, when paused, no automatic emails go out for that enquiry until you resume, though you can still send manually. Ticking, stopping, and pausing are all instant now.
A daily limit on automatic emails per job, so no one gets blasted. A job's workflow will send at most a few automatic emails per day (default 4, set per workspace). If more come due the same day, the extras are held and go out the next day on their own, or you can send them now from the enquiry. When that happens you get a heads-up email naming the job, so you are never surprised.
Workflows can now send emails on their own. A workflow step set to Send email and to "Send automatically on the due date" is emailed to the job's included recipients when it falls due, at a civil hour in your timezone. Every auto-send is recorded, no one is emailed twice, steps armed long after they were due are flagged for review rather than blasted late, and you can pause automation per job. It stays dormant until a step is explicitly armed, with nothing armed it sends nothing. For now this covers email steps; auto-sending questionnaires and contracts comes next.
Rename an enquiry from its title. A pencil next to the enquiry name lets you rename it inline (Enter to save, Esc to cancel). The new name shows straight away. Clear the name to fall back to the couple's names. Until now the enquiry title was fixed at creation and editing the couple fields did not change it.
Quotes is back in the top nav. The full quotes list now has its own nav entry (next to Money), so you can reach every quote without needing the URL.
Jobs: choose who automated emails go to. Every enquiry now has a Recipients panel. The primary client (the one who signs) sits at the top, and you can add secondary clients and third parties like a planner, each with their own email address. A tickbox next to each person decides whether they are included in automated workflow emails: clients are ticked by default, a newly added third party starts unticked so an outsider is never auto-emailed by accident. Colour labels (Primary, Secondary, Third party) show each person's status at a glance. Signing is unchanged, only the primary client signs a contract; the tickboxes only affect who gets emails. Add and edit people behind an Edit control in the panel header, and every tick, add, edit, and remove updates instantly (no page reload wait).
Workflows: write the email each send step sends, and choose manual or automatic. A workflow step set to Send email, Send questionnaire, or the new Send contract now carries its own subject and message, composed right in the workflow editor (start from a template or write from scratch, with client merge fields and, for questionnaire and contract steps, a link variable to drop in). Each send step also chooses how it goes out: wait for you to send it (manual, the default), or send automatically on its due date at your best time, as early as possible, or a specific time. Nothing sends on its own yet, the background engine that acts on the automatic setting is next; until then automatic steps simply show an "Auto" badge.
Workflows: "Send now" on a lead's email steps. When a workflow step is set to Send email with a template bound, the lead's workflow card shows a "Send now" button. One tap sends that template to the client (always to their stored email address), drops your signature in, logs it in the enquiry history, and ticks the step off. You stay in control, nothing sends on its own yet.
Workflows: say what each step does. A workflow step can now be set to Task, Send email, Send questionnaire, or Appointment. For the send actions you pick which template it uses, and the step shows a warning if no template is chosen yet. Steps display a small action chip so a workflow reads at a glance. (Sending still happens when you choose; the automatic scheduler is a later step.)
Workflows: phases you can collapse, rename, and reorder. The workflow editor now groups steps into phases (new workflows start with Lead, Production, and Post). Collapse a phase to tidy a long workflow, rename phases to suit your process, reorder or add and remove them, and move steps between them. Steps show as compact one-line rows now (name plus when it is due), click one to expand and edit it. Existing workflows keep working and tuck their steps into a single phase you can split up.
Per-template signature control. Each email template now has an "Include my signature" switch (on by default). When you start a message from a template, the composer follows that template's preference, and you can still change it for the one send. Turning it off removes the signature (and any {{signature}} marker) for that template.
Email a client straight from their enquiry. The enquiry detail page now has an "Email client" button that opens a composer: start from one of your email templates or a blank message, write with the same rich-text editor (inline images and merge fields like the client's name), and send. The email always goes to the address stored on the enquiry, never one typed in by hand. Your signature is shown exactly as it will appear and added automatically, and you can tweak it for this one send (or drop a {{signature}} marker in the body to place it yourself) without changing your saved signature. Every send is recorded in the enquiry's activity history.
Calendar Year view: starts in January, with a Financial-year toggle. The Year view now opens on the current calendar year (January to December) instead of rolling twelve months forward from today. A new toggle in the calendar header switches between "Calendar year" and "Financial year" (your workspace's financial year, e.g. July to June), and Compass remembers which you prefer on that device.
Faster-feeling navigation (loading skeletons). Opening Clients, Contracts, Quotes, Jobs, Money, Questionnaires, Event bookings, or any of their detail pages now shows an instant, correctly-shaped skeleton while the page loads, instead of a generic placeholder or a frozen screen. The page swaps in as soon as its data is ready.
Duplicate a template. Email, contract, and questionnaire templates each have a Duplicate button now, it makes a copy named "... (copy)" you can then tweak, so you can spin a variant off an existing template without rebuilding it.
Rich email signature (T2, slice 2). Your email signature is now edited with the same rich-text editor as the rest of Compass's email tooling, so you can format it and drop in a logo or photo, and it matches the emails it is appended to. Existing plain-text signatures keep working (detected and rendered with their line breaks, migrating to the rich format the next time you save). The signature now renders consistently across every outbound email (quote, invoice, contract sends, and AI-sent client emails).
Inline images and GIFs in the email editor (T2, slice 1). The rich-text editor now has an "Insert image or GIF" button: pick a file, it uploads to storage and embeds by URL (never base64, which hurts deliverability), so emails can carry inline photos and animated GIFs. Live in the event-type email editor now; rolls out to the main Email Templates editor and a new "Email this client" composer next.
Smarter enquiry naming (derived, not baked). New enquiries now build their display name from the structured contact fields per the workspace's rule, instead of trusting whatever single name a form sent. Couples land as "Keely & Alex" automatically, and solo or company-based industries get a sensible single name. Each industry preset ships a default rule (weddings show both partners; hair & makeup shows the first contact), with a Vocabulary override ("How a new enquiry's name is shown"). Applied to both intake paths, the website webhook and the hosted contact form, plus the owner notification and auto-responder.
Workflows: "after a specific step" timing. A workflow step can now be timed relative to any other named step, not just the five fixed anchors (lead created, quote sent, quote accepted, event date, previous step). Pick "after a specific step" and choose which one; the due date resolves off that step's own date (with a cycle guard). First slice of the larger workflow-builder rework. No migration (steps are stored as JSON).
MCP connector, Phase 5: hardening, audit visibility, and help. Closes out the connector. The "Connect to AI" page now shows a Recent AI activity log (every change and send, with status), and warns when both changes and sending are armed together. The MCP endpoint re-checks paid-plan entitlement on every call, so a downgrade cuts off existing connections, not just new ones. Adds a "Connect your CRM to an AI assistant" help article and an AI-connector section to the deploy smoke test.
MCP connector, Phase 4: write and send tools. With the arming toggles on, a connected AI can now act, not just read. Write tools (
create_enquiry,update_enquiry,add_enquiry_note) create and update leads; the send tool (send_client_email) emails a client. Heavily guarded: every write/send call re-checks the per-workspace arming flag (off by default), is rate-limited per workspace (sends tighter), runs as the connected user through RLS, and is recorded in a new audit log. Wrong-recipient protection: the send tool only ever emails the address stored on the named client record, never an address the AI supplies. Tools the connection's scope does not grant are hidden from its tool list.MCP connector, Phase 3: Settings "Connect to AI" + arming toggles. A new Settings page (Connections, Connect to AI) shows the connection address to paste into Claude or ChatGPT, a plain-English how-to, the list of connected apps with one-tap disconnect (revokes that app's tokens), and the tiered permission controls. Reading is always on once connected. "Allow changes (safe writes)" and a separate, louder "Allow sending to clients" are off by default and only arm after the user accepts a warning and liability disclaimer. The flags are stored per workspace and will be re-checked on every write/send (Phase 4). Gated to paid plans.
MCP connector, Phase 2: read tools. The MCP endpoint is live at
/api/mcp(Streamable HTTP, built on @vercel/mcp-adapter), bearer authenticated by the Phase 1 OAuth tokens. It exposes read-only tools across the client-facing surface: leads and enquiries (list_enquiries,get_enquiry), clients (list_clients,get_client), quotes and invoices (list_quotes,get_quote,list_bookings), contracts (list_contracts,get_contract), and calendar (list_meetings,get_availability). Every tool runs as the connected user through their RLS (a real per-user session is minted server-side, never the service role), so an AI only ever sees what that user could see in the app. No write or send tools yet (Phase 4).MCP connector, Phase 1: OAuth 2.1 gateway. Compass is now its own lightweight OAuth 2.1 Authorization Server so paid workspaces can connect their CRM to an LLM (Claude, ChatGPT) over the Model Context Protocol. This phase ships the auth plumbing only (no tools yet): dynamic client registration (RFC 7591), the discovery documents at
/.well-known/oauth-authorization-server(RFC 8414) and/.well-known/oauth-protected-resource(RFC 9728), the/oauth/authorizeconsent screen behind full login plus 2FA, and the/api/oauth/mcp/tokenendpoint with mandatory PKCE S256 and rotating refresh tokens (with reuse detection: replaying a rotated token revokes the whole connection). Tokens are Compass-issued, workspace-scoped, hashed at rest, and validated on every call. The feature is gated to paid plans (open to everyone until billing goes live, so it is testable now).{{partner_name}}contract placeholder for the second partner. Resolves from the client's stored Partner 2, so couples' contracts fill in both names instead of leaving a dead token (and Compass never has to block a send the way Studio Ninja does for single-contact couples). Listed in the template-editor placeholder hint.
Changed
A just-created quote, contract, questionnaire or invoice no longer shows a status badge. A brand-new document is simply editable and "just exists", so the "Draft"/"Ready" pill is gone from the lists and detail pages. The meaningful states stay as the at-a-glance fact (Sent, Accepted, Signed, Completed, Unpaid, Paid, Declined, Void, etc.). (Step 1 of dropping the redundant status badges; the sidebar cards and client portal follow.)
Saving a quote takes you back to the enquiry. The quote editor's button now reads "Save quote" (was "Save draft"), and saving returns you to the enquiry the quote belongs to instead of leaving you on the quote. The attached contract no longer appears to clear after saving (the dropdown used to reset visually even though the contract was saved correctly).
A saved contract reads "Ready", not "Draft". A contract you've set up but not yet sent now shows the status "Ready" (matching how a saved quote reads), since it's editable and ready to send, not an unfinished draft. It's still the same editable, not-yet-sent state.
The settings menu closes when you move to another page. If you open "Show all settings" without pinning it, navigating to a different settings page now starts with it closed again. Pin it (the "Keep open" toggle) to have it stay open across pages as before.
Tidier "Use for" menu on email templates. When choosing what a template is for, the menu no longer offers the three categories with nothing behind them yet (Welcome, Quote declined, and Enquiry auto-reply, the real auto-reply lives on your contact form). Existing templates already set to those still show fine.
"Start from a template" is grouped by type everywhere. Both the "Email this client" composer and a workflow Send-email step now group your templates under the same headings as Settings (General, Quotes, Invoices, Payment reminders, Contracts, Questionnaires), General first, so you can find the right one fast instead of scrolling a flat list. Nothing is hidden (a Send-email step is still the catch-all until dedicated quote/invoice send actions arrive).
Accessible text-colour picker (colourblind-friendly). The email editor's text-colour control no longer opens the operating system's colour wheel. It now opens a list of named colours (Black, Red, Green, Blue, Navy, Purple, and so on) shown as readable labels next to each swatch, so you can pick by name instead of by sight. Includes an "Automatic" (default colour) option and a typed hex field, and the toolbar button's tooltip shows the current colour's name.
Signature preview is tucked away by default in the email composer. The signature used to take up a big chunk of the "Email this client" window. It's now collapsed by default ("Show preview" reveals it, and the per-send edit lives inside), so you get the full room to write your message.
Friendlier merge-variable names. The Insert-variable menu now reads "Client's name", "Client's phone", "My business name", "My business address", "My email signature", etc., under Client / Event / My business headings, clearer than a generic field list.
Cleaner Waypoint/Roadmap headers. Tightened the spacing (less empty room above the eyebrow and between the intro and the first step), shrank the big slash titles (Business / Costs, The / Numbers, Each / Month) so each step takes less vertical space, and made the two pages consistent. The industry button moved below the "Photography pricing" label and now reads "Switch industry" so its purpose is clear.
Warmer "free forever" pricing. The free Pilot plan now reads "Free forever" with a green badge, and the page copy leads with backing creatives while they find their feet and grow at their own pace (no card, no countdown), instead of feature-speak. Hero, FAQ, and meta description updated to match.
Help links open in a new tab and lead back. The "? Help" link on a settings page now opens the article in a new tab, so you keep your place in settings, and each article that documents a setting shows a "Back to
" link at the top for the return trip. The Help link was also added to the pages that were missing it (Workflows, Contract templates, Questionnaire templates, Booking pages, Calendars, Appearance, Email, Invoicing, Packages, Payment schedules, Vocabulary, Dashboard layout). A fresh, more distinct set of appearance themes. The theme gallery (Settings, Appearance) swaps the near-identical older palettes for ones that actually look different from each other: Sage, Apricot, and Ocean (light), plus Midnight, Forest, and Plum (dark), alongside the existing Light, Dark, Espresso, and Build your own. If you were on one of the retired themes it moves you to the closest new one automatically (Sand to Apricot, Slate to Ocean, Noir to Dark), so nothing resets to plain Light.
Faster dashboard, enquiries, and calendar loads. These screens now fetch their independent data in parallel instead of one query after another, so they appear sooner. Every workspace list also carries a safety limit so an unusually large account can never stall a page with an unbounded query.
Quote types renamed for clarity (and to stop echoing Studio Ninja's terms): "Fixed" is now Tailored, "Pick & Choose" is now Customisable. Updated across the new-quote picker, quote list, quote detail, client detail, and the packages copy. Display labels only; the underlying
fixed/pick_choosetype values are unchanged.Packages "Direct cost" relabelled "Your cost", with a hover tooltip explaining it is your cost to deliver (second shooter, prints, travel), and that it is not GST. Same field, clearer label.
Noir and Espresso themes unlock earlier, at more than half the Recommended setup steps (5 of 9) rather than all of them. Build-your-own stays the final, all-steps unlock.
Quote breadcrumb now shows a person icon (Client) and a flag icon (Lead) so the two same-named crumbs are distinguishable.
Fixed
Video-call reminder emails now include the meeting link. The 24-hour and 1-hour booking reminders showed only "Google Meet" with no way to join, the exact moment a client needs the link most. Reminders now show a prominent "Join the meeting" button, matching the confirmation email.
The "Invoices live at /invoices" link on the Invoicing settings page went nowhere. It now points to Money, where invoices actually live.
Three email preference toggles (tracking, new-lead notifications, daily digest) were impossible to turn off. Unticking the box saved nothing, the form read an unchecked box as "leave it on", so the off state was silently ignored. Fixed as part of the email-settings split; if you tried to turn one of these off before and it didn't stick, it will now.
Adding an enquiry now confirms it. Creating an enquiry gave no feedback, the dialog just closed and the new row showed up a moment later, so it felt like nothing happened. You now get an "Enquiry added" confirmation the instant you save.
Contracts read "Ready" everywhere now. The contract list and contract page still showed "Draft" (each kept its own copy of the status labels); both now match the rest of Compass ("Ready" for a saved, unsent contract).
Clearer wording on the quote's auto-invoice option. It now says it creates the invoice (ready for the client to pay) when the quote is accepted, instead of calling it a "draft invoice" (it was never a draft, it's live and payable).
No em dash in the auto-created contract name. A contract created from a quote was named "<Template> — <Client>" with an em dash; it now reads "<Template> for <Client>" (matching the other contract name defaults).
Deleting a contract now shows progress and takes you back to the job. Confirming a delete shows "Deleting…" on the button (it used to look like nothing happened), and afterwards you land back on the enquiry or job the contract belonged to, instead of the flat contracts list. (The progress cue now applies to every confirm-to-delete across Compass.)
The booking "Move to…" buttons now respond instantly. Clicking a status (upcoming, in production, completed, cancelled) used to give no feedback until the page refreshed; the button now shows "Moving…" immediately.
Accepting a quote now creates a live, payable invoice (not a draft). When a client accepts a quote, the invoice it generates is live straight away so they can see and pay it, instead of sitting as a draft. The resulting booking is now named after the enquiry (the couple), not the quote's label.
Clients can get back to their portal after acting, and see the signature right away. After accepting a quote, signing a contract, or submitting a questionnaire, the confirmation now has a "Back to your portal" link. And on signing a contract, the "Signature on file" detail shows immediately on the success screen instead of only after a reload.
Contract "Agreed at" time now shows your timezone. The signed-contract timestamp rendered in UTC; it now uses the studio's timezone, like the rest of the app.
Contracts and questionnaires now link back to their enquiry. The top link on a contract or questionnaire used to say "All contracts/questionnaires" and go to a page you can't otherwise reach. It now reads "Back to
" (or the client) and takes you there, the same as quotes. Booking confirmation emails now show the right time and a join link. The confirmation (and the owner's notification) showed the meeting time in UTC instead of your workspace timezone (e.g. 1:15am rather than 9:15am), now fixed in both. And for video meetings, the email now includes the "Join the meeting" link instead of just the label. (A
{{meeting_link}}variable is also now available for custom booking templates.)Public client links no longer demand a Compass login. The booking page (
/book/...), booking confirmation (/booking/...), contract e-sign (/contract/...), questionnaire (/questionnaire/...), and public studio profile (/p/...) were being bounced to a "needs an account" page for logged out visitors, so clients couldn't book, sign, or fill anything you sent them. They're now correctly public (each is still protected by its own unguessable token/id in the link, the same way quote and portal links already worked).Double-clicking a word no longer secretly makes it bold. In the email editor (signature, composer, templates, workflow steps), double-clicking or triple-clicking to select text could silently apply bold to the selection. The toolbar buttons now act on press rather than on click, so a stray click from selecting text can't trigger Bold by accident. Keyboard use of the toolbar is unchanged.
Email editor: text cursor + toolbar no longer steals your selection. The editing area now shows a proper text (I-beam) cursor, and clicking a toolbar button (Bold, etc.) keeps your highlighted text selected, so a format applies to exactly what you selected instead of the button grabbing focus first.
Bullet and numbered lists show their markers in the email editor again. In the email composer and template editors, list items had lost their bullets/numbers (the underlying list structure was there, just no visible dot or number). They render correctly again.
Waypoint/Roadmap header is frosted again, as one seamless bar. The frosted-glass blur behind the page header had silently stopped rendering (a build-time CSS minifier was dropping the standard property in Chrome/Edge, and an earlier scroll fix disabled it in Safari), so content scrolled under it as sharp text. It is back, and the switcher pill, progress bar, and step row now sit on a single continuous frosted surface with no gap or seam between them on either page.
Waypoint steps no longer creep down the page. Clicking through the steps used to scroll a little further down each time. Now the first step opens at the top and every other step opens with its "Step X of N" heading just under the header, and clicking around holds its place.
Tidier pricing pre-launch badges. The "Coming soon" tag is now uniform on the paid plans and the call-to-action buttons line up across all three cards.
No more unstyled page after leaving Waypoint. Navigating from Waypoint (or Roadmap) into the app sometimes landed on a page with no styling until you refreshed. Those two pages carry a large standalone stylesheet, and the in-app (single-page) navigation away from them occasionally failed to load the next page's styles. Leaving Waypoint/Roadmap now does a full page load, so the destination always arrives fully styled.
Mouse-wheel scrolling works on Waypoint in Chrome. The page set its horizontal-overflow clip on both the page and document elements, which made Chrome create two scroll layers and ignore the mouse wheel (the scrollbar still worked, and Safari was fine). Clipping on one element only restores normal wheel scrolling.
Help articles read better. The expandable sections inside a help article were accidentally inheriting the big card styling from the help home page, which made each collapsed heading a tall, mostly-empty box with a tiny, hard- to-spot triangle. They're now compact rows with a clear round expand button, and the first section opens by default so it's obvious the headings expand.
The phone menu gets out of the way as you scroll. On a phone, the row of menu links now collapses when you scroll down (leaving the compact bar with the logo, tools, and your avatar) and slides back when you scroll up or reach the top, so it stops eating screen space while you read. Inspired by the novoaweddings.com.au header.
The "Show all settings" bar stays glued to the top menu. Pulling the page down past the top no longer bounces and detaches the settings bar from the nav (the elastic overscroll that caused the gap is turned off).
A loading screen when you open Compass from your home screen. Launching the installed app used to show a blank (black) screen for a few seconds with no sign anything was happening (the home-screen app has no address or progress bar). It now shows a branded Compass loading frame that matches your theme and clears itself once the app is ready. iOS also now shows a brand launch image during the very first moments of a cold start (the home-screen app has no progress bar), so the screen is filled from the instant you tap the icon, before any page has loaded.
The top menu is readable on phones now. Instead of cramming every menu item into one shrinking line (which cut off labels), the wordmark, tools, and your avatar stay on the top row, and the menu links (Dashboard, Clients, Enquiries, Jobs, Calendar, Money) drop to their own row underneath at a comfortable, tappable size, wrapping onto a second or third line as needed.
Tapping a field no longer zooms the page on iPhone. Form fields now use a 16px text size on phones, which stops iOS from auto-zooming when you tap into them.
Buttons tell you they're working. Creating a new quote draft now shows "Creating…" instead of sitting silent until the page changes, and saving a payment schedule shows "Saving…" then a brief "Saved", instead of just greying out. (The currency and contract-template saves already had this.)
Quotes send you back where you started. When you begin a quote from an enquiry or a client profile, the back link and deleting the draft now return you to that enquiry or client (named on the link), instead of dropping you on the global quotes list with no way back through the nav.
Quote line items: deleting a row no longer scrambles another row's price. A quote starts with one blank line. If you added a product (so you had the blank line plus the product) and then removed the blank line, the product's price was being replaced with the blank line's 0, both on screen and when saved. Each line now keeps its own identity, so removing one never touches another. Also, adding the first item from the catalog now replaces the starting blank line instead of leaving an empty row above it.
Website enquiries no longer lose their owner notification. When two enquiries arrived at nearly the same moment, both leads were saved but only one "new enquiry" email reached you, and a failed send left no trace. The notification now runs reliably after the response (so it isn't cut short), checks whether it actually sent, retries a transient hiccup, and reports a genuine miss so it can never go silent again.
Passkey sign-in works again. The pre-login passkey endpoints were being blocked by the auth gate before they could run, so "Sign in with a passkey" failed with "Could not start passkey sign-in" in every browser even with a passkey registered. The login passkey endpoints are now reachable while signed out (the rest of the passkey routes stay protected).
Password sign-in works again. Entering your email and password and clicking Sign in wrongly reported "Password is required" and cleared the fields. The form was carrying a hidden, empty copy of the email field that masked the one you typed; it's removed, so password sign-in goes through.
Rich-text editor cursor + image-upload feedback. Three fixes: the caret no longer vanishes on Return / Backspace (the editor was re-syncing its content on every keystroke and resetting the selection); pasted or dropped images now show an "Uploading image…" hint while they upload; and an uploaded image lands where you dropped or pasted it, not wherever the caret happens to be when the upload finishes.
Email template merge fields now show email variables, not scheduler ones. The Email templates editor was reusing the booking editor's variable list (Invitee, Event time, Reschedule link). It now offers client-email fields (Client name, Partner name, Studio name, Event date, Quote link, Invoice number, Invoice link). The editor's variable list is configurable per surface.
Pasted and dropped images in the email editor. Copying an image and pasting it, or dragging an image file onto the editor, now uploads it to storage and embeds the real URL, instead of doing nothing or leaving a broken icon. Images pasted from apps like macOS Mail that arrive as unresolvable references (cid: attachment links) are stripped on paste, so the text comes through clean with no broken image placeholders. (Bulk-copying from Mail still cannot carry the image bytes themselves, those are attachment references, not real image data; use the image button or copy an image on its own to bring it across.)
Email templates can be managed again. Settings, Email had no way to view, edit, or create templates: the only control was a one-time "Add starter templates" button that hid itself the moment any template existed, so seeding left a dead end. There is now a full template manager (list, create, edit, delete) with the rich body editor (formatting, inline images, merge fields). Compass-seeded templates can be edited but not deleted.
Navigation progress bar restored. The top loading bar never showed because its "finish" step was triggered the instant it appeared (it keyed on becoming visible, not on the route changing). It now rides up on click and completes only when the new route lands.
Sentry browser noise filtered. Three benign client-side errors were cluttering the issue list with zero user impact:
AbortError: Transition was skipped(an interrupted page navigation),Failed to fetch(a Server Action torn down mid-navigation, already handled by Next's router), and transientnetwork errorblips. Added them to the existingignoreErrorslist ininstrumentation-client.tsalongside the matching Firefox/Safari wordings, so real issues stay visible. Nothing in app behaviour changed. (Sentry JAVASCRIPT-NEXTJS-4, -6, -7.)Contract-templates settings copy no longer says e-signature arrives "when the quote is sent (coming next)". Quote-send and the client signing link are live, so the copy now reads in the present tense.
Quote line-item amount fields now select on focus. The qty and unit-price inputs default to
1/0; clicking in and typing used to prepend to that default (typing3900into a0field produced39000, a silent 10x quote error). Focusing a field now selects its contents so a typed value replaces the default.Waypoint calculator no longer throws on stray document clicks. The delete-popover's document-level
clicklistener (andclosePopover, reachable from the Escape key listener) readpopover.hiddenwithout a null check, so once those listeners were attached they could crash withCannot read properties of null (reading 'hidden')on pages where the popover element is absent. Added the sameif (!pop) returnguard the file's other handlers already use. (Sentry JAVASCRIPT-NEXTJS-1, 27 occurrences.)Hydration mismatch on Settings, Calendars. The Google and Apple connection cards rendered
new Date(last_synced_at).toLocaleString()directly, so the server (UTC) and browser (local timezone) produced different "Last refreshed" text and React flagged a hydration error. The timestamp now renders in its ownsuppressHydrationWarningspan, keeping the user's local time while silencing the mismatch. (Sentry JAVASCRIPT-NEXTJS-5, 12 occurrences.)Em dash removed from quote-name placeholder. The new-quote and quote-editor name fields used "e.g. Wedding photography, full day" with an em dash, against the no-em-dash copy rule. Now a comma.
[0.3.0] — 2026-06-02
Added
- Unlockable themes (Appearance settings). A new
/settings/appearancepage with a theme gallery: Light, System and Dark are always on; Sand and Slate unlock once the Essential setup steps are done, Noir and Espresso once every step is. Locked swatches show why. Themes shift mood + surface tones only, the brand red dot stays constant. Generalised the no-flash theme script + picker to named themes (was light/dark/system only); the old toggle on Sign-in & security moved here. Ties the wizard's progress to a tangible reward. - Starter content for setup steps. Instead of a blank page, the Tax rates and Packages settings now offer a one-click starter: the common tax rate for your currency (AUD GST 10%, NZD 15%, GBP VAT 20%, ...) and a set of example packages + add-ons for your industry (photography, videography, celebrancy, florals, planning, hair & makeup, catering, with a generic fallback). Prices are placeholders to edit. Now also covers a starter contract template, a starter questionnaire, three email templates (enquiry auto-reply, quote-sent, invoice-reminder, used as send-time defaults), a payment schedule (30% deposit, balance before the date), and a follow-up workflow. Every starter is guarded (no-op if content already exists) and surfaced as a one-click button on the empty state of each settings page.
- Setup checklist celebration. Completing a step now pops a toast ("Add your packages, done") next time you open Getting started, and finishing every step fires a one-time confetti burst. Detected via a per-device snapshot of seen steps; first visit seeds silently.
- Getting-started setup checklist (first draft). A new
/getting-startedpage walks a studio through Essential steps (studio details, lead capture, packages, invoicing) and Recommended steps (profile photo, vocabulary, tax rates, payment schedules, templates, workflows, calendars, passkey, public profile), with a progress bar, tier badges, tick states, a "ready to take enquiries" marker, and a completion celebration. Each step deep-links to its settings page; completion is read from live data, so ticks never drift. A compact "Studio readiness" card on the dashboard links in and auto-hides once everything's done. (Reward unlocks, themes, the reciprocal-backlink step, and per-step toasts are the next passes.) - Error reporting is automatic, with optional detail. Every error is reported to us the moment it happens (captureException + an error-session replay), no action needed. The error page now says so, and offers a prominent "Add a note or screenshot" button (any file, not just images) to attach extra context to that same report.
Fixed
Tax-"inclusive" products no longer overcharge. Quotes and invoices added GST on top of inclusive-priced items, inflating the total (a $3,900 inclusive quote billed $4,290). The quote and invoice calcs now branch per line on the product's tax treatment via a shared helper (
src/lib/tax.ts): inclusive backs the tax out (total stays $3,900, GST shown as $354.55, subtotal-ex $3,545.45), exclusive adds on top (unchanged), none is untaxed.tax_inclusionis now carried from the catalog through the quote line items / packages / add-ons (and onto the auto-created draft invoice), so a quote and its invoice always agree, and quotes can mix inclusive and exclusive lines.Public pages no longer demand a sign-up. Clicking Pricing (or any public marketing link) while signed out wrongly popped the "that feature lives behind a free account" modal. The tease now only fires on protected CRM links; public links navigate normally.
Errors now actually reach Sentry. The dashboard error boundary only logged to the console; a render throw isn't a window error, so Sentry never captured it (issues dashboard sat empty). It now calls captureException, and the captured id is attached to any user report.
Error page recovers from stale-deploy "Failed to fetch" too. Previously only chunk errors hard-reloaded; a Server Action whose id changed in a redeploy throws "Failed to fetch" and a client re-render can't recover it. "Try again" now hard-reloads on those as well.
Empty clients no longer linger. A client with no live leads and no bookings (the ghost left behind when its only enquiry is trashed) is now hidden from every clients view. It reappears if a lead/booking attaches again (restore from Trash, or a re-enquiry).
Deleting a client can no longer silently wipe money/work. The delete cascaded to invoices, quotes, contracts and bookings with no guard. It's now blocked (with a reason) whenever a booking, quote, invoice, contract, questionnaire, or open enquiry is attached; the Delete button shows disabled + why. To delete, a live enquiry must first be marked lost or trashed. Clients whose only leads are lost/won or trashed can be deleted, and those leads' pushed calendar events are cleaned up first.
Currency (and other workspace fields) no longer snap back on Save. The business-profile form's fields were uncontrolled, so React 19's post-submit form reset reverted them to the stale prop even though the save succeeded (only visible after a reload). Made the fields controlled.
Error page recovers from stale-deploy chunk errors. "Try again" now shows a Retrying state and hard-reloads on a ChunkLoadError (a client re-render can't recover a missing chunk); the "Back to…" button is a real navigation so it works even when the client router is wedged.
Trashed leads no longer linger. Soft-deleted leads were still counted in dashboard metrics + "recent enquiries" and kept their client showing as Active. Both now exclude
deleted_at.Footer "Pipeline" → "Enquiries" (the nav rename reached the footer link).
Changed
The brand dot is now a page-title design language. The red dot from the "Compass." wordmark trails every top-level page title across the signed-in app: the dashboard greeting, the nav sections, every settings page, the feature lists (Quotes, Contracts, Questionnaires, Event bookings, New/Edit invoice...) and the detail pages (a quote, client, job, contract, questionnaire or enquiry's name). One shared
(decorative, aria-hidden). Deliberately not on card/section sub-headers, client-facing pages (portal/quote/contract links your clients see), marketing/auth pages, or error screens, so the accent keeps its weight. Settings drawer labels now match their page headings. Several settings entries read one way in the drawer and another on the page. Aligned: Business profile, Sign-in & security, Integrations, Dashboard layout, Packages, Contact forms, Calendars (heading follows the drawer), and Event types & availability + Marketing & business costs (drawer follows the heading). The enquiry-stages drawer label now follows the studio's own pipeline term (Vocabulary), matching its page heading on any wording.
Settings left the nav bar; the drawer is everywhere now. The "Settings" nav item is gone. The collapsible "Show all settings" drawer (mounted globally) hangs from the nav on every signed-in screen, so settings are one tap away from anywhere, not just from inside the settings section.
Settings drawer sits flush with the nav and collapses on scroll sooner. It hung 6px below the nav bar (stuck at 64px while the nav is 58px tall); now flush. Scroll-collapse fired only after 120px of scroll with a 1.5s post-open grace, so on short settings pages it felt stuck open and an immediate scroll did nothing. Now collapses after a 24px scroll with a 350ms grace.
/settingsopens the workspace profile; nav is the drawer. The standalone settings cards hub is gone —/settingsredirects to Business profile and the collapsible drawer is the only nav. The drawer tab reads "Show all settings" (no count).Settings navigation is now a collapsible drawer. Replaced the Sidebar/Cards toggle (and the left rail) with a single TWTM-style drawer: a sticky "Show all N settings" tab hangs below the top nav, expands into grouped pills (Personal / Sales / Customisation / Money / Connections / Workspace), has a Pin to keep it open, and auto-collapses (one-way) when you scroll down unless pinned. Open / pinned / per-group state persists per-device in localStorage. Desktop shows a 2-column group grid; mobile is a per-group accordion. (
settings-drawer.tsx.)
Security
- Closed an anon RLS exposure on
event_bookingsand pinned trigger search_path (2026-05-30). Dropped two vestigial anon policies. The public booking flow runs entirely through the service-role admin client and guards the manage page with an unguessablemanage_token, so the anon policies were unused, andevent_bookings_public_read (USING true)exposed every workspace's booking PII (name, email, phone, answers) to anyone holding the public anon key. Authenticated dashboard access is unchanged (event_bookings_select/event_bookings_modify, both workspace-scoped); RLS stays enabled. Also setsearch_path = ''on four trigger helper functions (advisor lint 0011). Applied live via the Supabase MCP, recorded in migration20260530030000_harden_event_bookings_rls_and_fn_search_path.sql.
Added
Lead sources are grouped by category. The source list now groups under Search / Social / Referral / Directory / Direct / Other headers instead of a flat wall, with a "Change my name" example at the top signalling the list is editable. The default set new workspaces get is leaner (drops the long tail like Pinterest / LinkedIn / Reddit / EasyWeddings / etc.) while keeping the paid-vs-organic split for Google, Facebook, and Instagram. Reorder now works within a category. Migration 20260531130000 adds the category column + backfills existing rows.
Lead sources are fully manageable + filterable. Settings -> Lead sources now supports rename (label only; the slug stays stable so webhook matching keeps working), reorder (up/down), and delete (guarded: refuses with an "Archive instead" nudge if any lead still uses the source), on top of the existing add/archive. The /enquiries source filter now matches on source_id, so filtering by a granular source ("Google Paid") or a custom source actually returns results instead of nothing. The legacy per-lead
leads.sourceenum column was retired (dropped in migration 20260531120000) now that source_id -> workspace_lead_sources is the sole model; the lead_source enum type stays for expenses, clients, and the inbound-matching bridge.Calendar conflict picker groups Calendars, Shared, and Reminders, and opens on connect. Settings → Calendar's per-account picker now splits calendars into sections: your own Calendars, Shared calendars (calendars others shared with you, Google only), and Reminders (Apple Reminders lists). Reminders default to OFF and only block a bookable time when the reminder has a specific time; all-day reminders are ignored. The picker now opens automatically the moment an account connects (Google via the OAuth return, iCloud via the connect action) so you choose which calendars block conflicts right away instead of hunting for a link. Every toggle autosaves and shows the standard "Saved" tick. Note: iCloud can't tell shared calendars from your own (Apple doesn't expose ownership over CalDAV), so iCloud shared calendars stay in the Calendars group. Also fixes a bug where refreshing the calendar list silently re-ticked calendars you had unticked.
Sentry Cron Monitors on all 3 scheduled routes. New
src/lib/cron-monitor.tswraps each cron with an in_progress check-in at start and ok/error check-in on exit. Schedules registered inline (0 3 * * * cleanup-trash, 0 9 * * * workflow-reminders, */15 * * * * booking-reminders). Sentry alerts when a scheduled check-in doesn't arrive within the checkinMargin, regardless of cause (Vercel didn't fire, bad secret, route threw, hung past maxRuntime). Closes the 17-day silent-failure trap that bit between 2026-05-09 and 2026-05-26. Auth failures with a Bearer attempted are also reported as error-level captureMessage events; bare unauthenticated probes stay silent.Lead source enum:
direct+email. Two new values cover attributions that the Novoa enquiry form sends as "Direct" (no referrer / direct nav) and "Email" (newsletter, personal email referral). Both previously collapsed to "Other". Migration20260530000000_lead_source_add_direct_email.sql.Per-workspace customisable lead sources. Lead source is no longer a hard-coded enum; each workspace gets its own list at Settings → Lead sources. Default seed includes the granular paid/organic split (Google Paid + Google Organic, Facebook Paid + Facebook Organic, Instagram Paid + Instagram Organic) so paid-ad attribution lands cleanly in the pipeline + reports without custom config. Users can add custom labels ("Wedshed inquiry", "Walk-in") from Settings. Inbound source strings from the webhook, hosted form, CSV import, and +Lead dialog are matched against the workspace's sources (exact slug → substring → legacy enum bridge) with "Other" as the fallback. The lead-detail edit picker now reads from the workspace source list. Migration
20260530020000_workspace_lead_sources.sqladds the table + seeds every existing workspace + backfillsleads.source_idfrom the oldleads.sourceenum. The legacy enum column stays in place during the dual-write transition; both columns get populated on every write. Service layer atsrc/lib/lead-sources.ts.Deferred to a follow-up commit: source picker swap on the +Lead dialog (it still sends the legacy enum, which gets bridged to a source_id via legacy_enum_value), source filter dropdown on the /enquiries list, edit / reorder / delete actions on the Settings page (only Add + Archive shipped tonight).
Lead and job event dates can mirror to Google Calendar. New Settings → Calendar → "Lead and job calendar" card flips on a one-way Compass → Google push. Compass creates two calendars in the user's Google account ("Compass Leads", "Compass Jobs") and writes one all-day event per lead with an event date. Title is "(LEAD)
" for active leads; on transition to Won the event moves to Compass Jobs with the "(LEAD) " prefix dropped. Updates + deletes propagate. Google-only (Apple's CalDAV API is read-only for this path). Compass uses the narrower calendar.app.createdOAuth scope so it can only touch the two calendars it created itself, never the user's personal calendars. Existing Google connections need to reconnect once to get the new scope; the toggle surfaces a reconnect nudge if a stale connection is hit.Touches: migration
20260530010000_lead_calendar_push.sql,src/lib/calendar/lead-push.ts(enable/disable/sync/cleanup),src/lib/calendar/google.ts(createCalendar, createAllDayEvent, updateAllDayEvent), and syncLead hooks across every lead write path (webhook intake, hosted form, CSV importer, +Lead dialog, board status drag, lead edit, bulk ops, soft delete, restore, hard delete, cleanup-trash cron).
Changed
parseSource()smart matching (Phase 2 of Novoa → Compass). The inbound lead source normalizer now resolves messy attribution strings to the right channel instead of falling through to "Other". Three passes: exact enum match, channel-keyword substring ("Google Paid" → google, "Facebook Organic" → facebook, "vendor_referral_xyz" → vendor_referral), then a token-level alias scan for Meta CAPI's short forms ("ig_paid_social" → instagram, "fb_organic" → facebook, bare "gads" → google). Paid-vs-organic granularity stays preserved inleads.custom_fields(utm_source + utm_medium captured by the Novoa form's Phase 1 attribution layer), so reports that need the split read from there. The enum stays clean for solo creators who don't run paid ads.
Removed
/api/sentry-checkprobe route. One-shot Sentry-activation probe. Retired 2026-05-29 now that the cron monitors send check-ins on every scheduled run (a live liveness signal that doesn't need a manually-curled endpoint). The route was also unreachable from outside sincesrc/proxy.tsnever had it on the public-route allowlist, so production curls would 401 at the proxy before reaching the route.
[0.2.0] — 2026-05-29
Added
Per-calendar conflict toggles + single bookings destination (Wave 12). The Calendar settings page now matches Calendly's model: connect an account, get a list of every calendar on it, tick which ones should block your Compass bookable slots. A single "Add new bookings to" dropdown across all your connected accounts picks the one calendar Compass writes booking events into.
New table
workspace_calendar_subscriptions(one row per individual calendar inside a connected identity, with blocks_availability + is_default_destination toggles, a partial unique index guaranteeing exactly one default per workspace). Backfill happens lazily: the Calendar settings page fetches each connected account's calendar list on first render after this deploy and persists one subscription row per calendar. No reconnect required.Updated wiring:
- external-busy.ts reads every blocking subscription rather than just the identity's primary calendar. Google freeBusy is batched across calendars in one API call via new getBusyRangesMulti.
- submitBooking writes the Google event to whichever subscription is flagged is_default_destination=true, falling back to the legacy primary for unmigrated workspaces.
- New helpers: src/lib/calendar/subscriptions.ts (refreshSubscriptionsForIdentity); listCalendars on both google.ts and apple.ts.
New Settings → Calendar pieces:
- "Checking N calendars" link on each connected identity card that opens a modal listing every calendar with a checkbox. Toggles persist instantly via server action.
- "Add new bookings to" card with a single dropdown of every writable subscription across all connected accounts.
- "Refresh calendar list" inside the modal pulls fresh data from the provider so newly-added shared calendars show up without disconnect/reconnect.
Migration: 20260528000000_workspace_calendar_subscriptions.sql.
Calendar Month and Week views. /calendar now has a Year / Month / Week view switcher with prev / today / next navigation. Year stays the default. Month renders a 6-row x 7-day grid with up to 3 entries per cell; Week renders a 7-day hour grid (12am to 11pm) with an all-day band, positioning event_bookings by their scheduled_at and duration_minutes. View and anchor date are URL-driven (?view=, ?date=YYYY-MM-DD) so the page stays a Server Component and links are shareable. Week start day and weekday labels come from the workspace calendar_settings.
Previously undocumented
Backfilled 2026-05-29 from a verify-the-code pass. These items
shipped to production before 0.2.0 was cut but never made it into
[Unreleased] (one of the failure modes the AGENTS.md release-cut
hygiene rule now prevents). Verification source noted on each line.
- Two-level Waypoint industry picker. The flat list of 9
industries became 4 group cards (Weddings & Events, Fitness &
Health, Beauty & Aesthetics, Design & Creative) that drill into
per-group job-type cards. Returning users with an active
industry land at the GROUP level, not the type level. Verified
in
public/waypoint/calculator.js(INDUSTRY_GROUPS at line 761,_pickerLevelstate machine). - Five new Waypoint industries: Marriage Celebrant (celebrant), Yoga & Pilates instructor (yoga), Makeup Artist (mua), Hair Stylist (hair), Nail Artist (nails). Each seeded with its own Business Costs and Job Costs defaults. Verified at calculator.js lines 535, 582, 625, 671, 716.
- "Type 'I understand' to continue" gate on the industry
switcher. When switching active industry on a workspace that
already has non-zero values, the user must type "I understand"
(case-insensitive) before the Switch button enables, then
Business Costs / Job Costs reseed with the new industry's
defaults. Life & Admin, goals and roadmap are untouched.
Same-industry repicks with empty values skip the gate.
Verified in
src/app/waypoint/calculator-body.html:790-791andpublic/waypoint/calculator.js:3636. - "Return to pipeline" unhold action on lead cards. Lead cards
in the on-hold lane expose a "Return to pipeline" button that
clears the hold and restores the lead to its original stage.
Verified in
src/app/(dashboard)/enquiries/board/lead-card.tsx:137-139. - Custom-fields editor overflow fix on lead detail. Each row's
CSS grid switched from
grid-cols-[1fr_2fr_auto]togrid-cols-[minmax(0,1fr)_minmax(0,2fr)_auto], so long field names and values stay inside the column instead of pushing the card wider than the right-hand stack. Verified insrc/app/(dashboard)/enquiries/[id]/custom-fields.tsx:92, diff in commit 3436ac8.
Removed
- /invoices route family retired. Phase 3B completed the move: /invoices/[id], /invoices/[id]/edit, /invoices/new, and the /invoices listing all gone. The shared detail / edit / new view components live under src/app/(dashboard)/_invoice-views/ and the thin route wrappers at /jobs/[id]/invoices/* and /clients/[id]/invoices/* are the only entry points. The legacy redirects from /invoices/* (added in Phase 3) were also dropped since the app is pre-public-launch and external links to old paths don't exist yet.
Changed
New + Edit invoice flows now live under their parent. Two canonical "new invoice" URLs: /jobs/[id]/invoices/new (creates an invoice tied to a job) /clients/[id]/invoices/new (creates a standalone invoice) Same shape for edit: /jobs/[id]/invoices/[invoiceId]/edit /clients/[id]/invoices/[invoiceId]/edit All four routes render the same shared form component (_invoice-views/new-view.tsx or edit-view.tsx) and propagate breadcrumbs from the parent. The "Edit" button on the invoice detail page now receives its canonical edit URL via prop rather than building one itself, so the action bar doesn't need to know about the route structure.
/invoices server actions redirect to canonical URLs. Create, update, and the lifecycle actions (mark sent / mark paid) now use the invoiceViewPath helper after looking up the invoice's parent ids. Delete redirects to /money. revalidatePath calls target the canonical URLs too, so cache invalidation works on the new routes.
Invoices moved under their canonical parent. An invoice's URL now reflects the relationship to its job (or its client when standalone). Canonical URLs: /jobs/[jobId]/invoices/[invoiceId] (job-attached) /clients/[clientId]/invoices/[invoiceId] (standalone) The legacy /invoices/[id] URL still resolves: it now permanently redirects to the canonical path after a DB lookup, so sent invoice emails and bookmarks keep working forever. The /invoices listing page also redirects (to /money, which replaced it). The invoice detail itself extracted into a shared server component (src/app/(dashboard)/_invoice-views/detail-view.tsx) so both canonical routes render from one source. A breadcrumb above each invoice spells out Dashboard > Clients > [Client] > Jobs > [Job]
Invoices > Invoice #X (or the shorter chain without Jobs for standalone invoices). Edit + new invoice flows still live at the legacy /invoices/[id]/edit and /invoices/new URLs; moving those is a follow-up commit.
Added
/components/breadcrumbs.tsx — small generic Breadcrumbs row. Last item rendered as plain text (current page); earlier items link when an href is supplied.
/lib/invoice-urls.ts — invoiceViewPath / invoiceEditPath / invoiceNewPath helpers. Every internal invoice link should go through these so the next URL change touches one file rather than twenty.
Changed
- Top nav restructured around 7 essential items. Old nav (Dashboard, Pipeline, Leads, Clients, Calendar, Invoices, Settings) replaced by Dashboard, Clients, Enquiries, Jobs, Calendar, Money, Settings. Quotes, Contracts, Questionnaires and individual invoices are now reachable from their parent Client / Job pages rather than crowding the top bar. Lead list + pipeline board merged behind a single "Enquiries" item with List / Board tabs at /enquiries and /enquiries/board (the original /leads and /pipeline routes were renamed and redirect). /bookings renamed to /jobs (booking detail pages now live at /jobs/[id]).
Added
/jobs landing page. Lists every confirmed Booking for the workspace with name, client, event date, status, and total value. Replaces the previous "/bookings had no list view" gap.
/money landing page. A single payments tracker over every invoice, filterable by status (All / Unpaid / Paid / Draft) and date range (This month, last month, this/last quarter, this/last financial year). Summary cards at the top show count + unpaid + paid totals for the current filter. Due-date filtering and paid-on date filtering will come once invoice schema gets due_on / paid_at columns.
Changed
Settings rework, phase 1. Final shape of the rework that landed in the previous commit: 6 themed groups (Personal, Sales, Customisation, Money, Connections, Workspace), URL slugs renamed to match labels, icon next to every item in the sidebar, and a new cards-grid landing page at /settings. URLs renamed: /settings/account -> /settings/sign-in-security, /settings/workspace -> /settings/business-profile, /settings/pipeline -> /settings/enquiry-stages, /settings/event-types -> /settings/booking-pages, /settings/costs -> /settings/expenses, /settings/dashboard -> /settings/dashboard-layout, /settings/calendar -> /settings/calendars. All seven old URLs redirect permanently to their new paths via next.config.ts so existing bookmarks keep working. Label changes since the previous cut: Pipeline -> Enquiry stages, Lead capture -> Contact forms, Wording -> Vocabulary, Calendar -> Calendars. Brand group merged into Personal (Business profile moved). Client-facing group retired (Packages + Templates moved to Customisation).
Settings reorganised into seven themed groups. The 22-item flat sidebar at
/settingsis now grouped under Personal, Brand, Sales, Client-facing, Money, Connections, and Workspace headings. A search box at the top of the sidebar filters items as you type (matches against labels and synonyms, so "vocabulary", "contact forms", "costs", etc. still find the renamed items). Renames: Account -> Sign-in & security, Workspace -> Business profile, Vocabulary -> Wording, Contact Forms -> Lead capture, Online booking -> Booking pages, Products & packages -> Packages, Dashboard -> Dashboard layout, Costs -> Expenses.Templates pages tab-grouped. Contracts, questionnaires, and email-template settings now share a tab bar at the top so they feel like one "Templates" surface. Same treatment for Invoicing, which now hosts Tax rates and Payment schedules as tabs.
Added
- WebAuthn conditional UI on the login form. Registered passkeys
now surface as autocomplete suggestions directly inside the email
field on
/login, alongside saved emails. Tapping the suggestion triggers biometric verification and signs the user in with zero modal prompts. The explicit "Sign in with a passkey" button stays as a fallback for discoverability and for users without a saved passkey. RequiresPublicKeyCredential.isConditionalMediationAvailable(Chrome 108+, Safari 16+, recent Firefox); gracefully no-ops on older browsers.
Changed
- Login rebuilt around passkey + magic link + 2FA gate. The login
page now opens with "Sign in with a passkey" as the primary CTA,
followed by an email field with "Send sign-in link" and a small
"Sign in with password instead" link. The old email > OTP code >
password three-step flow has been removed. Magic-link clicks land
on
/auth/confirmas before, and the new/login/totpchallenge page enforces a second factor for any user who has TOTP enrolled (whether they signed in via magic link or password). Passkey sign-ins satisfy AAL2 on their own and skip the TOTP prompt.
Security
- Two-factor enforcement at the proxy. Added an AAL2 gate to
src/proxy.ts: any signed-in user with TOTP enrolled must present a valid AAL2 cookie before reaching protected routes. The cookie is HMAC-signed (cannot be forged), expires after 12 hours, and is cleared on sign-out.user.app_metadata.has_totpis now written on enrollment/disable so the gate runs from JWT data with no DB hit; users enrolled before this change fall back to a one-time DB lookup. Closes the gap where the previous login flow silently bypassed 2FA after magic-link verification.
Added
Google Calendar sync (two-way). Hosts can now connect their Google Calendar at Settings > Calendar. Once connected, busy events from Google block availability on every event-type booking page, so a client can't book a slot when the host has a shoot or personal appointment already scheduled. Bookings made via Compass also write back to Google Calendar as events. Connection uses OAuth (the host clicks Allow on Google's consent screen, never types a password). Refresh tokens are AES-256-GCM-encrypted with the
CALENDAR_ENCRYPTION_KEYenv var before being stored. Status card surfaces the connected email, last-sync timestamp, and a Disconnect button.iCloud Calendar sync (read-only via CalDAV). Hosts using Apple Calendar can connect by entering their Apple ID + an app-specific password (Apple does not offer OAuth for CalDAV). Busy times block availability on event-type booking pages, but Compass does not write back to iCloud (Apple's CalDAV is read-friendly but write-unreliable for our use case). Credential encrypted at rest with the same
CALENDAR_ENCRYPTION_KEY.Per-event-type locations. Each event type can now offer multiple meeting locations (phone, video, in-person address, free-form other). The invitee picks one at booking time. Replaces the single per-event-type location field. Existing event types auto-migrate their old location into a single-item list on first load.
Per-event-type custom questions. Hosts can add structured questions to any event type (short text, long text, single select, multi select, checkbox). Invitees answer at booking time. Answers travel with the booking and surface on the /event-bookings admin page and inside the auto-spawned lead.
Per-event-type email overrides. Each event type can override the confirmation email subject and body with a TipTap rich-text editor. Falls back to the workspace default when no override is active. Schema also supports cancellation, reminder, and follow-up template kinds (UI for those ships separately).
Per-event-type reminder toggles + cancellation policy. Each event type now has 24h reminder + 1h reminder boolean switches and a free-form cancellation policy textarea that renders as a subdued footer in every transactional email this type sends. Note: the reminder cron is not currently scheduled (Vercel Hobby limit on sub-daily crons); the route is wired and a TODO is filed to add an external scheduler. Toggles + policy still ship and apply when the cron eventually runs.
Public profile page at /p/{handle}. Hosts can claim a handle (3-40 chars, [a-z0-9-]) and an optional welcome blurb at Settings > Workspace. The public page lists every active event type with one-click booking links. Calendly-equivalent of calendly.com/{username}.
Timezone-aware booking slot computation. Slot picker now honours the workspace's IANA timezone (e.g. Australia/Perth) end-to-end: wall-clock times in availability settings, slot generation, busy-time merging from connected calendars, and rendered labels on the booking page. Replaces the previous UTC-anchored math.
Changed
Database types extended for Wave 11 tables. Hand-maintained
src/lib/database.types.tsnow includesEventTypeLocation,EventTypeQuestion,EventTypeEmailTemplate,EventBookingAnswers,WorkspaceCalendarIdentity, plus thepublic_handle/public_blurbcolumns onWorkspaceand the newselected_location_id,answers,reminder_*_sent_at,external_event_idcolumns onEventBooking.Default placeholder text de-personalised. Form placeholders that referenced the maintainer's family names (Margarita Novoa, Javiera Hair Styling, Gonzalo Novoa Photography) replaced with neutral studio names (Aurora Lane Photography, Alex Morgan, River Stone Celebrant) across invoices, onboarding, email settings, and invoicing.
Tax-rate row layout tightened. Grid column widths adjusted so the "Default for new quotes & invoices" label sits on one line at narrower viewports.
Removed
- Vercel-scheduled
booking-reminderscron. Removed fromvercel.jsonbecause Vercel Hobby limits crons to daily cadence (the reminder cron needs every-15-minutes to land 1h reminders within their window). Route atsrc/app/api/cron/booking-reminders/route.tsstill works; an external scheduler (GitHub Actions recommended) will poke it. Tracked as TODO 19.4.c.
Security
- AES-256-GCM encryption for calendar credentials at rest.
Both Google refresh tokens and Apple app-specific passwords are
wrapped with a 32-byte key from the
CALENDAR_ENCRYPTION_KEYenv var before being stored inworkspace_calendar_identities.secret_ciphertext. The database never sees plaintext credentials. Decryption happens at use-time only.
Added
One-tap passkey sign-in (discoverable credentials). The "Sign in with a passkey" button now works without typing an email first. The browser shows whichever passkeys are stored for the site and the user picks one with their biometric. The server resolves the user from the returned credential id. The email-targeted path still works when an email is filled in, which keeps the helpful "no passkey for this account" message for first-time users.
Clients can reschedule + cancel their own event bookings. Confirmation email now ends with a "Reschedule or cancel" button pointing at a private
/booking/[id]?token=…page. From there the client can pick a fresh slot (same picker as the original booking flow, with the current booking excluded from clash detection) or cancel with an optional reason. Both paths email the studio owner + send the client a confirmation. Token is 32 bytes of randomness stored atevent_bookings.manage_token, validated in constant-time on every request. Migration:20260521000600_event_booking_manage_token.sql./event-bookings admin list. Dedicated page for every online booking the workspace has received. Upcoming / Past / Cancelled / All filter chips with counts. Each row shows the client name, start time, duration, email, phone, and any notes. Status changers (Mark completed / no-show / cancel / re-schedule), an "Open lead" link to the auto-spawned lead, and a delete button that keeps the lead intact.
Online bookings now show on the calendar. When a prospect picks a chemistry-call slot via
/book/[event_type_id], the resultingevent_bookinglands on/calendaralongside bookings and leads. Status label shows the start time and duration; click-through lands on the auto-spawned lead. Past event bookings roll into the past list as expected.Wave 10 Online Booking — public slot picker. SN-style scheduling. Three new tables (
workspace_event_types,workspace_availability,event_bookings, migration20260521000500_online_booking.sql). Settings, Online booking lets the user define event types (Chemistry call, Consultation with duration, buffer, advance-notice, max-per-day, phone / video / in-person location) and a weekly availability grid (Mon-Sun, up to 6 slots per day). Each event type ships with its own public URL at/book/[event_type_id]; a prospect picks a date, sees free slots computed live from availability + existing bookings + buffers, leaves their contact info, and the submit creates both anevent_bookingrow AND a new Lead in the workspace. Confirmation email fires to the client; notification email fires to the studio owner. Slot-conflict re-check on submit guards against races. Phase 2 (timezone resolution, Google Calendar sync, reschedule links) sits in the backlog.Wave 9 Phase 2.1: daily workflow digest email. New cron at
/api/cron/workflow-remindersruns once a day (09:00 UTC, wired up invercel.json) and emails each workspace owner a digest of every overdue and due-today workflow step across all their leads. Reminders only — never auto-ticks; you stay in control. Per-workspace opt-out lives in Settings, Email under "Daily workflow digest" (default on). Email body lists the steps with overdue / today badges and a one-click "Open dashboard" link; each row deep-links to its lead."Today's actions" dashboard widget. New section at the top of
/dashboardthat pulls together everything needing attention into a single punch list. Surfaces overdue + due-today workflow steps (anchored to each lead's real dates), sent contracts unsigned for 3+ days, sent quotes awaiting response for 5+ days, sent questionnaires unfilled for 5+ days, and sent invoices unpaid for 7+ days. Sorted most-urgent first; each row links straight to its surface. Shows "You're all clear" empty state when nothing's flagged.Sample data now seeds a contract / questionnaire / workflow. Clicking "Add sample data" on the dashboard now also drops in one starter contract template ("Main wedding contract" with placeholder substitution baked in), one questionnaire ("Wedding day details", 8 questions across every field type), and one workflow ("Wedding workflow", 9 steps with realistic anchor + offset combinations). Idempotent: re-seeding wipes prior seeded templates by
[seed_sample]marker without touching anything the user authored.Workflow step badges: "Due today", "Overdue 3 days", "In 12 days". Each unchecked step on a lead's workflow card now shows a badge computed from its anchor + offset_days against the real lead / quote / event dates. Overdue badges paint in the warning red; "today" picks up the new accent; "soon" (within 3 days) goes amber; future steps stay quiet. Helpers live in
src/lib/workflow-dates.tsso other surfaces can reuse them. Still fully manual — Phase 2.1 cron + email reminders is the next step once you've seen the visual cue.Client Portal: contracts to sign + questionnaires to fill. The
/portal/[token]page now surfaces two more sections so a client can find everything from a single URL: "Contracts to sign" (deep-links to/contract/[token]) and "Questionnaires to fill" (deep-links to/questionnaire/[token]). Signed contracts and completed questionnaires roll into the history collapse with their submission/signing date.Wave 9 Workflows, Phase 1: templated lead checklists. Build repeatable step-by-step checklists in Settings, Workflows. Each step carries a label, an optional notes line, and a "days after" marker tied to an anchor (lead created, quote sent, quote accepted, before event date, after previous step). Attach a workflow to a lead from the lead detail's new Workflow card and tick steps off as you go; ticking the last one stamps a Complete badge. Editor supports reorder, duplicate prevention, and an "is default" flag for the workspace's go-to workflow. Snapshot model: editing a template later doesn't retroactively rewrite running instances. Migration:
20260521000400_workflows_phase_1.sql. Phase 2 (cron-driven auto-completion / auto-email at the trigger date) is the next iteration; today everything is manual progression.Wave 8 Questionnaires. Whole loop in one go. New tables
workspace_questionnaire_templates(reusable forms with a fields builder for text / textarea / date / pick-one / pick-many / yes-no question types) andquestionnaires(per-client instances with a responses jsonb). Settings, Questionnaire templates page lets the user build forms with an inline drag-free field editor. Lead detail now has a Questionnaires card with "+ New questionnaire" template-picker. Each questionnaire has a /questionnaires/[id] dashboard page (responses view + send-by-email + delete) and a public /questionnaire/[token] surface where the client fills it out. Submissions flip status to Completed and the responses surface back on the dashboard. Brevo-powered email delivery via the existing wrapper. Top-level "Questionnaires" link in the SiteNav. Migration:20260521000300_questionnaires.sql.Date-formatted invoice numbers (optional). Settings, Invoicing now has a "Number format" radio: Sequential (0001, 0002, ...) or Date-based (20260521, 20260521-02 for same-day duplicates). Switching applies to future invoices only; existing rows keep their original number. New
formatInvoiceNumber()helper renders either format consistently across the list page, detail page, edit page, lead profile, client portal, and emails. Studio Ninja defaults to the date-based pattern; many studios use it because it doubles as a record-keeping date stamp.Calendar settings now thread through to display. Workspace's
calendar_settings.date_format(DD/MM/YY, MM/DD/YY, YYYY-MM-DD),time_format(12h, 24h), andweek_start(monday, sunday, saturday) get respected on the calendar's past-events list. New shared helpers insrc/lib/calendar-format.ts(formatWorkspaceDate,formatWorkspaceTime,formatWorkspaceDateTime,weekStartDay,workspaceWeekdayLabels) so any future surface that needs a workspace-aware date can reach for the same function.Tax rate picker on quotes and invoices. New nullable
tax_rate_idcolumn on bothquotesandinvoices(migration20260521000200_tax_rate_id_on_quotes_invoices.sql) lets a single quote or invoice override the workspace-level GST flag with any named workspace tax rate (GST, VAT, exempt, etc). Quote editor and invoice new + edit forms now show a "Tax rate" dropdown when the workspace has tax rates set up. Quote editor live-totals update against the picked rate. Display labels switch from "GST (10%)" to the rate's actual name + percentage. NULL falls back to the existing workspace.invoice_settings.gst_registered behaviour, so existing data renders identically.Wave 7 Contracts, Phase 3: public signing surface + email. Contracts are now a complete loop. New public page at
/contract/[token]renders the contract body in serif type with a typed-name signature form at the bottom; on submit, Compass captures the typed name, the client's IP (fromx-forwarded-for), the user-agent, and the timestamp into thesignature_datajsonb column. Decline button does the same but sets status todeclined. Bothsignedanddeclinedend-states render their own confirmation panel so the client knows they're done. "Send by email" button on the contract detail page mints the public token (if missing), sends a "contract ready to sign" email through Brevo, and flips status to Sent. Sending a quote with an attached contract template now auto-spawns the contract instance for the client and fires the signing-link email alongside the quote email. Re-send is idempotent (re-uses the existing token). Added anon RLS policies on contracts so the public page can read- update by token without service-role for defense in depth
(migration
20260521000100_contracts_phase_3_public_policy.sql).
- update by token without service-role for defense in depth
(migration
Wave 7 Contracts, Phase 2: instances + surfaces. Contracts are now real first-class entities in the app. Top-level
/contractslist with Open/All/Signed/Past filter chips, per-contract detail page at/contracts/[id]with edit modal, status changers (manual overrides for paper-signed flows), and linked-entity rail. New "+ New contract" floating dialog (template picker + custom name + notes) on the lead detail page Contracts card, plus a Contracts section on the booking detail page. Quote editor now has an "Attach a contract" dropdown that references workspace contract templates. Phase 3 will wire the public signing surface at/contract/[token].Wave 7 Contracts, Phase 1: template editor. New
workspace_contract_templatesandcontractstables (see migration20260521000000_contracts_phase_1.sql). Settings → Contract Templates page with full CRUD: list, create, edit, archive, restore, delete. Templates support placeholders ({{client_name}},{{event_date}},{{event_location}},{{total_value}},{{studio_name}},{{contract_date}}) that get filled in when attached to a client. New-template flow ships with a starter wedding-photography body so the user has a working example to edit instead of staring at a blank textarea. Thequotestable also gained a nullablecontract_template_idso a quote can carry an attached contract; the create/edit wiring ships in Phase 2.Send invoice by email. New "Send by email" CTA on the invoice detail page. Renders the full invoice inline (line items with discounts, GST, tip, total, bank-transfer instructions) so the client reads it directly in their inbox without clicking through anywhere. Status flips to Sent on click. Reuses the existing Brevo wrapper that already powers quote-send and lead emails. Disabled when the invoice has no client email on file (tooltip explains how to fix it). If Brevo isn't configured the action still flips status and explains "send manually from your inbox" so the workspace's records stay accurate.
Fixed
- Sample-data seeder redirected to the retired
/pipelineroute.seedSampleData()insrc/app/(dashboard)/dashboard/sample-data-action.tsredirected to/pipeline?seeded=1after inserting the five sample leads. The nav rework retired/pipeline(enquiries pipeline now lives at/enquiries/board), so the user landed on the 404 "Off the map" page and never saw the "Five sample leads added" toast. Now redirects to/enquiries/board?seeded=1. Toast watcher already allow-listed/enquiries/boardfor theseededflag, so the success toast fires at the new path. Same class of bug as delete-lead (commit 09b7b09, 2026-05-30). Verified by file read (toast watcher pathnames + new redirect target), not by browser repro. - Delete-lead redirected to the retired
/pipelineroute.deleteLead()insrc/app/(dashboard)/enquiries/[id]/actions.tsredirected to/pipeline?deleted=1after soft-deleting. The nav rework retired/pipeline(enquiries pipeline now lives at/enquiries/board), so the user landed on the 404 "Off the map" page and never saw the "moved to Trash" toast. Now redirects to/enquiries/board?deleted=1. Toast watcher already allow-listed/enquiries/boardfor thedeletedflag, so the success toast fires at the new path. Verified by file read (toast watcher pathnames + new redirect target), not by browser repro. - Settings layout went stale across client-side navigation.
After landing on
/settingsand then clicking into an inner page (e.g. Profile), the sidebar and Cards/Sidebar toggle would silently disappear because the layout'sheaders()-based "which chrome do I render" decision was cached from the first visit. The Settings tree is now split into two route groups —(landing)for the cards hub and(inner)for every other page — so the right chrome mounts for the right route every time, no stale state possible. URLs are unchanged. - Phantom-error after invoice create. The user could see a
"Something snapped" page even though the invoice had been saved.
Root cause: post-insert chores (bumping client activity timestamp,
cache revalidation) ran unguarded, so any transient throw between
the successful insert and the redirect bubbled up to the error
boundary. Best-effort try/catch now wraps the post-insert work
and logs failures to Sentry without making the user re-submit.
Same hardening applied to
updateInvoice.
Fixed
- iCloud-synced passkeys no longer break on repeat use. Apple synced passkeys always return signCount = 0, which can collide with our replay-detection counter when the user also has a hardware authenticator. The verify route now preserves the highest counter we have ever seen for a credential, so a YubiKey asserting after an Apple passkey keeps benefiting from replay detection while the synced passkey keeps working.
Changed
Palette revision: red is now an accent, not just decoration. Reassigned
--accentfrom ink to the brand red#e8423a, and--accent-softfrom a cream wash to a warm-red washrgba(232, 66, 58, 0.08). Every "highlight" surface across the app (status pills, callout panels, active-state cards, settings card chrome) picks this up without per-component edits. Primary CTAs stay on ink so the surface stays calm. Focus rings already used the brand red, so they're unchanged. Added--accent-ringtoken for any future ring/glow styling that wants the warm red.New app icon: hand-painted bearing star. Replaced the C-arc glyph with a north-pointing bearing star on cream paper, framed by a single letterpress ring. Four cardinal points (warm red north, ink east / south / west) sit over four faint diagonal kites for chart-like depth on close inspection. Source SVG includes a paper-grain filter and an ink-edge irregularity filter; both render in the browser favicon (
icon.svg) but are dropped from the iOS apple-touch and PWA-manifest PNGs because Satori doesn't supportfeTurbulence/feDisplacementMap. Updated the inline mark in the PWA install prompt too so the "Add to home screen" banner shows the new glyph.
Added
- "Add to home screen" floating prompt. Small banner that
appears at the bottom of the dashboard on iOS Safari and Android
Chrome, modelled after the PickyEater Pal style. iOS gets the
manual "Tap [share icon] then Add to Home Screen" instruction;
Android with
beforeinstallpromptgets a one-tap Install button. Self-hides in dev, on desktop, in standalone mode (the app is already installed), and for 14 days after the user dismisses it. PWA manifest + icons were already wired; this is the discoverability piece.
Changed
Error screen looks like Compass again. The "Something snapped" page was rendering in the wrong font stack because the global-error boundary replaces the whole document, which means the next/font CSS from the root layout never loaded. Now imports Fraunces + Inter directly, bumps the brand mark to 64px serif and the headline to 32px serif, and matches the dashboard error page's heading size (text-3xl/4xl). Also removed two em dashes from the dashboard error copy.
Confirmation dialogs are now in-app. The native browser
confirm()prompt (the chunky one with system Cancel / OK buttons) is gone. Replaced with a native<dialog>styled to match the rest of Compass: serif title, body copy, Cancel + Confirm buttons with a "danger" tone for destructive actions. Touched every destructive flow: delete client, delete booking, delete lead, delete forever (Trash), delete draft quote, send quote, delete invoice, revoke portal link, revoke API key, and the public-facing decline-quote button. Two reusable components:<ConfirmSubmitForm>for form-action flows and<ConfirmButton>for in-clientuseTransitionflows.
Fixed
- Em dashes removed from destructive-action copy. Two user-facing strings on the client detail page had stray em dashes (delete-client warning + the "No invoices yet" empty state). Replaced with commas per the project copy rule.
Added
Per-workspace logo on invoices (URL-based). New "Logo URL" field on Settings, Invoicing. Paste a link to any public image (PNG / SVG / JPG); it renders top-right of every invoice header above the business name. Stored on the existing
invoice_settingsjsonb, so no migration. Uses a plain<img>(not Next/Image) so the user can host on any CDN without whitelisting domains. Full file-upload pipeline is a follow-up.Per-line discount on invoice line items. Each line carries an optional
discount_pct(0..100) applied before GST. New "Disc %" column on the line editor; the invoice detail view renders "Discount NN% off" beneath the description when set. No schema migration (line_items is jsonb). Matches Studio Ninja's per-line discount behaviour for seasonal-sale studios.Invoice polish batch (five SN-gap fillers).
- Edit existing invoice. New
/invoices/[id]/editpage. The invoice number + status + sent_at + paid_at are owned by the lifecycle actions and stay untouched; everything else (client, dates, lines, tip, notes) is editable. Closes "fix a typo without delete + recreate" workflow. - Service date separate from issue date. New
invoices.service_datenullable column. Surfaced on both the new + edit forms; rendered on the invoice header beneath the issue date when different. Used for tax records where income is recognised on the service date. - Multi-line descriptions on line items. Description input
swapped from
<input>to a resizable<textarea>, rendered withwhitespace-pre-lineso the user can list inclusions inside one line item (matches SN). - Big total above the fold. Total amount now sits in the invoice header at 3xl–4xl, alongside the invoice number. The most important number on the page is no longer buried under the line table.
- "+ New invoice" on lead + booking pages. Lead detail now
shows an Invoices card (when status is won or invoices exist)
with a one-click "+ New invoice" button. Booking detail's
button now passes
booking_idso the new-invoice form prefills the service date from the booking's event_date.
- Edit existing invoice. New
/clientsfilter chips. Four chips above the rolodex: Active (open leads or upcoming / in_production bookings), All (everyone), Booked (has any booking), Past (all bookings done or cancelled, or stalled stragglers with no leads or bookings). Default view is Active so the first paint shows the relationships actually demanding attention; the full rolodex stays one click away. Counts shown next to each chip.Booking detail page at
/bookings/[id]. Bookings now have their own home (instead of routing the user back to the source lead). Shows the booking name, status, event date + location, notes, invoices on this booking with totals (paid of invoiced), and a right rail linking to the parent Client, source Lead, and source Quote. One-click status changers expose the three most-likely next transitions (upcoming, in_production, completed, cancelled). Edit modal mirrors the same native<dialog>pattern as client + lead edit. Calendar entries, client profile bookings list, and lead profile bookings card all now link here.Backlog batch: three small SN-gap fillers.
- Tipping line on invoices. New
invoices.tipcolumn. The new-invoice form has an optional Tip input; the invoice detail page renders a "Tip" row beneath GST when non-zero; the rendered total carries the tip on top. SN's longest-standing unaddressed feature request (Capterra Jun 2019). - Custom submit-button label on hosted contact forms. New
contact_forms.submit_labelcolumn. Surface on the Build tab with examples ("Request a quote", "Get in touch", "Tell us about your day"). Null falls back to "Send enquiry" in the public renderer. Was hard-coded before. - Webhook double-email opt-out. New
workspaces.email_settings.notify_on_intaketoggle, default true. When off, the webhook + hosted-form-submit paths skip the owner notification email. Surfaced on Settings → Email. Fixes the scenario where a studio's website already sends its own notification and Compass would otherwise double-up.
- Tipping line on invoices. New
Fixed
- Phase C build was failing on Turbopack.
src/app/(dashboard)/quotes/actions.tsended withexport type { Quote }— same rule that bit us with the earlier*_INITIAL_STATEconsts, a'use server'file can only exportasync functions. Turbopack rejected the type re-export and the whole Phase C deploy went toERROR(which is why the live invoice page kept showing "Something snapped" against the older deploy). Removed the re-export; callers importQuotedirectly from@/lib/database.types. - GDPR account-export route was querying the dropped
jobstable. Wave 4 retiredpublic.jobs; the export at/api/account/exportwas still pulling from it and would have 500'd the first time anyone requested their data dump. Swapped to the Path B equivalents: clients, leads, quotes, bookings, invoices, plus the existing pipeline_stages and lead_activities. Now the export is a complete snapshot of everything the customer has under their workspace.
Added
- Wave 4: calendar reads from bookings + leads-with-dates. /calendar's monthly grid now unions Path B's bookings table with the legacy lead-with-event_date rows. Bookings render in bold (with their own status colours — upcoming green, in-production amber, completed grey); unbooked leads-with-proposed-dates render in regular weight with the lead's status colour. A booking on a lead always wins over the same lead's date so accepted-quote rows display once. Lost / cancelled rows are still excluded. Past view (collapsible at the bottom) unions both sources the same way.
- Wave 4: Settings → Calendar. New sub-page at /settings/calendar.
Per-workspace date format (DD/MM/YY, MM/DD/YY, ISO), time format
(12h / 24h), and week-start (Monday / Sunday / Saturday). Stored
in
workspaces.calendar_settingsjsonb. Powered formatters get threaded through every date display in a follow-up pass; the schema is in place now. - Wave 5: unified Client Portal. New public surface at
/portal/[token] showing one client every open quote, invoice, and
booking they have, plus a "history" disclosure with accepted /
paid / past items. Branded with the studio name, no app chrome,
no auth — the token in the URL is the access boundary (matching
the /quote/[token] pattern). On the dashboard's Client profile
there's a new "Client portal" card with a Generate / Copy /
Rotate / Revoke surface. Token is generated lazily on first
Generate click and lives in
clients.portal_token(unique partial index). Fixes SN's biggest UX complaint: returning clients no longer juggle a different URL per job.
Removed
- Legacy
jobstable dropped. Vestigial from an earlier session, never wired into the UI, empty in production (verified before drop). Path B'sbookingstable is the canonical accepted-job surface from here on. Drop is covered by20260519000500_path_b_wave_4_5_settings.sql.
Added
- Settings extensions for Quote-builder ergonomics (Path B Wave 3,
Phase C). UIs to populate the Wave 3a tables that the quote
flow relies on.
- Settings → Products & packages now surfaces description, kind (package vs add-on), tax treatment (inclusive / exclusive / none), image URL, "recommended" flag (highlighted on Pick & Choose), and "mandatory" flag (client cannot deselect). The list splits into two cards: Packages and Add-ons. Settings sidebar label renamed to "Products & packages".
- Quote editor: From-catalog picker. Each list (line items, packages, add-ons) gains a "From catalog" dropdown next to its "+ Add" button. Picking a row drops it into the quote with name + description + price + flags pre-filled from the workspace catalog. Line items on a Fixed quote can pull from any catalog row (the description gets a "Package name — description" tag).
- Settings → Tax rates. New page. Define every tax rate the studio charges (GST 10%, GST 0%, sales tax, VAT, etc.). One rate per workspace can be marked default; setting a new default auto-unsets the old one. Rates are decimal-stored, percent-displayed. Archive instead of delete so historical references stay valid.
- Settings → Payment schedules. New page. SN-style reusable deposit-and-balance patterns. Each schedule has a name, applies to either Quotes & Invoices or Online Booking, and contains N payments. Each payment is a fixed amount, a percent of order, or a percent of the remaining balance, due N days / weeks / months relative to "after quote accepted", "on invoice created", "before main shoot date", or "on date of your choice". Edit the whole payment array inline; archive instead of delete.
- Settings → Email. New page. Per-workspace sender name,
reply-to email, multi-line signature, BCC-self toggle, opens +
clicks tracking toggle. Stored in
workspaces.email_settingsjsonb. The signature field is already consumed bysendQuote; sender-name / reply-to / BCC will flow through to subsequent send paths (invoice sends, contact-form autoresponders) as they're wired in.
Added
- Quotes, end to end (Path B Wave 3b + 3c, Phase A). Build a
quote, send it to a client, they accept from a public URL,
Compass spawns a Booking (and optionally a draft Invoice) and
flips the source Lead to won. The full loop, on top of the Wave 3
schema applied earlier.
- /quotes lists every quote with status badge, total, client name, and type (Fixed or Pick & Choose).
- /quotes/new picks the Client (and optional Lead) the quote is for, then chooses the type. Creates a draft and redirects into the editor.
- /quotes/[id] is the live editor for drafts (Fixed: line items table; Pick & Choose: packages + add-ons with mandatory / recommended flags) with totals computed live from the same calculator the server uses on save. Sent / accepted / declined / superseded quotes render read-only.
- Send quote action emails the client a magic-link to
/quote/[token](256-bit public token, branded email template, workspace signature appended if set), supersedes any other sent quote on the same Lead, and timestamps the send. - /quote/[token] is the public accept page (no auth, no app chrome). Fixed quotes show line items; Pick & Choose lets the client pick one package + optional add-ons (mandatory ones locked in) with a live total. Accept / Decline buttons fire server actions via the admin client (RLS-bypassing, token is the access boundary).
- Accept lifecycle does everything in one transaction-ish
flow: marks quote accepted with IP + UA signature, creates a
Booking (snapshotting the total and pulling event_date /
event_location from the originating Lead), creates a draft
Invoice if
auto_invoiceis on (copying line items or the chosen package+addons), moves the Lead to status='won' with the right stage_id, supersedes other sent quotes on the same Lead, and bumps the Client's last_activity_at. - Quotes section on Client profile lists every quote for the
Client with status badge and total; "+ New quote" links to
the new-quote picker with
?client_id=…prefilled. - Site nav gains a Quotes link between Calendar and Invoices.
- Proxy allowlist opens
/quote/*to unauthenticated visitors, alongside/form/*.
Added
- /clients rebuilt as the Client umbrella (Path A Wave 2). The
page used to show won-leads-only. Now it lists every Client the
workspace has ever touched, sorted by most-recent activity
(
last_activity_at desc), which naturally floats currently- active relationships to the top while leaving the rolodex one scroll away. Each row links to the new Client profile. - /clients/[id] Client profile page. Two columns: left card
with contact details, partner names, pronouns, structured
address, and any client-level notes; right column with two
surfaces of activity:
- {Leads}: every Lead this Client has filed, with status badge and event date. "+ New {lead}" opens the New-Lead dialog pre-bound to the Client (skips find-or-create) and pre-fills contact info from the freshest Lead so a returning enquiry is one-tap.
- Invoices: every Invoice issued to this Client, with status, issue date, and total. Footer line summarises paid vs invoiced so the relationship's financial state is glanceable. "+ New invoice" links to /invoices/new?client_id=…
- Edit-client modal. Same native-
- deleteClient server action (ON DELETE CASCADE removes every Lead and Invoice under the Client — no soft-delete for clients yet, the confirm dialog is explicit about that).
- Lead detail page surfaces the parent Client up top. A small "Part of {Client Name} →" link sits above the lead title when the Client's name differs from the lead's, or a "View {client} profile →" link sits underneath when they're the same string. Either way, drilling from a Lead to the Client (other enquiries, invoices, all-in-one history) is one click.
- Invoice creation hub on the Client profile.
/invoices/new?client_id=… now pre-fills client name, email, and
multi-line address from the Client's structured address fields.
/leads/[id] → "Create invoice" continues to work; it resolves the
Client via the lead's
client_idand writes bothclient_idandlead_idon the invoice row so the surface keeps both kinds of attribution.
Changed
- NewLeadDialog accepts
clientId+clientPrefillprops. When the dialog is opened from a Client profile, it renders a soft banner ("Adding this enquiry under {name}") and submits a hiddenclient_idfield.createQuickLeadresolves the parent Client in two paths: explicit hidden field (workspace-scoped lookup + activity bump) or the default find-or-create-by-email. Both paths land at the same Lead-insert with the right client_id. - createInvoice server action now writes
invoices.client_id. Either taken from the explicitclient_idform field or resolved via the lead row'sclient_id. Touches the parent Client'slast_activity_atso the new invoice nudges them up /clients's sort.
Added
- Client as a top-level entity (Path A, Studio Ninja-style umbrella).
New
clientstable introduced via20260519000200_path_a_clients.sql. Every Lead now belongs to a Client (leads.client_id NOT NULL); Invoices gain an optionalclient_idFK (NOT NULL coming in a follow-up once any pre-Path-A invoices are re-homed). Backfill creates one Client per existing Lead, reusing the Lead's UUID as the new Client's UUID so the 1:1 mapping is trivial and lookup-free. Duplicate Clients (Sarah enquired twice as two Leads) are merged manually via a future merge UI. findOrCreateClientByEmailshared helper atsrc/lib/clients.ts. Case-insensitive email match against the workspace; matches bumplast_activity_atso /clients's recent-first sort floats them. Phone-only enquiries always create a fresh Client (recycled numbers / shared lines make phone-match risky). The new-lead dialog, the webhook intake, the public hosted-form submit, the CSV bulk import, and the seed-sample-data action all route through this helper, so every Lead is born with the right umbrella.
Added
- New-lead dialog reachable from every list surface. A single
shared
NewLeadDialogcomponent now mounts in the header of the Pipeline, Leads, Clients, and Calendar pages, opening a native modal with a proper labelled form: primary contact, secondary contact, email, phone, event date, source picker (every recognised source), and notes. Closes Esc, closes on backdrop click, restores focus on close. The cramped inline quick-create on the Pipeline header has been removed.- Clients mounts it with
initialStatus="won", so adding a booked client there lands the lead in the won column without a second step. - The server action allowlists
initial_status(onlynew_enquiry / won / on_hold / cold) so a tampered hidden field can't push leads into terminal states likelost. - On successful create the action revalidates Pipeline, Leads,
Clients, Calendar, and Dashboard, and the dialog calls
router.refresh()so the new row appears wherever the user opened it from. No more redirect-to-lead-detail interruption.
- Clients mounts it with
Changed
- Default pipeline-stage label renamed Chemistry → Discovery.
The seeded
chemistry_call_donestage now reads "Discovery call" everywhere user-facing copy renders it: status badges on Leads, status filter on the Leads toolbar, "Move to …" bulk action, onboarding wizard stage-hint examples, and the Pipeline help article. The internal enum valuechemistry_call_doneis kept unchanged so existing rows and analytics queries continue to work; only the display label moved. (The migration that renamed the seeded stage name was already in20260519000100; this finishes the rename in the application layer.) - Empty states on Pipeline and Leads lead with the hosted contact
form. Both pages used to suggest "Set up a webhook" as the
secondary path for getting leads in. After the integrations
rewrite the hosted contact form is the recommended path for
non-technical studios, so the empty state now leads with
"Set up a contact form" →
/settings/contact-formsand demotes Import CSV and Zapier to secondary options. - Dashboard error page is route-aware.
(dashboard)/error.tsxnow readsusePathname()and routes its "Back to …" link to the section the error came from (Pipeline, Leads, Clients, Calendar, Invoices, Settings) rather than always sending the user to the Dashboard. The body copy reflects the same section.
Fixed
- Every Settings Save / Submit was returning 500. Next.js 16 /
React 19 tightened the rule that a
'use server'file can only exportasync functions; anyconstvalue or sync function on the same module rejects the whole action import at runtime. Four modules were re-exporting their*_INITIAL_STATEconstants alongside the actions, which broke:Settings → Workspace Save,Vocabulary Save,Pipeline rename + reorder,Invoicing Save,Integrations Generate key.- The onboarding wizard's
TerminologyandStagessubmit steps. - The public hosted contact form submit at
/form/[id], which would have 500'd on the first real visitor enquiry. Each offender was split into a siblingstate.tsmodule that exports the type + initial value. Consumers now import the action from./actionsand the state shape from./state. No behavioural change.
/settings/dashboardwas throwing "Functions cannot be passed directly to Client Components". The page was passing the fullMetricDefinitionobjects (each carrying acomputecallback) into theCustomiseDashboardclient component. Added a serializable view type,MetricDefinitionView, plus ametricDefinitionView(def)projector that stripscomputebefore the boundary. The compute itself still runs server-side, so the displayed status is unchanged.- Manual lead creation crashed with a
display_name NOT NULLviolation.createQuickLeadwas insertingpartner_1_name,partner_2_name,email,wedding_datewithout the requireddisplay_name. The action now routes its inputs throughnormalizeLead()(the same normalizer the webhook + public form paths use), so the display name is always derived and partner_1 parses cleanly when the user types a full name.
Added
- Invoices in the top nav. Was reachable only by URL or from a lead detail page. Now sits between Calendar and Settings in the primary nav so it has parity with the other feature surfaces.
Changed
- Settings → Integrations rewritten for plain-English UX. The
page used to lead with "Webhook endpoint", "Workspace ID",
"Sample request" (curl), and "Shared secret (legacy)" cards, all
of which assumed the reader knew what a bearer token was. The
rewrite leads with three "pick your path" tiles, hosted contact
form (recommended), Zapier / Make / Typeform, or custom-built
website, and pushes the technical surface (webhook URL, sample
curl, field reference) behind a collapsed disclosure on a single
"Connection details" card. API keys are reframed as "Connection
keys" with friendlier helper copy; the new-key placeholder is
generic ("e.g. my website") instead of name-checking a single
tenant. The Workspace ID card was removed entirely, payloads no
longer need a
workspace_idsince each API key implies its own workspace.
Removed
- Shared-secret webhook auth. The legacy
WEBSITE_WEBHOOK_SECRETenv-var path on/api/webhooks/website-leadis gone. Every active integration was rotated onto a per-workspacecpsk_…API key, so the fallback path was carrying audit / revocation cost for no caller. Removed:- The
SecretGeneratorUI component and its card onSettings → Integrations. - The
shared_secretauth branch in the webhook route, thesafeCompareimport, and theauth_modefield on lead activity metadata. - The "Setting up the webhook secret" / "Generate a secret"
sections from
/help/integrations-overview,/help/webhook-zapier, and/help/website-form, plus the matchingWEBSITE_WEBHOOK_SECRETreferences in/docs/webhook. - The
workspace_id_required(400) andwebhook_not_configured(503) response paths, neither is reachable now that auth implies a workspace.
- The
Added
- Invoice MVP. New
/invoicessurface lets a workspace generate a numbered tax invoice (ABN, line items, optional GST) and print it to PDF via the browser's print dialog. Built for the "Javiera does a $30 hair job for Margarita and needs a tax record" scenario but works for any sole-trader flow.Settings → Invoicingstores the invoicing identity once: business name, ABN, address, contact info, GST-registered toggle, BSB / account number, payment terms, next number./invoiceslists every invoice with status (draft / sent / paid)./invoices/newcreates one;?lead_id=prefills from a lead./invoices/[id]renders a print-friendly Tax Invoice with aPrint / Save as PDFbutton that opens the browser print dialog. Action bar hides in print via@media print.- Migration
20260513070000_invoices.sql: newinvoicestable (RLS viaprivate.is_workspace_member) andworkspaces.invoice_settings jsonb. - Zero new dependencies. Print-to-PDF handled entirely by the browser; no server-side PDF library or rendering pipeline.
Security
Closed 9 of 10 Supabase advisor security findings. Two migrations (
20260518000000_advisor_fixes.sql,20260518000100_advisor_function_grants.sql) address the full output ofsupabase db advisors --linked --type security:function_search_path_mutableontouch_updated_at— setsearch_path = ''.public_bucket_allows_listingon theavatarsbucket — dropped the broad SELECT policy onstorage.objects. Direct URL access still works for public buckets via the storage CDN; only the LIST path is closed.anon_security_definer_function_executableandauthenticated_security_definer_function_executableonhandle_new_user,is_workspace_member,rls_auto_enable— revoked EXECUTE frompublicfor the trigger-only functions, and relocatedis_workspace_memberto a newprivateschema that PostgREST does not expose at/rest/v1/rpc/*. RLS policies now callprivate.is_workspace_member(...)and the public-schema copy was dropped.
The one remaining advisor finding (
auth_leaked_password_protection) is a Supabase Dashboard toggle, not a SQL change. Tracked inLAUNCH_CHECKLIST.md§ 4.
Performance
Closed 51 of 60 Supabase advisor performance findings. Same two migrations:
unindexed_foreign_keys(7) — added covering indexes onjobs.lead_id,lead_activities.created_by,leads.{assigned_to, created_by, package_id, stage_id},workspaces.owner_id.auth_rls_initplan(8) — rewrote 8 policies onprofiles,workspaces,passkey_credentials,user_totpto wrapauth.uid()in(select auth.uid()), turning per-row calls into single InitPlans.multiple_permissive_policies(36) — six workspace-scoped tables had overlapping "members read" (FOR SELECT) and "members write" (FOR ALL) policies; SELECT was being checked twice per row. Replaced with a single… members accessFOR ALL policy targetingauthenticatedonly (the old{public}role grant letanonevaluate the policy too, even thoughis_workspace_memberwould never match).
The 16 remaining findings are all
unused_index(INFO), expected because production has zero real traffic yet. The flagged indexes back the dashboard widgets and lead lists; leaving them in. Will revisit after a few weeks of real load.
Security
Email-confirmation gating on writes. Unverified users can still browse the dashboard, but every server action that mutates state now calls a new
requireVerifiedSession()helper that bounces them to/verify-email. Reads stay open. The new page wraps the existing resend / change-email server actions in a full-card layout so the user can fix a typo or trigger a new email without leaving the gate. Onboarding actions deliberately remain on plainrequireSession()so a brand-new user can get through the wizard while their inbox is loading; writes start gating the moment they hit a real dashboard surface. Trade-off documented insrc/lib/auth.ts: ~1% conversion loss vs. real spam-account containment + reliable password-reset.Files updated:
src/lib/auth.tsaddsrequireVerifiedSession().- new
src/app/verify-email/{page,actions-client}.tsx— located at root level (outside(dashboard)so the onboarding gate doesn't bounce a brand-new unverified user into a redirect loop). src/app/(dashboard)/{pipeline,leads/[id],settings/...}/actions.tsswappedrequireSession→requireVerifiedSession(8 files, 32 callsites).src/app/api/leads/import/route.tssame swap.
Fixed
- Production QA pass — 2026-05-09. Walked the live site via
Chrome MCP, caught a handful of real issues:
/clientsempty-state copy was hard-coded to "Mark a lead as won..."; on workspaces that renamed the won-mapped stage (e.g. ours uses "Booked"), the instruction was wrong. Now looks up the workspace's actual won-stage name and uses it. The "won clients" header count uses the same label./calendarwas rendering Lost leads alongside upcoming bookings. The page subhead's colour legend doesn't even list Lost, so showing them was confusing. Filteredstatus !== 'lost'from both the upcoming and past lists.- Em-dash audit across
/docs/webhook(5 instances), the backup-code settings card (2 instances). All replaced with commas or colons per the project's no-em-dash rule. Em dashes in JSX comments are deliberately left alone (AGENTS.md allows them in non-rendered text). - Sample seed data ("Add 5 sample leads") now populates
partner_1_nameandpartner_2_namefrom the display name so the Couple card on the lead-detail page renders with realistic values instead of two empty inputs.
Operational
- Hands-on launch session 2026-05-09. Drove every signup +
config that Compass needs to talk to the world via Chrome MCP
(with explicit user consent for the OAuth + TOS confirmations):
- Brevo account created (org "Compass Studio",
gonzalo@novoa.me). API key generated and stored as
BREVO_API_KEYin Vercel Production.BREVO_FROMset toCompass <noreply@getcompass.studio>. - Four Brevo DNS records added at Namecheap for
getcompass.studio(Brevo verification TXT, DKIM 1+2 CNAME, DMARC TXT). Domain still showing "Not authenticated" because Namecheap's authoritative NS hasn't synced; up to 48h propagation per Namecheap's docs. - Sentry activated on the existing pre-flighted scaffolding.
Org
compass-51p, projectjavascript-nextjs, US data region. All 5 env vars (SENTRY_DSN,NEXT_PUBLIC_SENTRY_DSN,SENTRY_ORG,SENTRY_PROJECT,SENTRY_AUTH_TOKEN) set in Vercel Production. Redeployed so the build picked them up. - UptimeRobot account created via GitHub SSO. Monitor on
https://getcompass.studio/api/auth/meat the default 5-min interval, email alerts to gonzalo@novoa.me. - Supabase Pro-only items (leaked-password protection +
Free-tier usage alerts) documented as blocked-on-upgrade in
LAUNCH_CHECKLIST.md§ 4. www.getcompass.studiodomain identified as belonging to another Vercel account; documented the unblock action inLAUNCH_CHECKLIST.md§ 3.
- Brevo account created (org "Compass Studio",
gonzalo@novoa.me). API key generated and stored as
Added
- /api/sentry-check route for verifying Sentry activation in
one curl. Returns a JSON snapshot of which Sentry env vars
reached the deploy;
?throw=1fires a recognisable test error the Issues page should pick up within 60s. Auth-gated byCRON_SECRETso it can't be tripped accidentally. Delete after Sentry is verified working — it's a one-shot tool, not product surface.
Changed
- Lint sweep — 248 problems → 0. Disabled
react/no-unescaped-entities(220 false positives on JSX apostrophes), added the standardargsIgnorePattern: ^_to@typescript-eslint/no-unused-vars, dropped a few staleeslint-disabledirectives, replaced two<a>tags with<Link>(csv-import.tsx → /leads, global-error.tsx kept as<a>with an explicit suppression because Link doesn't work in a document-replacing error boundary). Reordered helper declarations inpasskeys.tsxandnav-progress.tsxto satisfy React Compiler's "cannot access variable before declared" check. Replaced twowindow.location.href = …assignments withwindow.location.assign(…)to dodge a false positive in React Compiler's immutability rule. The handful of remaining set-state-in-effect cases are one-shot hydrations (theme, consent banner, command palette open) suppressed with inline rationale.
Added
- Sentry pre-flight. Installed
@sentry/nextjsand scaffolded every file needed to ship error monitoring + performance traces + session replay:instrumentation.ts(server + edge),instrumentation-client.ts(browser),sentry.server.config.ts,sentry.edge.config.ts.next.config.tsis wrapped withwithSentryConfig(sourcemap upload, tunnel route at/monitoring, automatic Vercel cron monitors).global-error.tsxforwards exceptions viaSentry.captureException. Proxy and robots.txt allowlist the tunnel route. Privacy posture is mask- by-default:maskAllText+blockAllMediaon session replay,sendDefaultPii: falseon the Node + Edge SDKs. All paths no-op cleanly whenSENTRY_DSNis unset, so this commit is safe to ship before the Sentry account exists. Activation = paste 5 env vars (SENTRY_DSN,NEXT_PUBLIC_SENTRY_DSN,SENTRY_ORG,SENTRY_PROJECT,SENTRY_AUTH_TOKEN); steps inLAUNCH_CHECKLIST.md§ 4.
Changed
- Email provider: Resend → Brevo. Resend's free tier ties
verified domains to a single workspace, and that workspace was
already claimed by the photography domain. Brevo gives 300/day
free with custom-domain support, which is enough headroom for
the welcome / backup-code / share-results flows for the
foreseeable future. Code change is single-file
(
src/lib/email/send.ts), with the Waypoint share-results route refactored to call the shared helper instead of its own Resend fetch. Env vars renamedRESEND_API_KEY→BREVO_API_KEY,RESEND_FROM→BREVO_FROM. Privacy page, help article, README, LAUNCH_CHECKLIST, SMOKE_TEST, and TODO updated. - Marketing pages truly static:
/changelog,/docs/webhook,/help,/help/[slug],/pricing,/privacy,/status,/terms,/tree, and the globalnot-found.tsxnow render as○static (or●SSG) instead ofƒdynamic. The win comes from a new<SiteNavStatic />client component that fetches/api/auth/meon mount instead of reading the Supabase cookie during render. Marketing surfaces are edge-cached HTML, faster on first load, and don't hit the database for guests. Dashboard surfaces still use the original server-component<SiteNav />for instant per-workspace vocabulary. - Tree of Compass status sweep: corrected stale statuses on
/tree. Discount calculator moved from "exploring" to "done" (it shipped a while back). Added shipped-but-missing entries: Calendar, Soft-delete & Trash, Bulk actions, Custom fields under Pipeline; Financial year toggle and Status page under Insights; Account security, Data export & deletion, Dark mode, and Billing (in-progress) under Foundation. Wrong shipped markers erode trust, so the page now matches the codebase. - Lead-detail copy audit: the leads list table headers ("Couple", "Wedding") and the pipeline quick-create placeholders ("Partner 1", "Partner 2", "+ New lead") now route through the workspace terminology layer. Solo-industry workspaces see "Contact / Second contact (optional) / + New enquiry"; planners see "Event"; etc. Also softened the packages placeholder ("e.g. Wedding: Premium" → "e.g. Premium") and the CSV-import summary ("wedding directory" → "industry directory").
Added
- Click event date → jump to calendar (Studio Ninja request,
score 88). The Wedding column on
/leadsis a link to/calendar?focus=YYYY-MM-DD&lead=<id>. The calendar reads the focus param, highlights the matching month tile, and smooth- scrolls it into view via a small client island. - Lost-lead reason (SN score 32). When status moves to
lost, the lead detail surfaces a free-text reason input with a datalist suggester (budget / ghosted / style mismatch / date unavailable / went with another vendor / postponed / cancelled / not the right fit). Saves stamplost_at; reverting the status clears both. Migration:supabase/migrations/20260517000000_lead_lost_reason.sql. - Package column on
/leadslist (SN score 43). Workspace packages join vialead.package_id; shows "(removed)" if the package row was deleted, "–" if no package set. - Day-of-week prefix on every event date (Studio Ninja FB-group
top request, score 214). New shared
formatEventDate()inlib/format.tsrenders "Sat, 15 Nov 2026" (or compact "Sat, 15 Nov" inside narrow surfaces). Routed through pipeline cards, leads list, clients view, dashboard widgets, and lead detail. - Pronouns / gender-inclusive fields on leads (SN score 112).
New
contact_primary_pronounsandcontact_secondary_pronounscolumns. Lead-detail Couple card has datalist-backed inputs (she/her, he/him, they/them, she/they, he/they, any pronouns, ask me — plus free-text). Pronouns surface in the lead header. Webhook normalizer acceptspronouns/partner1pronouns/partner2pronounsaliases. Migration:supabase/migrations/20260515000000_lead_pronouns.sql. - Financial-year toggle on the dashboard (SN score 154).
Workspace setting (Settings → Workspace → Financial year) picks
the start month (Jan / Apr / Jul / Oct), and a Period dropdown
on
/dashboardscopes every metric and widget to that FY (?fy=fy26query string). Also supports "All time". Helper library atsrc/lib/fiscal-year.ts. Migration:supabase/migrations/20260516000000_workspace_fy.sql. - Lead activity timeline grouped by day: Today / Yesterday / weekday / dated, each with a sticky-style header. Time-only on each row; full timestamp shows on hover.
- Custom fields on leads: Settings card on the lead detail page
reads/writes
leads.custom_fieldsjsonb. Add / rename / delete arbitrary string-keyed fields. Edits log a genericfield_updatedactivity entry (without leaking values). - Waypoint → workspace.goals sync: when a signed-in user saves
Waypoint state, the True North numbers (annual take-home, jobs
target, revenue per job, tax bracket) mirror onto
workspaces.goalsso the dashboard + roadmap can read a canonical source. Defensive fallback if the column doesn't exist. - Status page at
/statuswith four client-side probes (web, API+DB, auth, static Waypoint) and a coloured overall badge. Refresh-driven; for proper incident history wire BetterStack. - Discount calculator on the Waypoint result page. Drag the slider; the card answers "to absorb a 15% discount, you'd need 18% more bookings to land the same revenue".
- Mobile pass at ≤480px / ≤380px: tighter SiteNav, smaller link text, full-width settings cards, stacked lead-detail columns, bigger button tap targets.
- A11y: stage-move buttons (
←/Stage name →) now mirror theirtitleintoaria-labelso screen readers get the full intent. - ⌘K / Ctrl-K command palette with fuzzy-matched commands across every dashboard route, settings page, tool, and help surface. Mounted only on signed-in routes. Keyboard nav (↑/↓/↵/Esc).
- Status toasts for one-shot URL flags. The pipeline
?deleted=1, dashboard?seeded=1, billing?status=active|cancelled, and the proxy's?gated=…redirect now surface as toasts and the flag is stripped from the URL so a refresh doesn't re-fire. - Waypoint Download PDF button in the Save & Share modal. Wires
the existing
exportPDF()(jsPDF + html2canvas) to a UI control with a "Building PDF…" pending state. - Defensive lead query:
listLeadsForWorkspaceandlistTrashedLeadsForWorkspacenow catch SQLSTATE 42703 ("undefined_column") and fall back gracefully — a forgotten migration won't take production down again. - Mobile nav polish at ≤480px: tighter spacing, smaller links, hidden help icon (it's reachable from the avatar dropdown and ⌘K).
/changelogpublic page that rendersCHANGELOG.mdstraight from disk. Linked in the SiteFooter and the sitemap./clientsview: filtered slice of leads wherestatus='won', ordered by event date so upcoming work is at the top. Empty state links back to/pipeline.- Bulk actions on
/leads: checkbox-per-row + a top action bar that bulk-moves selected rows to a status or trash. NewbulkLeadOpserver action does the work in one query. /docs/webhookdeveloper-facing reference for the website lead webhook: endpoint, auth, body shape, example curl, error matrix, rate limits.- Sample data seed: dashboard empty state has an "Add 5
sample leads" button that drops realistic data so a brand-new
user can play with the kanban + dashboard tiles. Idempotent —
marks rows with
custom_fields.seed_sampleso re-clicks don't pile up. - Lead-detail terminology: partner-1 / partner-2 / ceremony- venue / reception-venue labels now flow through the workspace terminology layer. Solo-industry presets (hair_makeup, other) collapse to "Primary contact / Second contact (optional)" and "Location / Backup location".
- Dark mode: token-only override at
html[data-theme="dark"]with brand-dot kept constant. FOUC-free inlineThemeScriptin the root layout. New Settings → Account → Appearance card with light / system / dark toggle. - Trash auto-cleanup cron:
/api/cron/cleanup-trashhard- deletes leads wheredeleted_at < now() - 30 days. Wired invercel.json; setCRON_SECRETto enable. - Stripe billing scaffolding: workspaces gain
tier,stripe_customer_id,stripe_subscription_id,plan_period_end. New/api/billing/{checkout,portal,webhook}routes use the Stripe SDK. New Settings → Billing card branches onBILLING_LIVE: shows the waitlist while off, the upgrade and manage flows when on. Help article/help/billing-plansexplains the model. LAUNCH_CHECKLIST.mdat the repo root consolidates every user-action step (migrations, env vars, DNS, Stripe setup, monitoring, day-one ops) into one document.- Help article search: client-side filter on
/helpwith a search input that matches title (×3), tags (×2), and blurb (×1). Cmd/Ctrl-K focuses the field. Empty results surface a "tell us what's missing" mailto. - Global toast system:
<ToastProvider>mounted at the root layout,useToast()hook for any client component to push a non-form notice (info/success/error). Auto-dismisses after 6 s, dismissable. Used as the home for ephemeral confirmations that aren't tied to a specific form. - Soft-delete + Trash for leads. Deleting a lead now sets
deleted_atand removes it from the working surfaces (pipeline, leads, calendar, dashboard). Settings → Trash lists deleted leads, with Restore (clearsdeleted_at) and Delete forever (hard remove) actions per row. Migration:supabase/migrations/20260512000000_lead_soft_delete.sql. - Lead-source enum expansion: 12 new values for non-photography
trades (TikTok, Pinterest, LinkedIn, YouTube, Yelp, EasyWeddings,
Thumbtack, Fash, "Cold outreach", "Past client", "Event /
market", "Vendor directory"). Single-source LEAD_SOURCE_LABEL is
now the truth; the import alias router derives its valid list
from it. Migration:
supabase/migrations/20260513000000_lead_source_expand.sql. - Backup recovery code on signup. Every new account gets a
single-use 12-character backup code emailed to them on the spot,
in a branded HTML email with explicit "don't delete this email"
copy. The code can be used at the
/loginsecond-factor step (new "Use a backup code instead" link) to bypass the OTP if the user loses access to their authenticator app or inbox. Codes are scrypt-hashed at rest, single-use (consumed on success), and regeneratable from Settings → Account → Backup code (which re-emails the new plaintext). Migration:supabase/migrations/20260511000000_backup_code.sql. - Per-user sign-in security mode. Settings → Account →
Sign-in security card lets users pick between:
simple: email + password (default).two_factor: email + OTP (TOTP if enrolled, else email-OTP) + password. Stored asprofiles.auth_mode. The/loginflow looks up the mode at the email step and either skips the OTP step entirely (simple) or branches into TOTP/email-OTP (two-factor). Passkeys remain available regardless of mode.
- TOTP (authenticator app) as the OTP source. Settings → Account
has a new "Authenticator app" card with a QR-code enrollment
wizard (Google Authenticator, 1Password, Authy compatible). Once
enrolled, sign-in branches: the OTP step prompts for the
authenticator-app code instead of emailing one. Email-OTP remains
the fallback for users who haven't enrolled. Secret is encrypted
at rest with AES-256-GCM. Migration:
supabase/migrations/20260509000000_user_totp.sql. - Three-step login (email → email-OTP code → password): Replaces the single-form login with an explicit 2FA-style flow. OTP arrives by email via Supabase auth; the verified-email marker is a short-lived signed cookie. SMS swap-in documented in the README — single-line change once a Twilio / Brevo SMS provider is configured in Supabase.
- Passkey scaffolding (off by default):
supabase/migrations/20260508000000_passkeys.sqlmigration for thepasskey_credentialstable with RLS.@simplewebauthn/server+@simplewebauthn/browserdeps./api/passkey/register/{options,verify},/api/passkey/login/{options,verify},/api/passkey/list,/api/passkey/[id](delete).- Settings → Account → Passkeys panel for adding / removing / naming registered credentials.
- "Use a passkey" button on the
/loginemail step. - Flip
PASSKEYS_ENABLED=1after applying the migration to turn the surfaces on.
- Launch-readiness pass:
robots.ts,sitemap.ts,opengraph-image.tsx,icon.tsx,apple-icon.tsx,manifest.tsso the site has a proper crawl map, share card, and PWA install metadata.- Site-wide security headers in
next.config.ts(HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP). - Vercel Web Analytics + Speed Insights wired in the root layout.
- Cookie / data-use consent banner with localStorage memory.
/privacyand/termspages with the legal-prose treatment, revalidate-weekly cache.- Shared
<SiteFooter>rendered on every public + signed-in surface (Pipeline, Pricing, Help, Tree, Privacy, Terms, dashboard, etc.). - Dashboard empty state — first-run users see a four-tile welcome
panel instead of a wall of
–placeholders. - Signup now requires a Terms-of-Service / Privacy-Policy
checkbox (both client-side
requiredand server-side guard). /api/account/exportreturns a JSON dump of profile, workspaces, leads, activity, stages, packages, expenses, jobs (GDPR data portability).- Account deletion flow at Settings → Account → Your data: re-auth via password + email confirmation, cascade-deletes everything via the Supabase admin client.
- Pricing CTAs for Captain / Fleet now route to a "Join the
waitlist" mailto until Stripe is wired (
BILLING_LIVEflag inpricing/page.tsx); paid cards show a "Coming soon" badge. - Page titles +
robots: noindexon/login,/signup,/forgot-password,/reset-password.
- Help articles now render as a table-of-contents + collapsed
sections. Each
<h2>becomes a native<details>(closed by default, multiple can be open at once), and the TOC at the top jumps to + opens the matching section on click. Deep links like/help/waypoint-overview#step-3-job-costsauto-open the target. - Loading skeletons for
/pipeline,/leads,/calendarso the layout anchors instantly while Supabase resolves. - Top-edge navigation progress bar that fires the moment any in-app link is clicked, so clicks always feel acknowledged.
- Speculation rules in the dashboard layout so Chrome / Edge prerender the most-likely next route while the user reads the current one.
- View Transitions CSS for a 180ms cross-fade between routes, with the SiteNav logo as a shared element that morphs in place.
- Optimistic UI on pipeline stage-move buttons: the button dims and nudges the moment it's clicked, before Supabase round-trips.
- SiteNav skeleton + Suspense boundary on every marketing page so the page outline streams without waiting on the auth fetch.
Changed
- Marketing pages (
/help,/help/[slug],/tree,/pricing) now declarerevalidate = 3600. The pages remainƒ(dynamic) until the SiteNav cookie read is moved client-side; until then the revalidate just documents intent. - Aggressive
prefetchon the SiteNav primary links + tools dropdown so Safari and Firefox (no Speculation Rules support) still warm the next route on hover/focus. - Tree feature grid is now
next/dynamic-imported so its hydration chunk loads in parallel with the page shell. - Help articles registry:
findArticleis O(1) (Map index) andarticlesByCategoryis memoized at module load. - Pricing feature matrix uses a discriminated union, dropping noisy
pilot/captain/fleet: falsetriplets on every group-row. - Waypoint static HTML: third-party scripts (Sortable, html2canvas,
jsPDF) now
deferso the hero paints before they download. - Reduced-motion: global override pares all animations to 0.01ms, plus the compass-rose spin pauses when the tab is hidden.
- Avatar img in SiteNav has explicit
width/heightto kill CLS on first paint. - Swept em dashes from all user-facing copy (help articles, pricing,
Tree of Compass, aria-labels). Documented the no-em-dashes rule in
AGENTS.mdso future agents pick it up. - Tightened the Waypoint and Roadmap heroes: smaller wordmark (was 64–132px, now 40–64px), smaller compass-rose mark, less padding above and below. Reclaims about 200px of vertical space on first paint.
[0.1.0] — 2026-05-07
The first internal milestone. Compass CRM, Waypoint pricing
calculator, and Roadmap planner all live under
getcompass.studio, with a shared nav, account system, and
help knowledge base.
Added
Domain & deployment
- Connected the custom domain
getcompass.studio(Vercel + Namecheap DNS for the apex andwwwsubdomain). - Permanent (301) redirect from the auto-generated
compass-crm-five.vercel.appto the canonical domain. - Retired the standalone
compass-calculator.netlify.appdeployment; every URL there now 301s togetcompass.studio/waypoint.
Waypoint (pricing calculator)
- Renamed the calculator from "Compass" to "Waypoint" across every user-facing surface (topbar, hero, results page, picker, PDF export, email subjects).
- New optional Step 0 — North / Star: yearly, monthly, and weekly
inputs that auto-sync (52 weeks, 12 months). State persists as
state.northStar.yearly. "Skip for now" jumps straight to Step 1. - New comparison block on Step 5 (Your Waypoint): only shown when North Star is filled in. Compares the dream income against the business's required revenue, with three styled states (green "+$X headroom", amber "within 10%", red "−$X short"), each with a tailored note.
- Star-glyph stepper indicator for the optional Step 0 (visually distinct from the numbered main flow).
- Cross-device state sync for signed-in users:
/api/waypoint/state(GET + PUT). Browser hydrates from server on load (debounced 1s PUTs on save). Guest users continue to use localStorage only. - Replaced the React port with the original Netlify static-HTML
calculator served at
/waypoint, restoring the polished animations and single-page UX. The Netlify email function ported to a Next.js Route Handler (/api/waypoint/send-results).
Pipeline & leads
- Universal lead-import normalizer (
src/lib/lead-import.ts): field-alias router accepts any payload shape (name/full_name/firstname+lastname,email/mail,phone/mobile/tel,event_date/wedding_date/shoot_date, etc.). Smart name parser handlesSam Smith,Smith, Sam,Sam & Alex,Sam & Alex Smith,Dr. Sam Smith Jr., single names. Strips honorifics and suffixes. - Coercion helpers:
parseMoney,parseDate(ISO + DMY/MDY heuristic + named months),parseEmail,parsePhone,parseSource. - CSV upload at Settings → Import. Browser-side parser handles RFC-4180 quoting, escaped quotes, CRLF/LF, BOM. Limit 5MB / 5,000 rows. Preview the first five rows before commit. Per-row failure reporting (imported / duplicates / skipped / errors). Dedup by email within the workspace.
/api/leads/importroute handler — same normalizer the webhook uses, plus per-row error attribution.- Empty states on Pipeline, Leads, and Calendar with action CTAs (Import a CSV, Set up a webhook, Connect your website).
Roadmap (/roadmap)
- Public feature roadmap at
/tree("The Tree of Compass") with click-to-detail modals. Five categories, ~30 seed features by status (done / in progress / planned / exploring / pruned).
Public pages & marketing
- Pricing page at
/pricingwith three tiers (Pilot, Captain, Fleet), feature matrix, FAQ. - Help & support knowledge base at
/helpwith category index and per-article pages at/help/[slug]. 18 articles covering: Compass overview, first steps, vocabulary system, pipeline stages, CSV import, webhook + Zapier, Meta Lead Ads, website-form integrations, integrations index, Waypoint overview, Roadmap overview, dashboard customisation, metric definitions, calendar view, change password, verify email, troubleshooting email delivery, sign out. - Per-feature help links from each Settings page that has a matching article (Import, Integrations, Pipeline, Profile, Account, Workspace).
Auth & account
- Sign up signs the user in immediately (no "wait for email" wall).
- Verification banner at the top of every signed-in page: surfaces the email, with inline Resend and "Wrong email?" edit controls.
- Forgot-password / reset-password flow at
/forgot-passwordand/reset-password. "Forgot?" link added next to the password field on/login. Generic "if an account exists" copy to avoid account enumeration. - In-app password change at Settings → Account. Verifies the current password before applying the new one.
- Avatar upload at Settings → Profile (server-side via
/api/profile/avatar, stored in Supabase Storageavatarsbucket, public URL). Cleans out stale uploads on each save.
Site-wide nav
- Unified
<SiteNav />Server Component used on every signed-in surface and the public/help,/tree,/pricingpages. - Compass-rose Tools dropdown (icon-only, Waypoint + Roadmap with blurbs).
- Help (?) icon next to the avatar links to
/help. - Avatar dropdown: hover shows a Google-style identity card (name / email / workspace), click opens a Studio-Ninja-style menu (identity header / workspace / Profile / Workspace settings / Account & password / Help & support / Sign out).
- Tools and Avatar are mutually exclusive — opening one closes the other.
- Static-HTML at
/waypointand/roadmapmirrors the same DOM and behavior in vanilla JS so the nav looks identical there. - Logo links to
/dashboardfor signed-in users.
Schema (additive, non-breaking)
- New
display_name(NOT NULL, backfilled),event_date,event_location, andcustom_fieldsjsonb columns onleads. Webhook + CSV import write all of these alongside the legacy columns.leadDisplayName()helper prefers the new column with a legacy fallback. - Renamed
workspaces.calculator_state→waypoint_stateto match the rebrand. - New Supabase Storage
avatarsbucket (public read, 2MB cap).
Public roadmap signals
/waypoint?gated=<route>shows a "this feature needs an account" banner above the mode selector when a signed-out user is bounced here from a protected CRM route.- Mode selector got a third card ("See pricing" →
/pricing).
Documentation
TODO.mdat the repo root: nine categories of deferred work.SMOKE_TEST.mdat the repo root: ~50-checkpoint runbook to run after every meaningful deploy.
Changed
- Calculator URL moved from
/compassto/waypoint(and/compassreferences purged). - Brand wordmark "Compass." now uses Fraunces serif consistently across login, signup, dashboard nav, Waypoint, and the static-HTML pages.
- Industry-aware terminology: "wedding" → "event", "ceremony venue" → "event location", etc., where appropriate. Photographer remains one of several supported industries (videography, copywriting, graphic design, web design, DJ, hair and makeup, personal training, event planning, other).
- Em dashes scrubbed from all user-facing copy (per copy preference);
retained in code comments and the placeholder
'—'rendered for null numeric values. - Old "Compass" tagline replaced with "Every great journey starts with knowing where you are." across auth pages.
app/(dashboard)/top-nav.tsx→src/components/site-nav.tsx(now shared across the whole product).- Proxy: signed-out users hitting a protected CRM route bounce to
/waypoint?gated=<route>(was/login). API routes now return 401 instead of redirecting. - Lead-source enum tightened to a curated set; future migration will open it to industry-specific values.
Fixed
- Static-HTML Waypoint reveal-observer fallback: page no longer silently renders invisible if the IntersectionObserver fails to fire (a 1.5s setTimeout + visibilitychange listener force-reveal anything still hidden).
- "Change industry" button on Waypoint now works for returning users
whose
state.industrywas already set (the click listener was previously bound only when the picker had been opened at least once). - Mode-row pills on
/waypointand/roadmapno longer overlap the CRM-style top nav (now positioned attop: 58pxinstead oftop: 0). - Layout bug where the body's existing
padding-top: 138px(to clear the floating mode-row pills) pushed the sticky CRM nav below the fold; CRM nav switched toposition: fixed; top: 0to avoid the offset. - Vercel "compass-crm-five" URL no longer serves the same app alongside the canonical domain — permanent redirect installed.
- DNS migration:
getcompass.studiorecords point at Vercel; old Namecheap parking page removed. - Resend SMTP configured on Supabase Auth so signup verification and password-reset emails actually deliver (was previously rate-limited by Supabase's built-in mailer).
Removed
- React port of the Waypoint calculator (
src/app/waypoint/*andsrc/lib/compass/*) — replaced by the more polished static Netlify HTML. src/app/(dashboard)/top-nav.tsx— collapsed into the sharedSiteNavcomponent.- "Photography Pricing" eyebrow text where it was misleading outside that industry.
Security
- All inbound webhooks require an
Authorization: Bearer …header matchingWEBSITE_WEBHOOK_SECRET. Endpoint refuses every request without it. - Avatar uploads go through the admin client server-side; no bucket-level RLS dance required, validation happens at the Next.js handler boundary.
- Forgot-password flow returns generic "if an account exists" copy regardless of whether the email is on file (avoids account enumeration).
- Password change requires the current password (verified via re-auth) before applying the new one.