Product Changelog
A running history of Clean Estimate Pro product updates, fixes, launches, and release work.
Product Changelog
2026-10-07 - Compact web workspaces
The web office uses a compact, expandable navigation rail, a smaller header, and consistent page and record spacing. Calendar opens to Day with a time axis and crew and individual resource rows; overlaps stay visible, and each populated row identifies its time zone. The Unscheduled strip opens the full queue in a sheet. Summary and Filters disclose secondary totals and advanced criteria. Week, Month, and Agenda remain available, and approved member time off appears as dated context for team managers.
Clients, Estimates, Pipeline, and Invoices put records first, with summaries and advanced filters available on demand. Compact layouts also cover Home, Inbox, Tasks, Phone, Plans, Operations, AI, Automation, Canvassing, Settings, and Reports. Existing workflows and permissions remain in place; the native mobile app is unchanged.
The invoice list keeps its existing status, billing, and permitted row actions. Deposit resolution controls require the existing invoice-management and refund permissions.
2026-10-01 — Android internal preview recovery and offline saves
The Android internal preview now recovers clearly from sync trouble and checks for pending work before sign-out. Authorized customer records, property and lead links, and lead-to-estimate context sync correctly. New estimates save together with an optional new customer and property, and keep entered values if saving fails. Commercial edits survive reconnect and restart, and a queued Holiday Lights edit without a property image reopens on the same draft. Cached owner and crew sessions reopen offline; completed sign-out stays at sign-in after an offline restart. Team and scheduling screens load without stale-account results or repeated requests. Standard jobs return to their read-only summary from Takedown returns. Inbox filters remain compact, and missing lead data is clearly unavailable. Queued crew photos upload automatically after their record details sync. The app remains an internal preview; physical camera and GPS checks and full release acceptance are outstanding.
2026-09-27 — Estimate sending and sales calendar fixes
- Sending a newly created V2 estimate no longer fails because its record ID
is rejected while recording sales activity.
- Booked lead appointments now appear on their scheduled dates in the web
Calendar, matching the Sales appointments total.
2026-09-25 — Phone-width web calendar
The responsive web Calendar now puts Day/Week dates and scheduled work before the unscheduled-work queue and summary metrics on phone-sized screens. Week shows seven dates with event counts and opens the selected day's service jobs and sales appointments. Filters start collapsed behind a button with the active-filter count. Month and Agenda remain available, job actions are preserved, and desktop keeps its crew grid and left-side unscheduled-work rail.
2026-09-25 — Native commercial property takeoff
Commercial estimates can save building outlines, disconnected paths, and count markers with the estimate, organize takeoff items into proposal groups, and rebuild takeoff-owned line items using manager-configured server pricing. Hand-entered commercial zones remain separate. Optional takeoff rows are informational and excluded from totals. Authorized pricing managers configure service rates, costs, margin, travel and equipment inputs, and site-condition factors; tenant rates are not preloaded. Review or enter stories and building height before applying. Takeoff users can download the saved pricing CSV; a sent proposal PDF remains based on its saved estimate revision. The map also offers Google 3D visual review, candidate building outlines from OpenStreetMap, and USGS 3DEP height estimates when available. Review detected outlines before adding them; accept only ready medium- or high-confidence height estimates, or enter the building height and story count manually. The takeoff operations and unsent draft proposal PDF are also available through the discoverable REST/MCP workflow catalog, subject to the same member, location, and pricing-permission checks.
2026-09-24 — Webhook documentation clarification
Clarified that legacy V1 webhook event names, including bug_report_created, are reference-only and are not available in V2. The Webhook Integrations guide lists the eight supported V2 events separately; this documentation update does not change webhook behavior.
2026-09-24 — Workflow and reliability corrections
- Lead, estimate, and appointment assignments show teammate names or email
when available. A saved assignment without a directory label reads Assigned team member; Unassigned means there is no saved assignee. Calendar scheduling also gives clear guidance when a same-day visit would cross midnight.
- The approval queue shows each authored below-floor option's label, price,
floor, and dollar difference. Approvers can save an optional note with an approval or rejection.
- Schedule changes to a job's truck, crew, or linked estimate preserve the
existing reminder and do not send a customer time-change notice. A genuine appointment date or time change continues through normal reschedule notification.
- Proposal checkout return links use the configured public application URL.
Missing or insecure configuration stops checkout before it starts.
- Completed-job checklists preserve the original completion attribution and
cannot be changed after the job closes; retrying the same saved completion leaves its attribution unchanged.
- Camera-denial recovery and Follow Up date/time behavior are scoped to the
signed internal Android QA preview. In that preview, camera denial shows Camera access is off, explains how to enable permission in phone settings, and offers Cancel or Open settings without changing the current job or unsaved work. A Follow Up visit requires an explicitly selected date and time, shows the campaign's effective timezone, rejects nonexistent daylight-saving times, and uses the earlier instant for a repeated fall-back time with an on-screen notice. Other visit dispositions are unchanged. These changes are not generally distributed; no public Android or iOS release is confirmed.
- Inbox email activity separates delivery failures from opens and clicks, so a
later engagement event does not hide a recorded bounce or complaint.
- Required web form fields now expose their required state to assistive
technology, and validation errors remain correctly associated with fields.
2026-09-24 — Customer privacy and recovery safeguards
- Personal referral codes, reward activity, and customer-attributed reviews
require customer-portal sign-in. Public organization/offer discovery and shared referral booking links remain available; email lookup does not reveal a customer's personal referral identity.
- An uncertain proposal-photo upload keeps its selected file available under
Retry uploads and resumes the same photo operation. New photo selection stays disabled while an upload is unresolved; the selected file remains in page memory until retry or discard.
- Crew Pay requires verified tax and item details when the active policy
excludes them, using a matching accepted option and snapshot, and reads subcontractor payouts from the ledger. Missing required data blocks the final calculation and withholds affected totals and exports; unsupported, manual, or incomplete source records are not treated as zero. Period close recomputes eligible open jobs while preserving already locked payout history.
- Task edits retain a saved assignee missing from the current staff list with
the exact label Current assignee (not available in this staff list), while allowing reassignment only to a listed teammate.
- An inventory adjustment retry with the same request reuses its operation and
cannot apply the stock movement twice. Editing the amount or reason starts a new intent; older callers without an operation key keep their prior behavior.
2026-09-24 — Reports, workflow, and settings refinements
- Global search finds estimates by number, customer, or property and jobs by
number, title, customer, or property. Estimate and job results have separate labels and routes, invoice support stays permission-gated, and old responses cannot replace results for a newer or cleared query.
- Estimate draft saves preserve asphalt measurements and every commercial
property, including unfinished entries. A transient failure offers Retry save for the same captured draft; a different newer revision is not overwritten. Preview and Send Now wait for the current save.
- The Lead Source Performance report shows modeled commission using current
source rates; it does not calculate ROI or cost per lead because it has no spend data. Rehash offers Try again after a temporary report-load error, preserving the selected filters. Location comparisons use refund event dates and avoid counting a legacy refund remainder twice.
- The current-month AI usage limit is a usage estimate separate from the
subscription balance. Warn and overage settings allow calls past the estimate without an automatic charge; Block prevents later requests, while an already-running request may finish across the threshold. AI agent requests also recheck the token creator's current membership, permissions, and organization/location scope; changes affect subsequent calls.
- Promotion validity uses the organization reporting/default-location time
zone, labels that zone, and rejects nonexistent daylight-saving times; a repeated fall-back time uses its earlier occurrence.
- Pausing an automation asks for confirmation, stops new matching enrollments,
and cancels waiting runs at their next checkpoint. Reactivating it does not resume canceled runs.
- Changing the unit for imported Holiday Lights strands preserves the selected
strands and reseeds quantities for the new unit; linear-foot imports use a known design length or leave the quantity blank. Bulk re-quotes count selected estimates, apply current delivery and approval checks, and leave ineligible estimates as drafts.
- A sent proposal revision keeps its original terms on the customer page and
PDF when company defaults change; a new revision uses current effective terms. A maintenance-plan renewal can proceed independently of an unpaid deposit on its original source job; the source job remains unpaid and schedule-blocked.
- Record-number formats save independently, retaining successful saves when
another format fails. Franchise unlink confirmation explains which roster entries and saved report snapshots are removed while preserving the organization and its own records.
- The full-height Sales intake / New lead form is scoped to the signed
internal Android QA preview. It has Customer, Service property, Qualification, and Notes. A name, structured service address, and source are required; service line, email, and phone are optional, but entered contacts are validated. Assign To can be Unassigned; a scheduled lead needs a complete date and valid, ordered start/end times. It is not generally distributed; no public Android or iOS release is confirmed. Physical-device acceptance remains pending.
- Lead intake saves as one operation; retries reuse it instead of creating
duplicate leads or customers. Correctable validation errors open the saved Leads > New lead form for review; saving a correction retries that same lead. If an existing customer's contact details have changed, the rep must select the refreshed customer before retrying. Uncertain outcomes, access rejection, and unresolved conflicts stay blocked instead of silently creating a replacement lead.
- In the signed internal Android preview, Add property photo offers
Take photo, Choose from library, and Cancel for one JPEG, PNG, or WebP image up to 4 MiB. Library selection requests a compatible image copy where supported. A private phone copy is saved before it is queued and can be recovered after an interruption. Upload begins after the lead is saved and its property is confirmed; retry keeps the same image. Before the property is confirmed, removal is local. Afterward, removal waits for the server to confirm.
- Office users can open the read-only photo viewer in Service properties on
a client profile with View intake photos and close it with Hide intake photos. It shows completed property photos they are authorized to see; Load more photos continues the list. The viewer has no photo add, edit, or delete controls.
- The sign-out and recovery behavior is scoped to a signed internal Android
QA preview. Sign-out discard shows pending counts, waits for in-flight requests to finish or time out, and recounts; changed work requires fresh confirmation. Keep working resumes the app; Discard work and sign out discards the full local queue without deleting office records or photos. Another account is blocked with Unsynced work belongs to another account. Sign back in to sync or manage that work. The recovery screen lists failed operations; Review and retry rechecks permission, while Discard saved work is available only when no unrelated healthy queued work remains. Let that work sync first, or explicitly discard the full queue at sign-out. These changes are not generally distributed; no public Android or iOS release is confirmed.
- Web global search on small screens resubmits its retained query when
reopened and lets the latest query win.
- In the signed internal Android preview, a successfully retried meter
correction clears its earlier rejection warning. The warning remains when the server records the attempt but refuses the meter change. Rejected changes are saved locally before upload receipts are completed, preserving recovery after an interruption.
- The signed internal Android preview retries the same unsaved canvassing
visit without creating a second visit. It is not generally distributed; no public Android or iOS release is confirmed. Visits still require a connection and are not queued for offline upload.
- Office lead and client forms expose named fields and associated errors.
Enter submits a valid form, while missing or invalid customer details do not create a record.
API key settings access
Clarified that the External API keys settings page requires the manage_integrations application permission. If access is missing, an administrator may need to migrate both the organization and member permission records through the supported permission migration. A role label or feature flag alone does not grant that permission or override the page's access checks. This guidance does not change API-key scopes or promise that every organization has completed permission migration.
Application workflow operations over REST and MCP
Live API keys can discover the V2 application workflow catalog, inspect each operation's input schema, run permitted queries and mutations, and look up a durable receipt for a mutation. The catalog includes supported mounted app procedures plus dedicated adapters for mobile-only actions and file transfers and tenant-scoped bug-report intake and triage. Schema details report whether an input schema is complete, partial, or unsupported; the runtime parser always remains authoritative. The catalog reports operations available to the key and its active creator; each call still checks that member's current application permissions and location scope. Test keys and legacy wildcard scopes do not authorize the new gateway.
REST operations are available on https://cleanestimatev2.vercel.app under /api/v1/workflows. MCP remains stateless Streamable HTTP at POST /api/mcp, protocol version 2025-11-25. The new MCP tools are list_workflows, get_workflow_schema, query_workflow, mutate_workflow, and get_workflow_request; the existing automation tools remain available. MCP queries send JSON over POST and do not need an idempotency key. Whole JSON requests and responses are capped at 4 MiB; raw inline file bytes are capped at 3,000,000 bytes, with larger transfers using signed storage flows where available.
Mutations use durable idempotency receipts. Pending or indeterminate work is never executed again automatically; integrations check the receipt and retry only with the same idempotency key and identical input. For operations that return secrets, the initial response may include the value once, while saved receipts and replays contain only the redacted projection. Operations with customer, payment, messaging, file, or deletion effects continue to enforce their application permissions and validation rules.
File workflows return bounded content or a short-lived signed storage URL. Upload completion verifies the stored bytes; signed URLs and binary results are not retained in durable receipts. Some PDF, Street View, and mockup operations can call metered providers. See External API Overview, Examples, Auth and Scopes, and Idempotency for the current contracts.
Automation REST API and MCP access
Authorized live keys can list and read existing automation workflows, save a revision-checked draft, publish a selected draft revision, or pause a workflow through REST or MCP. MCP uses stateless Streamable HTTP at POST /api/mcp and protocol version 2025-11-25. Test keys cannot access either interface. A save must include the full workflow definition and the revision you reviewed; stale revisions are rejected, and publishing names the exact draft revision. The key owner needs the matching automation permission, and both the owner and key need organization-wide location access. These interfaces do not create or delete workflows, enroll contacts, or start a manual run.
September 24, 2026 — Invoice payment safeguards
- Office invoice refunds now reduce remaining invoice principal first, then
the payment's remaining tip. A tip-only remainder can be refunded; tips do not change the invoice balance.
- Before a manual invoice payment is recorded, an open Stripe Checkout for
that invoice must be expired. Cash can still be recorded without a prior Checkout. Manual collection stops if Stripe reports the Checkout completed or cannot confirm its status.
- A valid late Stripe charge is recorded once and flagged for office
reconciliation if the invoice changed, became unpayable, or has less remaining balance than the charge. The payment is not discarded or silently treated as a normal invoice payment.
- These safeguards do not activate the new Stripe account-readiness path or
prove hosted card settlement readiness. Existing merchant connections are unchanged.
Unreleased — Stripe account readiness
The new Stripe account-readiness path remains off. When enabled, new business connections will require a full Stripe Dashboard account configured for Stripe to collect processing fees and handle negative-balance losses. Clean Estimate will collect its separately configured application fee; the amount is not set here. The Payments (Stripe) card will show whether charges and payouts are enabled, and returning from hosted onboarding will refresh account status automatically.
Before payment, the new account path will check the connected account's responsibilities, merchant configuration, and active card payments. Older or incompatible connections will need support before a payment can start. No live merchant onboarding or hosted card settlement was verified by the invoice safeguards release.
September 24, 2026 — Server-side break recording and timesheet review
The service now accepts break start and end actions and supplies net-time and manager-review data. Approval requires the full selected period to be visible; partial-period approval is unavailable. Pending actions block approval until they sync or are resolved. An authorized manager may reject a pending, unmatched break-end record with a required reason of 1 to 500 characters; its audit history remains. Approval also requires explicit acknowledgment of all rejected or conflicting actions. Valid late time data can return an approved timesheet to Changed. Existing job-based Crew Pay and commission calculations do not change.
Unreleased — Native mobile break controls and offline sync
Native break controls and review screens remain limited to an internal Android preview and are not generally available to mobile users. In the preview, an open shift offers Start break and End break. Clock out is unavailable while a break is active. Net worked time deducts confirmed break duration. The preview is designed to show queued offline break actions immediately and replay them in order. After the server confirms an action, the last confirmed total remains visible with Updating total… until the new total syncs; retrying an action does not apply it twice. Authenticated mobile use and end-to-end offline sync and conflict visibility have not been verified on a device.
For managers, the preview's Team Timecards shows pending and rejected action counts, while Review Timesheet shows net shift time, break details, and each pending or rejected action with its reason and time. The screen refreshes after an authorized manager rejects a pending unmatched break-end record with a required reason of 1 to 500 characters; its audit history is retained and the screen shows the manager resolution and note. The manager must review the refreshed list and explicitly acknowledge rejected or conflicting items before approval. The preview's On break status and count appear only when an authorized teammate's current, location-visible shift shows an active break; absent, hidden, or ambiguous shifts are not treated as off the clock. Break controls do not enable canvassing location collection: route capture remains off, and the raw-point retention purge is paused.
September 23, 2026 — Office acceptance and New lead accessibility
- Authorized office teammates can record a customer's actual verbal
acceptance after reviewing the exact priced option and payment terms saved with the estimate revision. The record names the customer and teammate and keeps the proposal's prepared date separate from its acceptance record.
- If the option, price, or terms change after the preview loads, the office
must reload and review the current estimate before recording acceptance. Configured below-floor approval and required-deposit gates still apply. Generic and Commercial estimates continue to show No price floor configured when no floor is set.
- Recording verbal acceptance does not send a proposal or message, capture an
e-signature, create a payment, or collect money.
- The New lead form gives its existing-client search and Location, Job source,
Segment, and Assigned rep controls accessible names for screen readers. The visible form layout is unchanged.
September 23, 2026 — Office job notes and crew photos
- Authorized office users can edit the shared Notes visible to the assigned
crew. The editor supports notes up to 8,000 characters and includes an accessible Cancel action.
- Office users with Manage team access see a private crew-photo section in
job details. It shows an empty state when no crew photos are available and remains read-only.
September 23, 2026 — Web navigation and accessibility
- Settings navigation stays available across pages. Desktop uses a side rail;
at narrow web widths, a section selector replaces it. The groups are Workspace, Money & team, and Connections. Existing permissions filter the pages, and nested routes keep their parent section active. The Tags and Online booking settings appear within these groups.
- Estimate customer search and its clear action have accessible names.
Estimate-status confirmations and Pipeline won/lost Reason dialogs keep keyboard focus inside the dialog, support keyboard navigation, and return focus to the opener when dismissed.
- Holiday Lights proposals use the neutral caption “Preview of the proposed design.” It does not change image rendering or saved design and version data.
September 23, 2026 — Franchise link metadata
Authorized platform administrators can configure optional royalty and ad-fund percentages, an optional territory name, and up to 500 unique, five-digit U.S. ZIP codes for each active franchise link. Blank rates remain unconfigured, an explicit 0% is valid, and no rates are prefilled. Duplicate ZIP codes are removed; overlaps across territories are allowed.
If the link changes while you edit, the save is rejected and the latest values reload for review. Changes are audited. Unlinking preserves prior link history; a later link starts with blank rates and territory metadata. This metadata does not calculate or bill royalties or ad-fund fees, change estimate or job costs, or alter the existing read-only franchise report. Franchisee workspaces cannot edit it.
September 23, 2026 — Lead, estimate, and job workflow improvements
- New Lead and reassignment workflows save street, optional second address line,
city, state, postal code, country, and available Google Places details as structured service-address data. Missing values stay blank; older records are not used to fill them in.
- The exact estimate-search text pre-fills First name in Create client
without being split across fields. Household or Business type stays unchanged, and the new client is selected when you return to the estimate. A property is selected from a client profile only when exactly one active property matches the service location; a valid choice stays selected and an incompatible choice clears when the location changes.
- Office job details show read-only crew checklist Done and Pending
status or an empty state. Jobs without a number display Job without a number. Settings > Team > Members displays a full name, then login email, or Team member. These labels do not change job numbers, teammate identities, or assignments.
- The New lead dialog uses its wider V1 layout again, with the same fields
and save workflow.
September 24, 2026 — Internal Android preview: Mobile Notes and Team
These mobile improvements are available in the internal Android preview only; they are not part of the production mobile app.
- Mobile job-note conflict review keeps both versions when an offline edit
conflicts with a newer shared note and asks the crew member to choose after comparing them. Job notes need review opens Your note and Current shared note; Use current note, Save my note, and Cancel leave the choice with the user. If the shared note changes during review, the app asks again using the latest version. Already queued edits and both conflict versions remain available after restart.
- New text in the preview's Notes field is limited to 8,000 characters.
Existing oversized drafts remain available for correction; users can edit or remove text but cannot add characters until the draft is within the limit.
- Save-failure feedback distinguishes text that has not reached the device
queue (Note not saved / Your note is still in the Notes field. Try again.) from a queued upload rejected because job access changed. In the second case, the on-device receipt retains the attempted note and the alert says This note could not be saved because job access changed. Your note is retained on this device. OK only dismisses the alert; a newer edit suppresses the older warning without removing the retained note.
- The read-only Team directory expansion is available to teammates with
Manage team under More > Team. It shows active members visible to them by name, job title or role, crew, and truck. Team members counts those visible active members, while Clocked in counts clocked-in teammates only at locations the viewer can access. Organization-wide hidden shift status appears as Status unavailable. Loading, retry, and empty states are included; there are no contact, pay, or invitation controls. V2 does not track breaks, so there is no On break metric or member break status.
- Improved deployment reliability for app updates.
September 24, 2026 — Mobile client relationship sync
Mobile sync now preserves company and service-site boundaries for client eligibility. A company portfolio with one or more child service sites remains a rollup, not a work target, even when some child sites are hidden by the member's office-location permissions. This does not reveal hidden sites, change permissions, or add a selection flow. Standalone clients and service sites keep their existing work eligibility.
September 24, 2026 — Web canvassing location-retention settings
Teammates with Manage Canvassing can save Point retention (days) from 7 to 365 and Collection interval (seconds) from 5 to 300 in web Canvassing settings > Location collection & retention. Blank values remain unconfigured; no defaults are selected. Saving policy values does not start mobile collection. The retention purge is paused and has not completed a run, so a selected period is not an active deletion guarantee.
September 24, 2026 — Internal Android preview: startup and location setup
The internal Android preview shows Preparing app… while fonts load and Connecting to sign-in… while sign-in initializes; a font-preparation failure shows Unable to prepare app. Please restart. The preview contains updated Android location permissions and a background-service declaration. A fresh cold launch reached the sign-in screen, but did not authenticate or start location collection. The cause of the earlier blank start remains unknown. These changes are for internal preview only and are not a general mobile release.
Unreleased — Mobile canvassing route capture
Optional on-shift route capture remains off and is not available in the released app. It will require explicit consent and platform-appropriate location permissions. Android requires precise location and background location. On iOS, foreground location must be granted as When In Use or Always, and background location must also be granted. iOS may provide approximate locations; the app cannot determine whether iOS is providing precise or approximate location, and the accuracy reported with each point is retained. The task shares the app's Clerk provider and cached-session configuration and coalesces initialization already in progress. Its wait is bounded, but a Clerk SDK load that has already started cannot be canceled and may finish later. The internal Android preview reached sign-in on a fresh cold launch, but authenticated background cold-start and sign-out cleanup have not been verified. Route capture remains off and unavailable, and the earlier blank-start cause is still unknown. Route capture will run only during an active canvassing shift and stop at shift end, sign-out, organization change, or loss of access or location permission. It will not track off duty.
Raw points are intended to follow the configured web retention period, while non-coordinate shift summaries are retained. The retention purge is paused and has not completed a run, so the configured period is not an active deletion guarantee. Offline route points will use a bounded local queue; if a shift ends offline, collection stops and the pending shift-end update retries after reconnection.
September 23, 2026 — Web workflow QA corrections
- Empty Calendar Day, Week, and Operations Month views retain their calendar
layouts; Agenda explains when no work matches. Home's accepted-work link opens unscheduled jobs. The Home, Calendar, Phone, and Maintenance plans headers and summary cards use less vertical space while keeping their existing metrics and actions.
- Pipeline saved views retain the selected AI lead-score tier. A lead with an
assignment but no available teammate name shows Assigned team member. The old /commercial path opens the commercial pipeline. Pipeline report copy explains its date-range and all-pipeline scope, which differs from the currently selected board; report calculations are unchanged.
- Estimate creation selects the sole active property at the chosen location
and keeps the choice open when several properties exist. Returning from client creation reopens the new-estimate dialog with that client. Opening an unchanged draft does not change its Updated time. Invalid negative measurements show an error and are rejected across web pricing and mobile upload. Commercial daily-rate text identifies crew-day pricing and the quote's server-derived calculation; bundle savings copy describes eligible services and conditional savings. Generic and commercial checklist text says that no price floor is configured, without inventing an approval threshold.
- Client search accepts formatted phone-only queries. Portfolio rows identify
the child site supplying a displayed contact method. New-client creation names missing required fields and confirms before discarding entered details. Address suggestions keep available structured fields for completion, and later City/State/ZIP edits do not reset them. A legacy lead address can be corrected against its saved version; an unavailable lead shows a useful path back to Pipeline. The client Sort control has a visible label; its ordering logic is unchanged.
- Settings notification permissions, phone setup, scorecards, canvassing
errors, and unnumbered job details use plain-language labels instead of internal keys. These changes do not alter permissions or job data.
- Invoice unit-price entry selects its current
0.00value on focus.
Review & Send shows the customer preview and explains when the secure link is added. Draft invoice PDFs use a pinned brand when available and keep short terms on the first page when space permits; known Holiday Lights roofline_eave service rows in proposal PDFs read Roofline eave. Customer links say Open client. These fixes do not change historical issued issuer names, enable draft cash/check collection, or send a draft.
- New lead keeps keyboard focus in its dialog, returns focus to its opener,
confirms before discarding edited details, and prevents dismissal during an active save. This changes dialog accessibility and recovery, not lead creation permissions.
Financial activation, customer messaging, and native mobile layout are unchanged.
September 23, 2026 — Tip refunds in rep earnings
- My Earnings now subtracts only refunded payment principal from rep
revenue and calculated commission. Refunding a tip alone does not create negative rep earnings; cash collection reports continue to include tips and their refunds. This does not activate a new payment provider or change the payable commission ledger.
September 23, 2026 — Rehash reporting
- The Rehash report adds template performance by cadence stage and saved
A/B variant, including successful sends, later replies, later won cases, verified recovered revenue, and unverified won cases. Only the last successful cadence send before a won close receives that case's recovery credit. Older messages without a saved variant remain Unknown variant; the report and CSV do not infer an A/B assignment from current settings.
- Cadence retries preserve the variant originally saved with the message.
Queued or failed Day 21 and Day 90 offers still obey the current offer enabled/floor gate; a previously sent message can recover its recorded outcome without sending again. This release does not resume paused customer messages or replay existing queued work.
September 23, 2026 — Canvassing shift and route infrastructure
- Shift and GPS route infrastructure is present but dormant.
Tracking remains off by default, organization retention is unset by default, and no mobile Start shift or End shift controls or route capture are released. The mobile workflow and retention policy still require owner approval. Existing canvassing campaign, territory, and visit workflows remain available as described in the canvassing guide.
September 23, 2026 — Restricted maintenance jobs
- Production now registers QC photo staging cleanup, stale lead-score refresh,
and in-app deal reminders. The QC job's first natural scheduled run completed; registration alone does not establish a run of the other jobs. This mode does not send customer messages, relay existing outbox events, or register financial jobs.
September 22, 2026 — Web and public site
The following V2 web and public-site changes are released. Mobile sync is listed separately until the updated app is distributed:
- Customer pages use the business's name and branding when the organization
is known. Signed-out login stays neutral until an email is submitted, and invoice checkout confirmation falls back to neutral wording if its session cannot identify the business. The invoice confirmation links back to the customer's invoices, and public quote pages no longer carry CleanEstimate Pro attribution.
- Customer-facing estimate follow-up emails and texts omit the internal
revision count. Office tasks, internal notifications, and webhook payloads retain it.
- Live API-key
POST /api/v1/estimatescreates an empty draft from an
existing client or lead and a permitted location. It does not price or send the estimate. Test-key sandbox writes stay isolated and unchanged.
- Job-photo uploads are write-once for each saved photo. Retrying the same
image recovers from a lost upload response, while different image bytes are rejected instead of silently replacing the saved photo.
- Settings now has a Workspace controls heading, role badge, and shortcuts
to Company, Booking, Calls, Offers, Messaging, Locations, and Team. The page reports loading and read-only state, offers Retry loading when settings fail to load, and shows save results for settings sections.
- The office Quality control workspace supports reusable active or inactive
checklists, job-linked or standalone location-scoped inspections, server- scored Pass/Fail/N/A reports, and a failed-report filter. Standalone reports can include immutable, location-scoped photo proof. Pending uploads expire after 24 hours; scheduled cleanup removes abandoned staging once its background job is active. Submitted proof appears in Storage & Attachments usage. This does not add crew mobile report submission.
- Database restore checks now launch migration commands compatibly on Windows,
so the synthetic restore validation can replay migrations and verify its test records. This improves the operator check; it does not claim a provider backup was restored.
- The web app build now uses bundled font files, avoiding a Google Fonts fetch
during build while preserving the site's appearance.
- The web production build now sets an explicit 4 GB Node.js heap for Next.js
and compiler workers to match the passing CI configuration. This changes a build-process setting, not runtime or build-machine sizing; no root cause is claimed.
- The public Clean Estimate website's homepage, pricing page, and current
marketing calls to action now use the waitlist. Pricing and trial terms are not announced; joining records contact details but does not create an account, start a trial, or grant workspace access.
September 22, 2026 — Office and admin
- Settings > Team > Permissions now includes owner-managed defaults for
Administrator, Office, Sales representative, and Crew. Existing members inherit default changes on their next request while explicit individual exceptions remain; owners can reset role defaults or clear one member's exceptions with Reset to role defaults. Managers can inspect the matrix read-only, and changes appear in Team Activity.
- Settings now includes a confidential subcontractor roster and jobs support
one location-scoped subcontractor assignment. Authorized office users can record positive manual payout amounts and track Pending, Approved, Paid, or Disputed status. Clean Estimate does not transfer funds. Matched Approved and Paid payouts also feed a separate mixed-basis estimated-cost view in Margin Analysis; this is not actual margin.
- Margin Analysis now adds Cost with subcontractor payouts. It replaces
estimated crew pay only for sold work matched to Approved or Paid payouts; remaining costs stay estimated, with covered, estimated, and unavailable counts shown. This is not actual profit or accounting margin.
- Settings > Pricing > Red Line now groups floor pricing, commission preview
and export controls, and Rehash policy settings. Per-member Red Line access follows role defaults or a saved allow/deny exception. Below-floor requests still show actionable approval status when private floor details are hidden; manager override controls are not part of V2, and payable commission records remain separate from previews.
- The web bug-report form now collects only the page path, browser, and viewport
automatically; screenshots are optional manual attachments. Bug reports and screenshots are reviewed privately by all-location managers with team access.
- Settings > Schedule & dispatch now stores a shop address, optional paired
coordinates, default truck capacity, and an optional financial overlay. The Jobs route-day view reports scheduled hours without travel time and can open manually ordered stops in Google Maps; it does not optimize routes. Settings changes do not change scheduled jobs.
- Billing now has an owner-only subscription status and history, hosted
invoice links, storage usage, and approximate AI usage. Checkout remains unavailable until price, trial terms, and platform configuration are set; subscription activation is off. Estimate and invoice payment collection through Stripe remains separate.
- AI Pricing Analysis now settles its reserved usage credits together with the
saved result. If result publication fails, the charge rolls back and the reservation is released. It rechecks the member's current pricing permission and location scope after provider work before saving or charging; a revoked or changed authorization blocks publication.
- Retention reporting now includes clients linked to the selected location
even when they have no property there. Health buckets use location-local calendar dates since the last completed job: At risk begins at 183 days and Dormant at 366 days.
- Settings > Brands now lets company-wide teammates with Manage team
create customer-facing identities and assign one to Residential, Commercial, Holiday Lights, or the future Fleet service line. New estimates use the saved assignment; existing drafts retain their pinned identity, and sent proposal and invoice snapshots remain unchanged after edits. Stripe Connect and SMS remain organization-level settings.
- The read-only Franchise report now shows a platform-linked member-company
roster and scheduled captures of prior closed-month measures by company and location. It includes Booked proposal value (accepted), quotes sent, close rate, completed jobs, and open pipeline value at capture. Small customer groups and unsafe totals are withheld; member-level records and customer details are not exposed. Access is platform-provisioned.
September 23, 2026 — V2 web release
- Quality Control photo staging has its hourly cleanup registered in
Production. Its first natural scheduled execution completed September 23; this does not establish a count of files removed. Customer messages remain paused, and existing queued messages were not replayed.
- The V2 Fleet estimate builder uses a single screen for inventory, per-unit
or hourly pricing, seasonal alternatives, options, discounts, and tax. The selected first-wash total is the accepted amount and payment basis; later visit and year-one values are informational. Private first-wash margin uses a tax-included quote basis and requires complete cost assumptions and wash minutes. Year-one margin remains unavailable until recurring costs are modeled. No recurring jobs or payment plans are created. The native mobile interface is unchanged.
- Web Manager AI analysis gives authorized owners and admins with
Manage canvassing scoped aggregate performance answers, verified facts, and recommendations for review. Free text is reduced to an allowed topic intent before provider work; names, addresses, notes, customer records, and conversations are excluded. Requests use AI credits and are limited to 50 per member per UTC day and 30 per hour. Field-coach AI remains unavailable. No native mobile interface changed.
- Gamification settings now include an organization-wide XP switch, leaderboard
visibility, sales and crew title sets, and working-day and timezone controls. XP tracking is off by default; new awards begin only after it is enabled and past activity is not backfilled. Canvassing awards also require a separate Enable canvassing XP awards gate that is off by default. Canvassing settings also expose editable event rules and representative goals; the Shift started rule remains read-only because V2 has no verified shift record. Existing awards keep their value and earlier activity is not replayed. Ten XP is awarded once per verified office-recorded manual payment; distinct partial payments count separately, but retries do not duplicate the award. Completed-job XP requires valid completion timing after enablement; older offline jobs and missing, invalid, or future completion times do not earn retroactive XP. Payment status and payroll are unchanged, and payments without verified staff attribution do not award employee XP. Provider-payment collection is not included. Shift XP, reward redemption, and weekly digest delivery are unavailable. Stale settings saves load the latest values for review before another save. No native mobile interface changed.
Pending native distribution — September 22, 2026
- The updated mobile app will wait for old local-data cleanup before
showing the next account or organization. A previous connector cannot use the next session's token, and offline connection attempts can be cancelled during cleanup. Sync queued edits before changing account or organization. No mobile layout or controls changed; device proof remains outstanding.
2026-09-15 — Client form cleanup
- Web client and portfolio-member forms use separate First name, **Last
name, and Business / company name** fields. The primary contact name follows the client identity, so the same name is entered once.
- Lead sources can be created from the lead-source dropdown by teammates with
Manage pipelines; a saved source is selected immediately and remains available for later records.
- Primary service addresses now include an optional Address 2 / Suite
field, which remains available when properties are edited and displayed.
- Edit client now includes the primary phone and email. Existing values
are prefilled, missing primary contacts are created when a value is entered, and replacing or clearing a method retains its communication history without changing secondary contacts or granting new permissions.
- The client forms no longer repeat service-message permission controls.
Permissions selected during lead capture remain the source of record and are not inferred from contact details or cleared by client form edits.
Waitlist-only marketing signup — September 13, 2026
The Clean Estimate marketing landing page now directs visitors to Join the Waitlist. Joining saves contact details without creating an account or starting a trial. The former signup page returns visitors to the waitlist, and public registration on the marketing site is closed until access opens. Existing users can continue signing in.
Shared homepage links now include the branded CleanEstimate Pro image and a waitlist title and description for social previews.
Outgoing email paused — September 10, 2026
Outgoing email from Clean Estimate (cleanestimate.pro) is paused across V1 and V2 until the account owner authorizes resuming it. This includes manual emails, proposal and invoice emails, automated follow-ups, and notifications sent through this domain. Sending attempts may show a delivery failure while the pause is active. Incoming email remains enabled. SMS is unaffected by this email pause.
This page is the running release history for Clean Estimate Pro. It is built from the cleanestimatepro git history and captures shipped product work across features, fixes, hardening, and rollout changes.
This log currently covers releases from 2026-03-06 through 2026-09-22. Merge-only commits, docs-only mirror commits, and housekeeping-only commits are intentionally left out so the list stays focused on product changes.
Going forward, every production-facing change should add an entry here before the work is considered done.
Unreleased — Mobile
- Added a focused Canvassing screen to the mobile app (**More >
Canvassing), reachable by owners and admins by default and by any rep or crew member an organization grants the View canvassing permission to. Since the phone has no synced property list yet, a rep opens a door by its canvassing ID and records a disposition and optional note. The app looks up device position only when Save visit** is tapped, asks for location permission the first time, and always saves the visit even when that permission is denied or a position can't be found — the visit is never blocked or lost. A visit whose position (or lack of one) looks far from the property is flagged for manager review; the office currently sees only that flag, not the captured coordinates. This is a visit-logging screen, not the full map-and-shift canvassing workspace described in Manage Canvassing Campaigns. (#362)
- The crew bottom bar's third tab is now My pay instead of Earnings. A
crew member could never earn sales commission, so that tab was always empty for them; the pay screen that used to sit in the More list took its place, and both pay rows were removed from that list. The Road workspace keeps its own Earnings destination unchanged. (#357)
- The mobile Leads list and lead detail header now show the linked
property's service address under the customer name, once a property is attached to the lead.
- Holiday Lights takedown jobs now pull their Takedown returns count
sheet from the job's actual rental manifest (the accepted quote's materials) instead of a placeholder item list, and show No rental items recorded for this job when there is nothing to return.
- The Android mobile preview build now builds reliably in CI on every pull
request that touches the app.
- Crew leads and technicians can clock in and out from a new Time clock
row on the crew More screen, optionally linked to today's job with a note. Only one open entry is allowed per person, entries sync like the rest of the crew workspace when offline, and office teammates with reporting access can already read the recorded entries (a Crew Pay report view is planned).
- A sent proposal's Present screen now offers Customer accepts now for
an in-person close: collect the customer's name, signature, and a terms acknowledgement, then submit the same acceptance the customer's own link would record. Multi-option proposals still require the customer to choose their option on the web link. Deposit or payment due after acceptance is shown as due, never as paid.
- A proposal with more than one priced option can now be accepted with
Customer accepts now too: a Choose an option sheet lists each option's name, price, and the recommended marker, matching the customer's web page. The customer taps their choice, then signs at that option's price. Single-option proposals are unchanged. This replaces the earlier limit that sent multi-option proposals to the web link. Ships alongside the asphalt measurement persistence fix in Estimating below (#356). (#355)
- Crew members can now see their own calculated hourly and performance pay
from a new My Pay row in the crew More list — the last 90 days, reusing the office's Crew Pay policy and completed-job calculations, shown only when the organization has a saved policy and the member's base role is crew. (#347)
- Crew members can now set recurring weekly availability and submit
date-bounded time-off requests from a new Availability & Time Off row in the crew More list. Owners and admins with Manage team review the pending queue and approve or decline; a manager can never review their own request. (#348)
- Training and certifications now round-trips end to end: the office defines
requirements and records completions at Settings → Training and certifications, and crew see their own read-only status (complete, expired, missing, optional) from a new Training & Certifications row in the crew More list. (#349)
- Owners and admins with Manage team can now review and approve crew
timesheets by period (this week, last week, or this month) from a new Team Timecards row in the crew More list — a summary strip plus per-teammate hours and status, approval blocked while a shift is still open, and an approved period that is later edited returns to Changed. (#350)
Unreleased — Customer portal
- After a completed job, an eligible customer can Rebook this exact work
(choosing pay-after-service or prepay), Upgrade to Suds Club (residential), or Request a custom maintenance proposal (commercial) from their post-job portal card. None of these book an appointment -- the office always reaches out to coordinate the service date. Every acceptance, including a prepaid rebook once its payment settles, raises an office alert, and the Jobs workspace's Repeat-work requests queue shows the customer's phone, email, and a client-profile link alongside the request.
- Customers can now leave a review from a public, no-login page at
app.cleanestimate.pro/review/your-slug, in addition to the existing signed-in Account > Share your experience form. The public page only accepts a submission from an existing customer's email, and every review is saved unpublished until the office approves it. The thank-you screen offers a Leave a Google Review button, for any rating, whenever a Google review link is configured.
- Customers can now share a personal referral link from a public, no-login
page at app.cleanestimate.pro/refer/your-slug. Looking up an existing customer's email returns their reusable referral code, share link, and stats (referrals, converted, rewarded); a first-time customer is prompted once for their name to mint a code. A referred visitor's booking carries the referral code forward, and the referral converts automatically once that lead's estimate is accepted. The client profile's new Referrals tab lists every referral logged under a client's code and lets an authorized teammate mark one Rewarded with an optional dollar amount.
Unreleased — Messaging
- Settings > Email sending adds a per-organization switch that a teammate
with Manage integrations can turn on or off. Off (the default) keeps quotes and automation emails recorded with a logged, not-sent status; nothing recorded while the switch was off is resent automatically once it is turned on. An organization's explicit choice on this switch now wins over the fleet-wide email pause in both directions, letting one organization resume email while the rest of the fleet stays paused.
- Email messages in the Inbox now carry the same colored delivery-status pill
SMS messages have: Delivered, Opened, Clicked, Bounced, Spam complaint, Suppressed, Failed, or Delivery delayed. The underlying delivery receipts were already recorded; only the display changed. (#352)
Unreleased — Canvassing
- Field View adds a Map mode alongside the existing List: property
markers colored by disposition with a legend, grid clustering that expands into individual pins as you zoom in, a Satellite/roadmap toggle (opens in Satellite), and the selected campaign's territory boundaries drawn on the map. Selecting a marker opens a property panel with the address, current disposition, do-not-knock status (with a manager override to clear it), attempt count, last visit and representative, next follow-up, and a linked CRM record when one exists. Map mode needs a configured Google Maps browser key; without one it shows a fallback notice and points you to List for the same live data.
- Field View, Visits, and Reports now share one filters bar (campaign,
territory, representative, disposition, address search, and date range), so the same filter choices and behavior apply everywhere and stay in the page URL.
- The Properties CSV export now walks every target property in the selected
territory instead of only the page already loaded on screen, so large territories no longer export a truncated file.
- Reports adds a Campaign comparison section alongside the existing
territory comparison: completion rate, attempts, contacts, leads and lead rate, appointments and appointment rate, estimates and estimate rate, and sales and sales rate, side by side for every campaign matching the current filters. Completed revenue is not yet available per campaign.
- Analytics page corrected: Canvassing Reporting and Exports
now matches the shipped report instead of marking the filters bar, Field View, reporting metrics, comparisons, leaderboard, and CSV exports as unavailable.
- The Field View property panel can now mark a property do not knock
directly (the existing manager Clear action remains) and shows the property's visit history -- disposition, date, representative, and a note excerpt for each recorded visit. (#336)
- The Properties CSV export now also includes each property's current
disposition, do-not-knock flag, attempt count, last visited date and representative, and next follow-up date, alongside the existing address, coordinates, and source columns. The Campaign comparison table on Reports now has its own CSV export, matching the territory comparison's. (#338)
Unreleased — Payments
- Server-side payment processing now refuses to start with a live Stripe key
outside production, and logs a warning (never the key itself) if a test key is ever used in production.
Unreleased — Holiday Lights workflow
- Holiday Lights package pricing now comes from the organization's active Price
Book instead of a fixed fourteen-zone choice list. Office users can snapshot a catalog row into Good, Better, or Best, edit its customer description, quantity, unit, and unit price for that quote, and keep those edits through reloads and later catalog changes. A reviewed Import drawing strands panel can prefill explicit estimated linear feet for linear_ft items while keeping per-strand and per-each quantities separate; source-line deduping, legacy-price conversion, and the 50-row limit remain visible in the builder.
- The web Winter Wonderland panel remembers the selected Classic, Colorful,
or Elegant style and keeps the same preview request for the saved estimate package and design. Reloads, ambiguous network responses, in-progress responses, and storage delays check or finish that request instead of starting a second generation. A terminal result that cannot be confirmed surfaces an explicit Generate a new preview action.
- Proposal Media lets office users add saved photos, lighting designs, and
Winter Wonderland mockups, then enable or hide an item, edit its caption, and reorder it. Explicitly hidden or removed media is absent from the customer proposal and PDF; legacy drafts without media settings preserve their prior behavior. On the web, Add photo uploads multiple property photos at once and adds each completed upload to the current list, including an explicitly empty list, while preserving existing captions and hidden choices. Rows can be reordered by dragging or with the move controls, and saved designs show their actual previews.
- Deposit display follows the selected signed Holiday Lights terms through
office review, customer proposal, and PDF. CE Pro does not substitute a generic deposit percentage when the saved choice is no deposit, fixed, percentage, or full payment; an unpriced selection stays visibly unpriced.
- Bulk seasonal re-quotes require a target season later than the source. Same-
season and earlier targets are rejected, the source remains unchanged, and retries skip an existing open renewal for that source and target. Renewals keep undated removal/storage choices, customer and crew notes, and the saved design selection; they clear prior installation/removal dates even when the source window is incomplete and ask you to add property photos or generated previews again for the new season.
- The web workflow saves the Monday-through-Sunday installation-week pair,
After January 1 or Leave up longer removal timing, requested removal date/note, and Storage included preference. Accepted install and takedown work carries those choices plus public customer notes and internal crew notes. Preferences do not auto-book dates; takedown remains targeted to the removal month and uses the saved season when no requested date is provided.
Unreleased — Holiday Lights designer
- The web visual designer supports column, bush, tree, garland, and pathway
decorations through compatible priced zones. Select Move items to reposition an accent, or use Edit selected to change its color, priced zone, and Quantity or Linear feet. Changing that quantity changes the active package quote. Delete selected removes only the visual accent; its priced zone and quantity remain in the quote until adjusted separately.
- If a browser interruption stops an autosave, a browser copy from the last
24 hours offers Restore changes or Discard browser copy for a draft estimate. When the saved estimate is newer, Reload latest continues from the newer saved version instead of silently overwriting it.
- Existing office-authored drafts keep their saved pricing adjustments in the
mobile Estimated total: commercial first-clean premiums or waivers, and Holiday setup or renewal adjustments.
Unreleased — Residential pricing and customer portal fixes
- Older residential drafts keep their saved custom charges when reopened,
so office and device totals continue to include those charges.
- Newly sent residential quotes keep named custom-charge lines in the customer
scope and PDF.
- Switching the selected service property in the customer account reloads
Documents, Invoices, and Work for that property and resets their lists, paging, status messages, and appointment actions so results from the previous property do not reappear.
Unreleased — Membership payment refunds
- Plan detail Payment history shows an unresolved membership refund with its
original amount and reason; those details stay fixed while Retry refund continues that same refund after an uncertain result or reload. Only one unresolved refund is allowed per payment, and payment history keeps the refund total correct when confirmations and retries arrive in a different order.
Unreleased — Campaign performance
- Campaign lists and detail pages load their existing enrollment and message
summaries more efficiently for campaigns with many records; displayed counts remain accurate.
Unreleased — CRM reliability
- A tier-change payment retry stays tied to the membership that owns the saved
change, so another attempt continues that change instead of applying it to a different plan.
- Selecting a different canvassing campaign loads its complete available
territory choices for visits, competitions, and reports. Visits and reports clear their current page when filters change and clear incompatible territory choices after the new list loads; late results from an older filter cannot replace the current view.
Unreleased — W11 automation builder draft
This entry records reviewable W11 work in progress. It does not claim that the builder, graph actions, storage changes, or provider integrations are deployed or live.
- The V1 builder usage is documented as a four-step Guided Editor flow:
Trigger, Conditions, Actions, and Review. A desktop-only Visual Builder provides the V1-style palette, graph canvas, configuration panel, TRUE/FALSE condition branches, and autosaved draft viewport.
- Guided Editor now exposes all 16 bounded graph actions for selection, card
editing, reorder/remove, and draft save. Changing a draft's trigger type replaces its old configuration with the valid shape for the new event and clears incompatible fields; the published revision remains unchanged until activation.
- The graph keeps an immutable executable revision, compare-and-swap saves,
organization and location scope checks, and directed-acyclic-graph validation capped at 200 nodes and 400 edges. If required condition data is unavailable, the run reports a visible retryable outcome instead of taking the FALSE edge. Disabling a workflow cancels its active run steps, including waits, rather than waking them after reactivation. split and recurring-schedule steps remain outside the W11 contract.
- The template catalog carries all 14 V1 graph recipes in V1 order with exact
graph-step counts: Speed to Lead (8), lead and job appointment reminders (2 each), Estimate Follow-Up (13), Post-Job Review (9), Post-Job Nurture (7), residential and commercial booking tasks (6 each), estimate follow-up tasks (6 each), job completion tasks (3 each), and invoice collection tasks (5 each). Cloning preserves entry conditions and branch topology while remapping graph identifiers and creating a draft; templates do not enroll records or send providers by themselves.
- The action contract preserves bounded messaging, delay, tag, task, status,
assignment, field, webhook, workflow, call, voicemail, internal-note, and award_xp semantics. XP follows the V1 disabled-by-default, multiplier, and replay rules and fails closed when a scoped target cannot be resolved. The adapter binds graph UUIDs to the published revision's member, resolves V2 assigned-rep ownership including the lead context used by Make call, and uses the exact scheduled truck-day assignment for crew_lead rather than guessing from a crew roster.
- Save conflicts preserve the local draft until the teammate confirms
Reload latest. Webhook values reload as [saved securely] placeholders with opaque unchanged-save references; raw URL, header, and body secrets do not return to the browser. Private webhook secret references are saved and resolved inside the owning transaction, so a failed save rolls back without leaving an orphaned secret; after Automations manage is restored, Save draft and Activate recheck the role and preserve the protected reference. Voice callbacks must be signed and account-bound, match the durable pending reservation, and pass provider evidence checks before a graph run resumes.
- Inbound automation webhooks accept scoped API-key requests with
webhooks:write, a 256 KiB JSON ceiling, and replay-safe idempotency. The signed customer review form accepts a service property, a 1–5 rating, and a plain-text comment; reviews remain unpublished and Customer Reviews defaults on. The review.left condition uses the exact review.submitted outbox fence or the enrolled run's start-time fence and fails closed without one.
- The V1
update_fieldbridge now keeps its field boundaries explicit:
lead.at_risk is a boolean separate from health, job.crew_notes is separate from job notes and crew assignment, and estimate notes plus follow-up dates are data-only fields with ISO or relative-day input and no automatic reminder side effect. Client customer_type accepts only household or company, preserves account/site and parent structure, and safely replays the same value.
- The automation template gallery now exposes all 14 V1 graph recipes in the
V1 order. Each card reports its graph step count, keeps the existing Use template path, and offers Build visually on desktop screens. Empty API results stay empty instead of silently showing the five legacy linear examples; those legacy keys remain accepted for existing links.
- Inbound message and phone-call automations require an existing canonical
customer in the same organization and location; concrete events cannot use a synthetic customer fallback. Estimate-expiring deadlines fire at or after the location-timezone calendar-day offset while the estimate is still unexpired, on a five-minute sweep, and invoice deadline occurrences use local midnight.
- Inbox reply-boundary checks keep working after phone-call privacy hardening by
reading only the approved call-history fields. Private automation context remains unavailable to ordinary member reads.
- Message retries keep their admitted sender, recipient, and Reply-To facts
while current organization, location, member, channel, and messaging policy are checked again. Sender defaults come from the organization display name and persisted database settings, voice callbacks follow the durable call ID through credential rotation, and a draft mentions its message owner only after that owner's record commits; otherwise it waits on the pending item.
- The graph task action records the V2 ownership boundary: tasks require an
assignee, so an omitted assignee falls back to the frozen triggering actor. Operators can choose the assigned representative, a specific member, or the current user; an unavailable triggering actor fails explicitly instead of silently using the publisher. For a planned job task, Assigned rep resolves the owner of the originating estimate or lead. A zero-hour due offset means due now, and a half-hour offset means thirty minutes. This is a documented V2 deviation from V1's unassigned task trigger. Automatic and event-triggered runs now advance round-robin assignment just like manual runs, while a retry keeps the same selected member.
- Preview and Test remain provider-free. The desktop canvas falls back to the
guided editor on narrow screens, and the native mobile app receives no Automations UI change.
- Existing linear workflows now open in Visual Builder with stable canvas node
identities. Appointment-relative waits remain visible and are preserved on save; Guided Editor remains the place to change their calendar anchor.
Unreleased — Phone and messaging
- Start a company-line call can now dial any customer phone number, not
only an exact saved contact. Type the number and an optional customer name, then choose Call from company number; the number is checked and normalized before dialing and an unusable number is refused with a message. Placing the call still requires Place phone calls, and the usual call limits still apply. A number that matches a saved customer contact in your location is linked to that customer automatically; otherwise the call keeps the typed name. The call uses your own location, or the company's default location if you work across all locations; if neither can be determined, the app asks you to choose a location first.
- Added live call guides. Turn them on and choose default inbound and outbound
scripts under Settings > Company calls, recording, and voicemail, then build up to 20 scripts with up to 20 steps each, including a name, a call type, a direction (inbound, outbound, or both), and a required/optional flag per step. Placeholders {{customer_name}}, {{rep_name}}, and {{company_name}} are filled in automatically. When an office user starts an outbound call from the web Phone page, the matching guide opens as a card in the lower-right corner with step-by-step talking points, a progress bar, Mark this step complete, and Previous/Next controls; it can be minimized to a pill or dismissed for that call. The manual-dial form gains a Call type selector bound to your enabled outbound scripts. Live guides do not yet open automatically for inbound calls.
- The Inbox now shows carrier delivery status for outbound texts. Each
message carries a status pill — Queued, Sent, Delivered, or Failed — next to its time, and the pill only ever moves forward: a later "sent" report never replaces a "delivered" or "failed" pill already shown. A Failed text also shows a short line with the carrier's error code when one was reported. Coverage still depends on the carrier: some report only as far as Sent and never confirm what happened at the handset. Nothing needs to be configured — these reports arrive automatically. The Delivery summary in the Inbox header and the message delivery figures on Business > Reports now count these text facts alongside email's, instead of reporting texts as unmeasured.
- Added Automatic post-call actions to the Call scorecard settings under
Settings > Company calls, recording, and voicemail. Create an assigned follow-up task (on by default) creates a task from the review's first recommended next step, assigned to the call's owner, due in the configured Task due in hours (default 24, 1–720); a review creates this task once and a retried review never duplicates it. Draft a customer follow-up message (on by default) keeps the review's suggested follow-up text as an editable draft instead of clearing it. Alert manager below score (on by default) routes a coaching-needed alert to managers when a review scores below the scorecard's low-score threshold — high priority 20 or more points below the threshold, medium priority otherwise. None of these actions ever send a text, email, or call automatically.
- A new contact method now starts with texting and email consent on file for
service (transactional) messages, recorded the moment it's added — an owner ruling that "all contacts when added to the system have given consent." A later STOP reply, unsubscribe click, or staff-recorded revoke still overrides this default. Marketing consent stays exactly as opt-in as before, and this default does not make a contact eligible for automated calls or voicemail drops — those still need consent a staff member explicitly recorded. The consent history in a conversation's timeline names this default as its own cause. See Client Profile. (#392)
- The Inbox composer disables Send for a channel the contact hasn't
consented to, with a Record consent link to that client's Contacts panel, instead of letting you send a message the server will refuse. (#387)
- The Send button now reads Sending… while a message is in flight.
Non-"sent" message outcomes in the conversation thread (refused for consent, refused for a notification preference, failed, no contact method, no sender number, still sending, or recipient changed) now read in plain words instead of a raw status code, matching the delivery report's wording. (#381)
Unreleased — Reports
- Reports now opens a hub listing every report you can see as a card,
grouped into Sales And Growth, Customer Health, Operations, and Specialty. Selecting a report opens it on its own page with a persistent row of report tabs across the top so you can jump straight to any other report; ← All reports returns to the hub. The date range preset (or custom from/to dates) and the location filter carry over automatically as you move between reports.
- The Margins report's Callback leakage figure now shows the real count
and revenue of callback or rework jobs against the range's total sold jobs, based on each job's pay settings, instead of "not available." Generic quotes now record a cost snapshot when sent, priced from the price book's unit costs, so their margins appear in the Margins report alongside residential, commercial, and Holiday Lights work; a snapshot is frozen at send time and never changes afterward.
- Job detail has a new Callback / rework checkbox (Manage team permission)
so the office can flag a job as a callback; the Margins report's callback leakage counts flagged jobs from the next run.
- The Holiday Lights report's Term mix, Open service calls, and
Weather holds figures now show real counts instead of "not available." Term mix reads the 1-year/3-year/other term saved on each accepted contract; open service calls and weather holds are as-of-now counts, not filtered by the selected date range. Job detail has a new Weather hold control (Manage calendar permission), beside the Callback / rework checkbox, to set or clear a hold; it is refused once the job is Complete or Cancelled.
- The office New job dialog now shows a Kind select (Standard or
Service call) whenever Job type is Holiday lights; a service call created there counts toward the Holiday Lights report's Open service calls figure while it stays Scheduled or In progress. (#335)
Unreleased — AI
- The AI hub (
/ai) now has one settings panel for the org-wide AI access
switch, default tone, monthly credit, and what happens once it's spent (warn, block, or allow overage), plus a usage summary for the current calendar month. Only members who can manage the team can change these settings. (#320)
- Brand Voice's tone setting now applies to AI Draft in the shared inbox.
(#320)
- AI Agent Access (
/ai/agents) issues scopedcea_tokens so approved
assistants can read business data and make two guarded, confirmation-gated writes without a full user login. Every token has its own tool list, revoke, and activity log. (#325)
- Smart Pricing (
/ai/pricing) turns an organization's own estimate
history into increase, hold, decrease, and bundle recommendations, cached for 24 hours, with a statistical fallback when no AI provider is configured or a provider call fails. V2 has no regional benchmark data yet, so recommendations are based only on the organization's own numbers. (#324)
- Photo-to-Price and Inbox Follow-Up Drafts now reserve an estimated cost per
call, rounded up to the nearest cent, instead of $0, so the monthly credit cap applies to every AI feature the usage summary meters; Inbox Follow-Up Drafts settles to its actual token cost once the draft is generated. The AI hub's usage panel shows the estimated cost per call. (#337)
Unreleased — Estimating
- Estimate photos are now automatically compressed to a smaller copy for
fast loading when a customer views the proposal on their phone. The full-resolution original is always kept and remains available.
- A new pricing engine maps up to four asphalt maintenance measurements —
lot square feet with coat, crack fill feet, striping count, and patch square feet — to priced line items on the Generic Quote, priced from the org's own Price Book rates. (#339)
- Price Book adds an Add asphalt maintenance items button that seeds V1's
five default sealcoat, crack fill, striping, and patch repair items, with V1's default rates, for the office to review and edit. (#340)
- The Generic Quote adds an Asphalt maintenance panel with **Add to
quote**: entering measurements adds the priced lines above, re-running replaces the previous asphalt lines instead of duplicating them, and the panel prompts to seed the Price Book first when none of the five items exist yet. Entered measurements are not saved with the draft. (#341)
- Asphalt maintenance measurements (lot square feet, coat, crack fill,
striping count, and patch area) now save with the estimate draft and come back filled in when you reopen it; the priced lines behave as before. Ships alongside the mobile in-person option picker in Mobile above (#355). (#356)
- The Accepted proposal card on an estimate now names how the customer
accepted — through the emailed or texted link, in the customer portal, or in person on a rep's phone — and shows the signature captured for that acceptance. Acceptances recorded before this change show the customer-link wording. (#354)
- The estimate builder's Price override ($) field refuses a negative value
at the field, with its own error message, instead of silently accepting it into the saved draft. Invoice line quantity and price fields now show their validation error on the specific row instead of one generic message for the whole invoice. (#380)
Unreleased — Web navigation
- The left sidebar no longer reflows on first load: permission-gated items
(Price Book, Approvals, Rehash, Canvassing, Automations, Marketing, Reports) now only ever appear below the items already on screen as your permissions resolve, instead of the whole list shifting once they do. (#380)
Unreleased — Settings
- Most Settings sections now confirm a successful save inside that same
section — Online self-booking, Booking Widget and portal settings, Company calls/recording/voicemail, Customer offers, Texting number, and Email sending — instead of only reloading the page with no visible confirmation. A failed save shows its error in the same place. (#384)
- The Online self-booking section now shows whether the published booking
page actually offers a slot, and if not, exactly which setup step is missing. See Portal Settings. (#388)
- Read-only banners across Settings now name the permission in plain language
and who has it by default (e.g. "Read-only — team management is granted to Owner, Administrator by default") instead of printing the raw permission flag. (#381)
Unreleased — Web performance
- The customer invoice page, and the booking, review, and referral pages,
now render their content on the server before JavaScript loads, so they appear faster on a slow connection. Pages nobody signs in on no longer load the sign-in SDK at all. (#375, #378, #382)
Unreleased — Documentation corrections
- The Asphalt Maintenance guide no longer
describes a dedicated asphalt module as live. It now explains how to quote asphalt work today with a Generic Quote and Price Book line items, and notes that a dedicated module is planned.
- The Canvassing AI guide no longer describes field and
manager AI coaching as available. It now marks that assistant as planned and describes what the canvassing workspace supports today.
2026-09-14 — Guided automation fixes
- Guided drafts save correctly with Wait and Internal Note. Internal
Notes stay in the conversation, team notifications retain their configured recipients, and new workflows save correctly even when other workflows already exist.
- New workflow waits use minutes, hours, or days and retain business-hours
settings. Older linear workflows keep their weeks, anchored waits, and team notifications. Preview follows workflow order and identifies sample steps that cannot run with a clear warning.
- Incompatible editor changes keep unsaved edits and show a clear error.
- Saved Guided workflows reopen with their Wait and Internal Note steps
still visible.
2026-09-14 — Company information save
- Company details can now be saved with Website blank. When provided, a
website must use a valid http:// or https:// URL.
2026-09-13
Commercial tiered proposal templates
- Commercial template authoring now has Scope & pricing and **Tiered
options** editors. Save reusable property scope, rates, frequencies, line items, program terms, custom terms, and proposal validity defaults without copying customer or site identity into the template.
- Tiered options reuse the estimate option workflow: add, edit, duplicate,
reorder, remove, and mark a Good/Better/Best choice as recommended, with per-option line items, discounts, tax, and a rounded pricing preview. Sets support 1–12 options and up to 100 line items per option.
- Applying a template from a deal on the Commercial pipeline copies the saved
scope and option data into the existing single-screen estimate builder. Templates remain unchanged, and newly authored options round-trip through the existing proposal option data and pricing services.
- Commercial draft validation runs before structured template saves. Legacy
option payloads remain recoverable until replacement, option-save errors reopen the Tiered options tab, and switching between the Residential JSON editor and commercial editors preserves the current draft or blocks malformed JSON instead of discarding it.
V1-compatible lead scoring recovery
- Restored the web AI > Leads workspace with deterministic lead scores,
Hot/Warm/Cool/Cold tier filters, ranked results, score-factor details, recommended follow-up actions, and score badges on pipeline cards and lead detail pages.
- Scores now use the current CRM signals, including recorded property square
footage, requested services, proposal views, returning-customer history, referral source, request age, stored lead value, contact methods, and primary estimate acceptance. Recalculation and the nightly stale-score sweep remain bounded and member/location scoped through the existing RLS context.
- Lead creation, stage changes, sent proposals, and deal notes schedule an
after-commit recompute through the existing outbox relay. This retained V1 scorer is rule-based and makes no provider calls or AI-credit reservations.
- Every recomputed score now persists rule version 1 and truthful source-field
evidence for its fired factors. Recompute locks the canonical lead row so a delayed event cannot overwrite a newer source state, and duplicate leads for one client are not treated as returning business without a verified prior- business signal.
- Recalculation keeps V1's 500-record batch and the ranked page keeps V1's
200-record page, while both expose continuation so larger scoped organizations are not silently truncated. The retained release still has no model/provider calls, paid-credit usage, or automatic routing engine.
- The ranked page now keeps refreshed results authoritative when an older
load-more request finishes later, ignores rapid duplicate load and recalculation clicks, and recovers cleanly after a failed read. Tier summary counts identify when they cover only the loaded pages. Expanded score evidence uses readable factor and source labels with the recorded values, while the original machine evidence remains attached to the score.
Commercial stage colors
- Commercial pipeline stage settings now include a native color control for
existing and new stages. Colors are validated as six-digit hex values, default to #6b7280 for legacy stages, and persist through save and reload.
- The commercial board and forecast show each saved color beside its stage
name. Any member can read the colors; changing them requires the existing manage_pipelines permission.
- The settings editor now keeps late pipeline and stage responses scoped to the
selected pipeline, shows Retry when the initial pipeline load fails, and disables stage controls while a request is pending. An uncertain save or stage add offers Reload latest before another attempt so a double click cannot replay the same intent. New-stage retries keep a stable stage identity through a delayed response, and category, archive, or add refreshes retain unrelated unsaved color and forecast fields.
Automation draft and activation trigger safety
- Saving an automation draft now keeps its trigger with that draft revision,
so editing a workflow does not change the trigger or subject type used by its active revision. The workflow list shows the newest draft trigger for review, while event matching and manual-enrollment choices continue to use the published trigger until activation.
- Activation publishes the latest trigger and actions together. Retrying an
already completed activation reuses the existing live revision instead of creating duplicate publication history. Activation also requires the draft revision shown in the editor; a newer save from another editor is rejected instead of being published silently. A manual request queued before activation is skipped if its subject no longer matches the newly published trigger.
- When another save makes an activation stale, the editor now explains that
the workflow changed and asks you to reload before activating instead of exposing an internal conflict identifier.
- Manual enrollment now queues reliably through the write-only platform event
boundary while retaining current actor, tenant, location, active-revision, and duplicate-enrollment checks.
- Publication and event enrollment now remain consistent when they overlap:
the queue either keeps the previous immutable revision or re-reads the newly committed active head after waiting for the publication lock.
- Graph templates and legacy workflows now share one trigger contract, so the
automation runtime can load both paths consistently when it starts.
- Time-based lifecycle triggers now run from the active published revision on
a five-minute schedule. Appointment upcoming/missed, plan-visit, and contract-renewal deadlines use durable occurrence identities, so a retry cannot create a duplicate automation event.
V1 automation webhook delivery
- Added the retained V1 Webhook action with POST, PUT, and PATCH methods,
custom headers, and JSON body templates that preserve V1 merge-field behavior. Requests are admitted into a service-only delivery ledger with a stable delivery identity, so editing a workflow cannot change an already queued request.
- Delivery runs asynchronously with bounded retries and per-attempt
identities. A retry clears the prior attempt claim before re-queueing; a claim that never reached the network can be recovered, while an in-flight request whose acknowledgement was lost is quarantined as ambiguous instead of replayed automatically.
- Immediately before DNS resolution or network I/O, the worker verifies the
run state, live definition, exact revision and node, actor, and location. Paused, canceled, terminal, or unauthorized runs fail closed. URLs are HTTPS-only, private and metadata addresses are blocked, and redirects are not followed.
- Fixed an issue that could stop a valid queued webhook before it was sent.
- V1-compatible requests now carry the frozen run envelope. A bodyless action
sends workflow_id, run_id, step_id, org_id, client and subject IDs, and triggered_at; an explicit JSON template receives that same metadata under _automation. The runtime resolves protected request secrets only in the service worker, revalidates current subject authority before each physical address, and settles the automation step only from the exact same-organization delivery attempt ledger.
- Header values, endpoint credentials, and rendered bodies stay in the
service-only ledger. Run history and manager-facing activity expose only redacted action and delivery outcomes.
- Step reservation, protected-secret resolution, and delivery admission now
commit together. A failed secret check or ledger insert rolls back the reserved running step, and dispatch also keeps the shared manual actor and active-location guard alongside the current graph subject checks.
- Each admitted delivery is now bound to the exact reserved graph step. HTTP
retries rotate only the physical delivery attempt identity, so they cannot mint a second graph attempt or settle a later logical retry. An explicit graph retry gets a new bound delivery row, while legacy rows without a step binding are quarantined and never sent.
- The registered webhook worker now performs a bounded evidence-only pass for
bound delivered, failed, and ambiguous rows whose exact graph step is still pending or running. This repairs a worker crash between the terminal ledger write and graph settlement without resending HTTP, and terminal steps drop out of later scans.
Lead detail follow-up controls
- Restored the V1 lead-detail control composition on the web: permission-gated
Messages history through a V1-style right-side panel and the linked client's existing communication reader, per-lead Enroll in Automation, CAS-protected Mark lost with a reason, configured-stage progress, stale and scheduled indicators, a Summary/Readiness rail, linked account and shared customer tags, and an inline Open Tasks plus activity panel.
- The Messages panel keeps the existing contact and inbox permission gates,
does not create a conversation when it opens, and reuses the customer- scoped Inbox workspace for full timeline reading and explicit replies while the teammate stays on the lead. Open full inbox remains available for the full route.
- Lead archival uses the existing revision-checked V2 archive writer and
retains estimates, activity, stage history, and audit history. Archive and restore now apply the same owner or Move others' cards boundary as lead stage and rep changes, with Manage calendar still required for saved appointments. The page does not expose V1 hard delete or a direct Convert Lead action. Appointment detail shows Appointment scheduled or Appointment confirmed from the stored current-appointment confirmation fact.
- Lead automation activity now loads the complete history for the selected lead
with Load older activity pagination. Archived workflows remain visible, and historical steps keep the action and channel recorded for that run even after the workflow is edited. Switching leads during a load now resets the activity list and pagination state for the newly selected lead.
- Lead activity pagination now uses each run's immutable creation time for its
snapshot fence and cursor, so queued runs remain in the correct page even if they start while history is being read. Queued rows show their creation time until a real start time exists, and a failed history read can be retried from the current lead without stale pagination state.
- Appointment emails now carry a secure confirmation link. A customer can also
reply with exact CONFIRM or CONFIRMED text from the saved phone number. Rescheduling or changing the linked customer, property, or location clears the old confirmation before the new appointment can be confirmed.
- The web lead detail page now provides a Deposit & Payments card for an
exact accepted proposal. It shows the remaining acceptance-bound deposit, invoices from the matching job lineage, and net deposit or invoice history.
- Offices can open the existing Stripe deposit Checkout, record split pre-job
cash, check, bank transfer, or ACH deposits, or record an enabled offline invoice payment through the existing permission, ledger, and idempotency services. Invoice payments keep their existing receipt path. Each deposit is bound to the displayed accepted estimate, revision, option, and acceptance; stale balance writes and changed idempotency intents are rejected. Multiple accepted proposals require an explicit selection. Native mobile remains unchanged.
- Manual lead collections now carry the configured invoice tip choice as a
separate ledger amount. Percentage tips use the accepted option's canonical discounted pre-tax amount from its frozen pricing snapshot, while the option total may include tax; tips sit on top of deposit principal and do not change the deposit balance CAS. Lead summaries expose that tipBaseCents together with the exact tipOptions (tip_1, tip_2, tip_3, and none) for the accepted option and each invoice. When a legacy snapshot cannot establish a truthful pre-tax basis, the summary returns tipBaseCents: null, a tipUnavailableReason, and only safe flat or no-tip choices. Exact replays preserve the original tip intent and compare any supplied displayed amount with the persisted tip. Office collection also supports a bounded Custom tip amount, and exact replays compare that amount. Manual refund amounts now mean the total customer charge: allocation consumes remaining principal first and then tip, so partial and full history/net values cannot strand a tip; a same-day void reverses both principal and tip.
- Commercial accepted options with complete frozen pricing provenance now derive
percentage tips from the untaxed first-year basis after the recorded portfolio discount, plus the untaxed first-clean premium. Tax-inclusive commercial invoice lines are never mislabeled as pretax, and legacy snapshots without enough provenance keep percentage choices unavailable. PostgreSQL acceptance coverage includes taxed and discounted commercial fractions, authored generic options on commercial estimates, office Custom replay, and partial-then-final tipped manual refunds.
- A manual collection validates its exact replay, accepted lineage, actor
location, current principal balance, method setting, and tip choice before expiring an open deposit Checkout. Retrying a lost response therefore leaves a newer remaining-balance Checkout untouched, and a provider expiry failure rolls the manual write back. Independent PostgreSQL transaction coverage now exercises these order, race, authority, replay, and ledger invariants.
- The same transition guard now covers direct Stripe PaymentIntents during
keyed-card and reader rollout. Manual payment, waiver, and replacement Checkout wait while a committed intent reservation is unresolved, even before a payment mirror row exists. Registered direct intents are cancelled only in provider-cancellable states through their saved connected account; settled, unknown, or refused provider states leave the local payment pending so an office retry cannot collect a second charge. Failed direct rows remain guarded because Stripe may still be able to confirm them. The replacement Checkout admission now applies this guard even when that failed row is the only mirror or an unresolved reservation has no mirror yet, while preserving exact replay of a still-valid pending Checkout.
- Lead history now supports reasoned partial or full refunds and same-day voids
for pre-job deposits from the existing manual ledger and Stripe Checkout rails. Stripe reversals use an acceptance-targeted durable command, so a provider timeout or worker crash can resume with the original provider idempotency key. The exact accepted estimate, revision, option, and acceptance stay bound through the locked payment row, and replaying the same request key returns the original adjustment instead of duplicating it. Permission, location, stale-lineage, and stale-balance checks remain server enforced. Refunds accept the remaining total customer charge, including tip, while preserving principal and tip amounts separately in the payment ledger; a void clears both. A refused provider void remains an explicit settled error and never turns into an unrequested refund.
- The lead card now includes the V1 keyed-card flow through Stripe's secure
Payment Element and provider-backed web Stripe Terminal reader controls. Both flows create one acceptance-targeted PaymentIntent with the displayed amount, accepted estimate lineage, optional receipt email and note, and a frozen idempotency key. Card details stay inside Stripe Elements and never enter app logs or the payment database. Reader controls discover and connect supported internet readers for the current location; the browser may require Local Network Access permission for discovery. Native mobile Tap to Pay remains on its frozen UI and is not represented by a web device simulation.
- Card and reader results expose Check payment status, Cancel payment,
and exact retry/reconciliation behavior for pending or lost responses. Reloading history keeps the original intent and key until Stripe has a definitive result, including for a partial refund or other in-flight action. A fresh page now offers Resume payment for the selected accepted attempt, reusing the server-provided frozen request and key for reserved, pending, outcome-unknown, and failed-but-payable Stripe states. When that request cannot be recovered, the card shows a held recovery state instead of offering a second charge. A failed result with an unresolved provider status also keeps the original action and key open until status is authoritative or the office cancels it. Permission, readiness, amount, location, and accepted-lineage checks stay server enforced. Office collection also preserves saved tip choices and offers No tip plus a bounded Custom amount, including when percentage tips cannot be calculated safely from an older accepted proposal. Manual cash, check, bank transfer, ACH, and external-card ledger entries can be reversed from the lead card; provider reversals surface setup or reconciliation action instead of silently claiming success.
- Allocation and reversal now share the accepted lead's lock boundary. A
deposit with a payment_allocations row cannot be reversed from the lead card even while its payment mirror keeps invoice_id null, and the lead summary hides Refund and Void for that credit. Invoice allocation refuses an accepted refund or void that is pending, failed, or between its durable command and provider settlement, so a provider retry cannot race the invoice credit ledger. The existing invoice deposit rail remains available after allocation.
Lead detail composition and payment readiness
- The web lead page now keeps the V1 composition in one usable surface: the
header's More menu groups View client, View account, and Mark lost; linked customer Tags stay compact and inline; and configured stages appear in one horizontally scrollable strip. Tasks & Notes stays in the left column while Summary, Deposit & Payments, automation history, and safe Archive stay in the right rail, including at narrow web widths.
- Summary now has the V1 six-row layout with relative update age such as `8d
ago`. Its readiness message is driven by the payment card's exact accepted acceptance, estimate, revision, option, and deposit facts. A lead with multiple accepted proposals must choose one explicitly; the page never treats any estimate or the newest working estimate as accepted implicitly. The message preserves the canonical pending, paid, waived, refunded shortfall, and unavailable payment states, with recorded money taking precedence after a waiver or refund; due amounts retain cents instead of being rounded to whole dollars.
- The lead page has no direct Convert Lead control. Archive retains
estimates, payments, activity, and audit history through the existing revision-checked writer.
- Unresolved direct payments now have a conservative 23-hour recovery window
when their provider identity is still unknown. After that, or when the reservation age is invalid, the lead card reports Recovery required instead of risking a second provider request. Known PaymentIntent IDs remain pinned to their connected account for safe retrieval, and provider refund delivery keeps its reservation presence check read-only.
Lead source ROI and attribution settings
- Settings > Lead Sources now lets managers set a source's company/self
category, commission percentage, and brand-source flag in a saved edit dialog while preserving the existing name, immutable prefix, ordering, archive, and restore controls. Self-generated sources use the V1 10% default and company-generated sources use 5% when a new source is created.
- Business > Reports now shows the V1 Lead Source ROI experience: Revenue by
Source, Revenue / Win Rate / Leads / Commission Paid cards, module filtering, sortable Source / Leads / Estimates Sent / Won / Win Rate / Revenue / Avg Deal Size / Commission Paid columns, and an Export Lead Sources action that follows the selected date, location, and module filters.
- ROI keeps lead-created funnel counts separate from accepted-work revenue,
includes accepted-only older leads in the correct period, and carries numeric upsell, source commission, and top-rep revenue facts into the export; Brand Source remains the V1 Yes/No column while the API exposes the aggregate brand-funded total. Loading, permission, retry, and stale-response states are scoped to the ROI section. Commission Paid is based on accepted work and does not change payroll or payment ledgers.
Lead & customer editing and pipeline recovery
- The web lead detail page now keeps customer name, email, phone, pipeline
status, assigned sales rep, lead source, service address, city, state, ZIP, property square feet, and lead notes in one Lead & customer editor with one Save changes action. Status continues to use the configured V2 pipeline stages, and a blank square-feet field clears the lead-owned value.
- Lead-owned changes are checked against the current record revision. A stale
tab asks for a reload instead of replacing newer lead details. Contact and address updates for a linked client remain one explicit follow-up sync with truthful declined and partial-success feedback. Shared addresses continue to use the linked property record, and a confirmed property sync checks the structured address shown in the editor before writing so a concurrent change produces a reload-required conflict. Clearing a contact method retires only the displayed method; when no method is primary, replacing the exact fallback shown in the editor makes the new method current without copying the old method's consent or verification, and retired identifiers no longer participate in current sign-in, delivery, or search while history remains.
- The lead Activity panel now records V1's manual Note, Call,
Email, Text, and Meeting entries through the existing internal activity history. Logging one never sends a message or starts a call, and a new entry remains a Note unless another type is selected.
- If shared-property validation fails after the lead save, the editor keeps the
required street validation and shows Reload required instead of a false Saving state. Reloading is required before another edit can be submitted.
- The Open Tasks and linked-client Tags cards now clear stale results when you
move between records. If a task or tag write has an uncertain response, the card pauses further edits and asks you to Reload latest before trying again, avoiding an accidental duplicate write. A stale create, completion, tag create, or tag save callback from the record you left is ignored before it can write against the newly opened record.
- Pipeline search now includes city, ZIP, email, and phone. Bulk stage and rep
actions keep per-lead revisions and preserve failed selections and reasons, while saved pipeline views retain their existing save, apply, rename, and reload behavior.
Company portfolio workspace
- Client accounts can now be created with their account type, status, industry,
contact details, label, and notes. Portfolio profiles show visible child client/site members in Contacts, account roles, primary-member state, and the existing property and contact details. Add Contact can create or search for a standalone client, nest it under the account, preserve its Property / Site Name, retain the selected lead source, and save its account role and primary state in one operation, including when the account is opened from an office-scoped view.
- Add Contact saves can be safely repeated after a lost response: the original
client and property are returned without duplicates, while a later edit is preserved and a changed retry asks the user to refresh.
- Linking an existing client from Add Contact now saves its role, primary state,
and account membership together. A retry after a detach or edit asks you to refresh instead of silently restoring the old membership.
- Portfolio Jobs lists the company's own and visible child-site jobs with
their job identity, client/contact, service, schedule, total, status, and updated time. Portfolio Estimates show client and created date; Leads show client, source, and created date. The profile's Lifetime value tile now uses frozen accepted estimate options and shows the subtitle Accepted revenue across sites; cash-collected values remain in payment history. Portfolio Estimates, Jobs, Leads, and Contacts use full scoped totals; retained parent invoices remain visible with permitted child-site invoices, including each invoice's created date, contact, amount, balance, and status. A saved primary member remains visible in the account header even when it is beyond the first loaded Contacts page, including an inactive member within the viewer's permitted scope. Activity can load older estimates, jobs, invoices, leads, permitted message summaries, and CRM activity while preserving the current user's organization and office access.
Bulk multi-property scheduling
- The existing Calendar and All jobs Schedule/Move dialog now preserves the
same date span, daily time window, service/job type, truck, and independent crew-hours facts used by scheduled work. The web calendar shows a saved job on each occupied date while native mobile keeps its approved start-date-only schedule surface.
- Jobs > Bulk Multi-Property Scheduler now accepts either a saved account or
standalone client and keeps all selected properties available across paged results. The V1 controls remain visible: saved customer quote pricing or manual line items, Tax %, saved-quote discount, service and job type, date span, time window, crew, truck, crew hours, notes, and per-property overrides. Discounted totals stay aligned with the job and draft-invoice pricing snapshots.
- Jobs Only and Jobs + Route Stops use the same multi-property workflow.
If a response is lost, retrying the batch keeps the same jobs from being duplicated. A deliberate correction pass can retry selected failures while keeping successful jobs available for the existing draft-invoice action.
- Older Holiday Lights quote sources retain their selected term and office
scope. Ordinary web job creation and Move editing preserve date-only jobs, optional daily windows, truck and service/job type, and independent crew hours, including explicit clearing of optional fields.
- Calendar New job now uses Service type as the saved job title and
starts scheduled work with today's date. Leave it date-only or add an end date, daily window, truck, and independent crew hours; the form preserves those values through the save and ignores a duplicate same-click submit.
- Route batches open the existing Schedule > Operations > Day consumer for
customer/job stop cards, address navigation, truck order, unassigned jobs, drag ordering, start, move, and office-only skip actions. Skip records a reason for the route stop while the scheduled job remains in job history. The web calendar also shows a saved multi-day job on each occupied date; native mobile keeps its approved start-date-only schedule view and query behavior. Native mobile navigation and layout remain unchanged.
- The route-day Edit Crew controls preserve the V1 Crew Lead, Technician,
Solo route day, Default pay/Hourly only, and notes workflow. Solo clears the technician selection; calendar/team permissions and stale-day errors are shown at save boundaries, and closed pay facts are not rewritten. A lost save response now offers Retry save with the original change or Reload day for an authoritative refresh before new edits; a successful exact retry returns to the clean editor without a stale-draft warning. Clearing a truck day preserves its revision history so a later crew assignment can be saved safely.
- See Bulk Multi-Property Scheduler and [Route
Management](/operations/routes).
Customer appointment request concurrency
- Customer appointment requests now stay safe when office staff update the
same customer's contact or active status at the same time. One operation finishes first, fixing a lock-order race that could fail a request; customer requests still check the current session and customer details before saving.
- Re-saving an appointment's crew or duration while keeping its existing slot
retains the confirmation link instead of requiring a new confirmation.
Durable Award XP automation action
- Automations can award XP through the generic gamification ledger with a
bounded amount and required reason. The server verifies the active scoped publisher and target before writing, applies the workspace multiplier, and records skipped outcomes when gamification is disabled.
- Retrying a completed action reuses the workflow run, node, and target
identity, so the existing XP event is replayed instead of duplicated. Failed validation and out-of-scope targets remain in durable automation history.
2026-09-12
Safe completion for closed dialogs
- Saves from dialogs opened on a client profile now stay attached to the dialog
that started them. Closing the dialog or navigating away before an estimate, contact, property, client, or lead save finishes prevents its older response from redirecting the page or closing a newer replacement dialog.
Client profile activity history
- Client profiles now bring estimates, jobs, invoices, leads, permitted
message previews, and CRM activity into one chronological Activity view. Phone calls, voicemails, and internal notes keep safe summaries, while each row keeps only the source details and safe View action the signed-in teammate can access.
- The Communication view now keeps Messages and Activity as nested
views. Both can load older rows while preserving the current-client, office, Inbox, and record permissions. Use Try again for a first-page failure or Reload messages/Reload activity when an older continuation expires. Invoice history shows the visible Amount, Balance, Status, Issued, and Due fields, and its Load older invoices continuation keeps closely created invoices in the correct order.
Operations Locations workspace
- Operations > Locations now provides the location
workspace and detail page with address, contact, service-area ZIP, location type, time zone, country, manager, notes, crew assignments, and scoped inventory, equipment, crew, and teammate totals.
- Adding and editing a location keeps the existing location selectors and
reporting-time-zone settings compatible. Full edits can clear optional fields, stale saves ask the user to reload, and deactivation explains when a default location, assigned teammate or crew, or active operational work still depends on the location. Completed accepted work remains available as history when a location is retired, and an unavailable assigned manager can be cleared explicitly without blocking unrelated edits. Operational notes support multiline text up to 2,000 characters.
- See Locations for the fields and access rules.
Rehash recovered revenue and case actions (follow-up release)
- Rehash recovered-revenue reports use the proposal option the customer
actually accepted, even when a newer working quote is still open. Older closes with no single verifiable accepted amount appear in an aggregate Unverified count and are excluded from known recovered revenue while overall close counts and existing attribution records remain available.
- The Rehash drawer keeps the loaded case and its actions together while a new
case loads, so delayed responses cannot send a touch or re-quote to a different case than the one shown.
Durable linear automation trigger coverage
- The complete supported event set for editable linear workflows now uses the
retry-safe automation processor, including manual enrollment and invoice payment, without changing the existing builder controls or trigger names.
Bounded Holiday Lights property-photo decoding
- Holiday Lights uploads continue to accept JPG, PNG, and WebP. The server
now uses the patched image-decoding runtime for bounded, metadata-free WebP normalization and rejects malformed or oversized image payloads before private storage.
Safer project photo completion
- Project photo completion verifies that the estimate is still editable before
reading the upload. Invalid or oversized files are rejected and cleaned up, missing uploads remain retryable, and a late completion cannot remove a photo already attached to the estimate or included in a sent proposal.
Reliable office lead intake and estimate handoff
- New lead keeps the existing V1 form and controls while saving the
selected client, service property, location, source, assignment, and optional appointment through one request intent.
- The form locks while saving. If the response is lost, Try again reuses
the same request identity and returns the original lead instead of creating a duplicate lead number, pipeline card, or activity. Selecting New lead again starts a deliberate new intent. A session, permission, or validation error during recovery keeps the original intent locked until it is recovered or the user explicitly chooses a separate lead, which may duplicate an already-saved record.
- Global search clears the previous dropdown while a new query is pending,
discards older in-flight responses, and invalidates pending results when the query is cleared or the search is dismissed, so a prior customer, lead, property, or invoice cannot reappear under a newer query.
- A confirmed save opens the canonical lead detail page. New Estimate on
that page carries the saved client, property, location, and lead lineage into the existing single-screen estimate builder, which reliably loads that context before editing begins.
- See Managing Leads, Sales Pipeline, and
2026-09-06
Bulk Schedule for company portfolios
- A company portfolio profile gets a Bulk Schedule entry beside
Ungroup. It loads every child service site paired with its own primary property and most recent accepted-estimate price, lets the office apply shared batch defaults or Apply Defaults To All Rows, then edit any row's service type, date, start time, duration, crew, total, and note before running the batch.
- Running a batch creates one job per selected site (capped at 25 sites),
never all-or-nothing: one bad site is recorded and the rest keep going. The result screen uses V1's own labels, Batch Created and {n} jobs were created successfully, with a Create Draft Invoices (n) follow-up.
- See Client Accounts.
Pipeline: saved views, bulk actions, job type filter, and an activity feed
- All leads gets personal saved filter views (save, apply, rename,
delete), row-checkbox bulk actions for stage change, rep assignment, and automation enrollment (each with a real per-lead result), and a multi-select job type filter backed by a new normalized requested-service relation.
- An Estimates count column shows on the list, the mobile card, and the
CSV export (V1 never had an estimate-count filter, only this column).
- Every lead now has one unified, newest-first activity feed merging stage
moves, rep reassignment, and manual Note, Call, Email, Text, or Meeting entries via Log activity. These entries stay in the internal history and do not send communication; the default type remains Note. The feed is cursor-paginated 200 rows at a time.
- Weighted Pipeline (one of V1's six pipeline stat cards) is still not
shipped; it needs a per-stage win-probability column V2 does not have yet.
- See Sales Pipeline and [V1 to V2 Lead Workspace
Parity](/getting-started/v2-lead-workspace-parity).
Crew pay: policy, crew splits, pay mode, and the Operations report
- Settings > Crew pay ships V1's policy fields (pool rates, elite threshold,
daily revenue cap, overtime, minimum wage, callback and exclusion rules) as versioned, prospective-only policy saves; a Crew split editor per crew; and a Team member pay defaults table setting each member's pay basis to Performance or Hourly with an hourly rate.
- A pay-period-close lock is new, not a V1 port: once a period closes, a
job's computed pay freezes at the participant level.
- The Reports page's Operations card gains a Crew Pay section beside
Crew productivity and Overdue invoices, with V1's exact crew-pay columns and period totals, CSV/Print export included.
- Job-level compensation overrides, the route-day hourly-only toggle, and
the dashboard's tip visibility remain V1-only; see Crew Pay for exactly what carried over.
Commission plans: per-rep plans, approval workflow, and payroll export
- Settings > Commissions gets an org-wide default plan plus an optional
per-rep override (owner decision: each rep may have a different plan), with residential/commercial schedules, percentage or flat rate, and company-generated/self-generated amounts.
- The ledger's pending -> approved/rejected -> paid workflow is new (V1
documented it but never built it); rejecting requires a reason. Clawback is also new: a refund on the ordinary invoice-refund path claws back the matching commission automatically (deposit-credit, plan-payment, retention, and direct Stripe Connect refunds do not trigger it yet).
- Payroll export covers V1's four formats (Estimate Summary, standard,
Gusto, QuickBooks), now bounded by period (This Week/Month/Quarter, Year to Date, or All time) and by rep.
- Lead Sources gets a Category column (Company generated/Self
generated) that determines which of a rep's two rates applies.
- See Commissions and [Lead
Sources](/crm/lead-sources#company-generated-vs-self-generated).
Reporting: margin, schedule, sales-cycle, lead-source, geography, retention, automation, rehash, holiday-lights, and location reports
- The Reports page gains seven new cards for the reliability rewrite's
reporting rows: Margin analysis by module, showing cost as not available rather than guessed once any sold estimate in a group has no costing snapshot; Schedule efficiency; Sales cycle, a real-milestone lead-to-close chain plus per-pipeline stage velocity, time to first view, and best send day; the Operations card's crew-productivity and overdue-invoice sections; Lead source ROI; Geography; and Retention.
- Four more cards ship for the same push: an Automations card (runs,
sends, exits, and conversions per workflow, gated on the separate View automations permission), a Rehash card (recovered closes, recovery rate, recovered revenue, the frozen commission split, and active stage load), a Holiday lights card (seasonal revenue, close rate, rebooking rate, installs/takedowns, shrinkage, and inventory), and a Locations card comparing your own locations side by side with an org-total row.
- Every new card keeps V1's labels where the capability carried over and
names what didn't as not available, with the reason: callback leakage; commission paid, upsell, and brand-funded revenue on lead sources; holiday lights term mix, service calls, and weather holds; and franchise royalty, ad fund, and territory utilization. Every new card exports to CSV or Print through the same controls as the rest of the page.
- See Margin Analysis, [Schedule
Efficiency](/analytics/schedule), Sales Cycle Analytics, Lead Source ROI, Geography Analytics, Retention and At-Risk, Automation Analytics, Rehash Analytics, Holiday Lights Analytics, and Franchise Reporting.
2026-09-05
Estimate files and a workspace storage limit
- Every estimate now has an Estimate files section on its detail page,
next to the Photos panel, for permits, signed approvals, scope documents, and other office files. It uses the same 20 MB size limit, thirteen file types, and 50-per-record ceiling as invoice attachments, soft-deletes with the same 30-day recovery window before a daily purge job, and is open to any member who can see the estimate. These files are never shown to the customer.
- Settings > Storage and Attachments now reports estimate attachments
as their own source, and adds a workspace usage bar: bytes used of the workspace's storage limit, and percent used capped at 100 for display. The limit defaults to 10 GiB per workspace, same as V1, stored as an organization setting rather than a bare constant. Going over the limit shows a warning banner; it does not block uploads, matching V1.
- Organization logos and other workspace branding assets now count toward
the storage total shown on that page, reversing an earlier V2 choice to leave them out.
Invoice search, CSV export, bulk void, AR aging, and overdue reminders
- Invoices now show up in global search by invoice number or customer name,
gated on Manage invoices, and link straight to the invoice's detail page.
- The invoice list gets row checkboxes and a selection toolbar: **Export
CSV downloads the checked rows in V1's seven columns (Invoice #, Customer, Estimate #, Status, Total, Balance Due, Due Date), and Archive to void** voids the checked rows one at a time with a per-row result. V1's other two bulk actions, Mark sent and Mark overdue, are not offered: overdue is derived from the due date and never stored, and a bare status flip to sent is rejected by the database.
- The Reports page adds an Accounts receivable aging card with V1's
five buckets (Current, 1-30, 31-60, 61-90, 90+ days), honoring the page's location filter.
- A daily sweep raises one Invoice is overdue automation event per
invoice the first time it crosses its due date, now selectable as a trigger in the automation builder; the event never repeats for the same invoice unless the due date changes, and only looks within 30 days of the due date.
- A manual Send reminder action on the invoice list sends under its own
template and a freshly minted link; it refuses to send while a payment for that invoice has been pending in the last 15 minutes, so it never cuts off a customer mid-checkout.
Reports hub export parity and revenue over time
- Every card on the Reports page (revenue summary, revenue over time,
pipeline funnel, estimate performance, jobs throughput, maintenance-plan book, top services, holiday-lights shrinkage when present, and accounts receivable aging) now has a CSV/Print export control next to its title, gated on its own Export data permission separate from the View financial reports permission that gates the page. Every export formats the same already-filtered data the card is already showing, never a fresh query. Filenames follow V1's exact {type}_{date}.{csv|html} pattern, and CSV cells are formula-injection-neutralized.
- The Reports page adds a Revenue over time table below the summary
cards, breaking out booked, collected, refunded, and net figures by calendar month. Its CSV export uses V1's exact revenue_report columns, Period, Revenue, and Estimates.
- The Top services export matches V1's real
service_mixcolumns,
Service, Count, Popularity %, and Avg Price, not the fuller breakdown the older docs described.
- Follow-up fix: Popularity % on the Top services export is now computed
over every accepted service line in the range before the card's own top-20 cutoff, so it no longer inflates once a range has more than twenty distinct services. The accounts receivable aging card, which had no export before, gets one (Bucket, Invoices, Balance, plus a Total row). Formula-injection neutralization across every CSV export (reports, invoices, and pipeline) now also catches a formula hiding behind leading spaces or tabs, not just one sitting at the very start of the cell.
Rehash queue, cadence, and recovered-close attribution
- The Rehash queue is live at Rehash in the sidebar: the same nine-stage
win-back sequence as V1 (Day 1 through Day 180, Day 7 selected by default), the same eight-code disposition taxonomy with required notes on HOT, PRICE, TIMING, and DEAD, and the same Red Line room badge. It requires Red Line enabled, Rehash Department enabled, and the signed-in teammate's own Rehash access toggle, set per member on Settings > Team > Permissions.
- Day 21 and Day 90 still offer the optional 10% and 15% win-back discounts
and still get suppressed if either would push a service below its Red Line floor. Automated stages currently send by email; Day 3, 21, and 90 carry their SMS copy over as the email body until a Rehash SMS channel exists.
- Re-Quote clones the estimate and keeps it linked to the same Rehash
case. The drawer's quoted price is read-only with a link to the full quote editor; it does not yet show contact info, suggested SMS/email drafts, or call logging, since none of those have a V2 data source yet.
- Recovered closes save a commission split by how long the case took to
close, matching V1's four bands by default. Those bands are now editable per organization at Settings > Rehash commission bands, and a band edit never rewrites an already-closed case's saved split. The split shows on the estimate detail page, the Rehash analytics report, and the rep's own earnings view.
Commercial deal templates protect site identity
- Saving or applying a Commercial Templates template now strips every
property-identity and site-condition field (address, city, state, zip, story and building counts, total square footage, water source, drainage, gate code, access notes, site notes, and preferred season) so a template built for one property can never stamp that property's address or square footage onto a different customer's estimate. Applying a template also assigns fresh IDs to its zones and line items.
- Save as Template now lives on the estimate detail page for commercial
(Fleet or Building) estimates, matching V1's fastest template-creation path.
Client health, custom fields, and lead source
- Every client now shows a health badge (Active, At Risk, Dormant, or New)
computed from completed jobs, matching V1's 183/366-day thresholds. The Clients directory adds a Health filter and badge, and a company portfolio's health rolls up its visible service sites.
- Organization-wide custom fields (text, number, date, or single-select) and
a Lead source field are available on every client, editable from a Custom fields card on the profile and from Edit client.
- The Clients directory adds bulk Activate/Deactivate and a client-side CSV
export alongside the existing bulk tag add.
- The client profile adds a Message action that starts or reuses the
one conversation for that exact client, a Communication tab listing every conversation for the client, and a property CSV import with a per-row report.
Real-time inbox alerts and portal message attachments
- The web app now raises a toast and sound for a new inbound text, email,
call, or voicemail, checking on the same 20-second cadence as the unread badge, restoring V1's Inbox alert behavior. A desktop notification also appears when the tab is hidden and the browser allows it.
- The customer portal's Messages tab now shows photo and document
attachments on both sides of the conversation, matching the office Inbox: a photo renders inline, and an emailed PDF shows as a named download chip.
Same-day payment void, office keyed-card collection, and saved-method admin
- A Void action next to Refund in a payment's history covers a
settled, non-adjustment, unrefunded direct invoice payment that has never been voided. A Stripe card payment can only be voided within 24 hours and only before it captures; once it has settled, the void is refused with V1's own message, "This payment has settled, use Refund instead." A tipped payment's tip is voided along with it.
- Office card collection reuses the existing hosted Stripe Checkout link
instead of a new in-app card form, and now always opens as a guest session for the invoice's exact balance. The session is never attached to the client's saved-card wallet, so a copied link can never see or charge a saved card; a card only reaches a client's wallet through the client's own action in the signed-in portal.
- The client profile's Payments tab adds office saved-method
administration: brand, last four digits, and expiry only, viewable by any teammate who can manage invoices, with removal gated on Issue refunds.
2026-09-04
Tag management and exclude-tag audiences
- Settings > Tags lists every tag with its color, state, and how many
clients you can see carry it; tags can be renamed, recolored, archived, and reactivated. An archived tag stays on the clients that have it and can be removed from them, but nobody new gets it, not even by typing its name into a tag picker. A rename is refused while a campaign audience references the tag.
- Campaign audiences can Exclude customer tags; an excluded tag wins
over an included one.
Carried-forward deposit refunds and invoice storage usage
- The invoice refund panel can now refund a deposit carried forward from
an accepted estimate, labeled Deposit carried forward. It refunds the original estimate deposit payment through the rail that collected it, up to the unrefunded credit, partial amounts included, but only when the deposit is fully applied to that invoice; otherwise the panel says to refund it from the estimate deposit instead. A failed deposit-credit refund retries from the same panel, the same way a failed invoice-payment refund does.
- Invoice attachments now soft-delete: deleting one keeps it recoverable
for 30 days before a daily job purges it for good.
- A new Settings > Storage and Attachments page reports stored file
count and total bytes, broken out by source (invoice attachments, estimate photos, job photos, message attachments) and totaled, scoped to what the viewer can see.
Company portfolio tabs, metrics, and self-service grouping
- A company portfolio profile now has Estimates, Leads, and
Activity tabs rolling up the company's own directly-attached records together with every visible service site's, capped at the most recent 200 for Estimates and Leads and the most recent 50 for Activity. When a cap is hit the tab shows a notice: "Showing the 200 most recent. Older records are not listed here." (50 for Activity).
- The profile adds Lifetime value and Open balance tiles alongside
the existing Open leads and Open pipeline tiles. All four now share one scope, the company itself plus its visible service sites, including tips and the company's own retained payments, leads, and invoices. The Clients directory row's customer-payments figure agrees with Lifetime value; its separate open-lead and open-pipeline columns still roll up visible sites only.
- Group sites (on a standalone client, single or bulk from the Clients
directory), Detach from company (on a site), and Ungroup (on a company) are now self-service actions from the Clients workspace. Grouping errors show a plain sentence instead of a raw error code.
Estimate numbers on every creation path
- Settings > Record Numbering now controls estimate numbers too, not
just job numbers. Estimates get a number on every creation path, including the web New estimate flow, the holiday-lights bulk re-quote, and estimates created offline on mobile: an offline estimate is numbered when its upload reaches the server, never on the device, so two techs working offline can never collide on the same number.
- Editing any record-numbering format now needs Manage team with
access to every location; everyone else who opens the page sees every editor read-only.
- A record created before its type started allocating numbers shows
Legacy estimate (or Legacy job) instead of an invented number. Date segments and reset boundaries run on UTC, and changing a reset policy carries the counter forward so a number already handed out is never reissued.
2026-09-03
Suds Club membership changes, refunds, and billable renewals
- The plan page can Change tier on a Suds Club membership. The dialog
prices the change for the days left in the term, bills an upgrade once as its own invoice, takes a downgrade off the remaining payments, and records the change on the membership's history.
- Membership payments can be refunded from payment history, and a renewed
monthly membership now gets its own payment link instead of being created with no way to collect.
Maintenance plan visits, caps, notices, and templates
- Plan visits can be skipped (with a reason, never billed or reminded)
or rescheduled from the plan page; plans take a visit cap and a renewal cap; auto-renewing plans send one renewal notice thirty days before the term ends; Start from template builds a plan from an org template, with a system set seeded per workspace.
Commercial properties, site conditions, and program terms
- Commercial estimates carry property identity and address, **Site
Conditions (office only), per-property Price Book line items, and a program panel with Portfolio Discount, Contract Term, Payment Terms, and First Clean Premium**. The quote page and PDF list each property with its frequency and line items and add a portfolio summary for more than one property.
Holiday Lights material demand and seasonal re-quotes
- The inventory catalog page has a Material demand section that sums the
wholesale materials on Holiday Lights estimates by location, month, and SKU, against on-hand stock and reorder points.
- Re-Quote for New Season on the Estimates list renews selected
prior-season Holiday Lights estimates for a target season with a percent price increase, as drafts or sent, skipping sources already renewed.
Proposal photos and checks before sending
- A Photos panel on every estimate attaches project photos (JPEG, PNG,
or WebP up to 8 MB) that are frozen with the sent proposal, shown to the customer as a captioned grid, and printed on a Project photos PDF page.
- Checks before sending lists what the pricing engine flags (missing
customer or property, a $0 service, a service under its Red Line floor, a package one service away) without blocking Send.
Proposal Options A/B/C
- The office can author its own proposal options on any estimate except
Holiday Lights: up to 12 choices with line items, discount, tax, and one Recommended, edited in the Proposal Options panel and read-only once the proposal is accepted or signed. Sending an estimate with saved options replaces the generated As requested and Complete Property Care choices; Suds Club is still offered with a maintenance plan.
Price Book add-ons on residential estimates
- The residential estimate builder has an Add-Ons section listing Price
Book items of kind Add-on that are offered in the quote picker. A checked add-on takes a Price override ($) and a Customer description seeded from the catalog; the description reaches the customer's quote page and the PDF with an Add-on badge. Add-ons saved by the earlier builder now price correctly instead of at $0.
2026-09-02
Margin calculator on commercial and Holiday Lights estimates
- The margin card now appears on commercial and Holiday Lights estimates for
teammates with Manage pricing, not just residential ones. Commercial estimates show Whole property and every zone; with hourly crew pay each zone's crew hours can be overridden and are frozen when the estimate is sent. Holiday Lights estimates show Selected package costed from the design's wholesale labor and materials, with nothing to type in.
- The pricing settings switch reads Enable margin calculator.
Price-book thumbnail upload
- The price-book item editor uploads a Thumbnail photo (JPEG, PNG, or
WebP up to 5 MB) straight to storage and checks the file before the item shows it; the catalog list shows the photo, or a pasted URL when there is none, and Remove photo takes it away.
Build a maintenance plan from a commercial estimate
- A saved commercial estimate with Quarterly or Semi-Annual zones shows a
Maintenance plan card in its pricing summary. Build maintenance plan (Manage plans permission) creates a one-year, per-visit plan linked to the estimate with its first-year visit jobs, one per season, and opens it. One estimate gets one plan.
- Copy for Workiz on the plan page copies the plan as plain text (customer,
address, visits with month and price, total) for a manual paste into Workiz.
Customer tips on invoice payments
- Settings > Payment collection now sets the three customer tip choices
(a percentage of the taxable subtotal or a flat amount; No tip is always the fourth). A workspace that never saved them offers 10%, 15%, and 20%.
- The public invoice page and the signed-in portal offer the choices before
Pay; the office collect panel offers them for both a recorded payment and the secure card checkout. The tip is charged on top of the invoice amount, never counts toward the balance, is printed on the receipt as its own line, and shows in payment history.
Generate invoices for several jobs
- The Jobs list takes a selection of scheduled, in-progress, or complete
jobs and Generate invoices makes one draft per job for the accepted price (keeping the accepted option's tax breakdown when it reconciles, otherwise one line with tax left for the office), each on its own, with every skipped or failed job named and its reason shown. Monthly plan visits and prepaid retention work are skipped. Up to 25 jobs a batch; drafts wait under Invoices.
Invoice attachments in V2
- Every office invoice page has an Attachments section: photos,
permits, signed approvals, purchase orders, and supporting files, up to 20 MB each and 50 per invoice, in V1's thirteen file types. The file's real type is read from its contents before it is saved, so a renamed file is refused instead of stored.
- Anyone who can open the invoice can open a file; uploading and deleting
need the manage invoices permission. Customers never see attachments.
Payment Collection in V2
- Settings > Payment collection is live for the office: Cash, Check,
ACH / bank transfer, and External card are always on, and any of Zelle, Venmo, Cash App, PayPal, or Financing can be turned on as other ways to pay. A workspace that never saved the page has Zelle and Financing on.
- The Collect payment panel on a sent invoice lists the workspace's
methods instead of a fixed four, gained an optional note, and the server checks the saved list again on every recorded payment.
- Receipts and invoice PDFs print the method's name as it read when the
payment was recorded, so a later settings change never rewrites history.
Workiz in V2
- Settings > Integrations > Workiz is live as the cutover wizard: connect
the workspace's Workiz account (the credentials are checked against Workiz and kept where only the service reads them), copy the webhook endpoint and signing secret into Workiz, and choose the sync switches.
- Workiz leads arrive through a Workiz lead connector, from the webhook or
Pull & Preview Leads; a repeat replays, an incomplete lead waits in the Review Queue.
- Migration previews and imports clients and leads from the Workiz API or
a CSV export, with dedupe against what the workspace already has, and the owner cuts over with Enable Native CRM And Disable Workiz Sync.
- A sent proposal pushes to Workiz as a lead when outbound sync is on, with a
retry queue (three attempts, 1 and 4 minutes apart) and Retry Failed on the Sync activity tab; a cancelled Workiz job declines the estimate it came from.
Meta Lead Ads in V2
- Settings > Integrations > Meta Lead Ads is live: click Connect Meta,
approve the Meta login, pick the page if the login manages several, and CE Pro subscribes the page to lead forms and creates the workspace's Meta lead connector. The page token is kept where only the service can read it; Reconnect Meta refreshes it and Disconnect forgets it.
- Every Meta lead form submission arrives through the shared signed
webhook, is recorded as a Received import run and acknowledged at once, fetched from Meta with the page token by a background job, and creates the lead the office would; a redelivered lead replays, and a form that leaves out a name, a contact method, or a service address waits in the Review Queue.
- The card says Needs Env until a platform admin configures the shared
Meta app for the environment.
Google Lead Ads in V2
- Settings > Integrations > Google Lead Ads is live: enter an optional
Form ID and a Lead Source Label, click Enable Google Lead Ads, and paste the shown Webhook URL and Google Key into the Google Ads lead form webhook settings. Google's standard columns map onto the lead fields on their own, custom questions become notes, lead_id replays instead of importing twice, and Google's webhook test is logged without creating a lead.
- The Google Ads connector lives under Lead Connectors like every other
source, so aliases, an assigned rep, a notes prefix, and the Review Queue apply to it as well.
Lead Connectors in V2
- Settings > Integrations > Lead Connectors is live for members with the
manage integrations permission and access to every location: create API, website form, and email-forward connectors with a display name, status, source label, assigned rep, notes prefix, email review threshold, and field aliases; run a test import; activate, edit, rotate the connector key, or delete; approve or reject queued imports in the Review Queue; and read the last 50 payloads in Recent Imports.
- Every connector creates the lead the office would: a client, a service
property, the pipeline's first stage, and the lead.created event. A payload missing a name, a contact method, or a service address waits in the Review Queue instead of failing.
POST /api/v1/leadsacceptsconnector_keywith a live key; an incomplete
payload answers 202 into the review queue. Website forms post to /api/public/lead-connectors/{connector key} (JSON or form posts, open CORS, rate limited). Forwarded emails reach an email connector's own forwarding address once inbound routing is configured, with parser confidence deciding whether a person reviews first.
Outgoing webhooks in V2
- Settings > Integrations > Zapier / Make Webhooks is live for members
with the manage integrations permission and access to every location: add endpoints with a name, HTTPS URL, and event list; pause, test, edit, rotate the signing secret, or delete them; and review recent deliveries.
- Eight events fan out to subscribed endpoints:
lead.created,
lead.status_changed, estimate.created, estimate.accepted, job.created, job.completed, invoice.created, and invoice.paid. Deliveries are signed with HMAC-SHA256, sent to the address the safety check resolved, and retried after 10 seconds, 60 seconds, and 5 minutes.
/api/v1/webhookslists, creates, reads, and deletes endpoints with a
live key; the secret is returned once at creation.
2026-09-01
External API keys and the first /api/v1 surface in V2
- Settings > Integrations is back in V2 as a hub of integration cards.
Members with the manage integrations permission can open it from Settings. The External API card is live; the Workiz, Meta Lead Ads, Google Lead Ads, Lead Connectors, and Zapier / Make Webhooks cards are listed as coming.
- The External API keys page creates scoped live and test keys, shows the
raw key once, edits scopes, rate limits, and expiry, revokes keys, and shows recent request logs per key. Every key is bound to the member who created it, and a revocation is final.
/api/v1now serves reads of leads, clients, estimates, jobs, and invoices
with cursor pagination, creates leads and clients, updates clients, and publishes /api/v1/openapi.json. Writes require an Idempotency-Key and replay safely on retry. Test keys work against isolated sandbox records.
- Estimate, job, and invoice writes, lead updates, and job notes are not in
V2 yet and answer 405 write_not_supported with a live key (notes work on lead creation and on client creation and update). The developer pages mark what is live.
2026-08-31
Separate, reproducible V2 mobile preview builds
- Created a dedicated cleanestimate-pro-v2 Expo project so V2 previews
cannot overwrite or publish through the existing V1 mobile project.
- Added repeatable internal Android APK and iOS simulator build profiles. The
Android artifact is intended for approved physical-device testing; the iOS artifact is simulator-only until the separate Apple signing and TestFlight gate is completed.
- Added a durable GitHub Mobile preview lane for the Android APK while the
Expo Android cloud builder is affected by an upstream native-module variant resolution failure. The lane requires the API, Clerk, and PowerSync preview endpoints before compiling, produces an authenticated GitHub artifact, and rejects the package if the final manifest contains microphone permission.
- Fixed the iOS production bundle failure caused by missing Metro async-chunk,
Apple authentication, and native navigation peers while preserving the repository-wide React type/toolchain versions required by the web build.
- Pinned the EAS Node and pnpm toolchain and added deterministic Android Expo
module autolinking so clean cloud builds use the current Expo namespace.
- Prevented the image picker from adding an unnecessary Android microphone
permission in both generated configuration and the checked-in native manifest.
- Verified Android and iOS production exports, the web production build,
mobile typecheck and lint, and all 81 mobile tests before cloud release.
- Changed no visible mobile layout, navigation, control, label, styling,
information hierarchy, or interaction. The approved V1-derived mobile design remains frozen.
2026-08-30
Inbox and Phone permission compatibility foundation
- Added five centrally defined permission records for future Inbox viewing and
management, Phone viewing and recordings, and assigned-rep coaching acknowledgement, with documented Owner, Administrator, Office, Sales representative, and Crew defaults.
- Added versioned compatibility reads and complete writes that preserve every
explicit saved value. Unknown or malformed legacy records are quarantined for repair rather than silently changed.
- Added a resumable, organization-scoped backfill with durable old-writer drain
evidence, stable progress, repair tracking, completeness checks, and an irreversible strict marker. Strict records reject missing or unknown keys, and old-format writes are blocked after cutover. Database-level serialization now makes concurrent member saves and strict cutover resolve safely in either order, including direct database writes.
- Limited repair evidence to canonical permission booleans, byte counts, an
error category, and a non-reversible diagnostic hash. Arbitrary malformed, oversized, or storage-compressed legacy payloads are not retained in the repair queue. Compressed documents are conservatively quarantined without decompression or permission enumeration, repaired entries are removed immediately, and unsafe entries cannot stall backfill progress.
- Fixed Reset permissions so a complete role-default record left on the
legacy version is still rewritten and stamped with the current permission version instead of being mistaken for an up-to-date no-op.
- Kept the five Team controls hidden and left current Inbox and Phone route
authorization unchanged. This release is compatibility-only migration groundwork and makes no user-facing route or permission-control change. Native mobile layout, navigation, controls and labels, styling and information hierarchy, and interactions are unchanged.
Promotions and discount codes
- Ported the V1 web-office Promotions workspace to V2's tenant- and
location-scoped services. The page provides office filtering, active, scheduled, and redemption summaries, detailed promotion cards, and permission-gated create, edit, activate, deactivate, and archive actions.
- Added percentage and flat discounts; optional inclusive start/end instants;
all-office or selected-office scope; authoritative active-service restrictions; residential, commercial, or all-customer eligibility; minimum estimate age; total and per-client caps; and controlled public proposal entry.
- Added Draft, Active, Scheduled, Expired, Exhausted, Inactive, and Archived
states. Derived schedule/expiry/exhaustion status does not rewrite saved terms, while deactivate and archive retain redemption history.
- Locked terms and location scope after the first redemption. Stale edits,
status changes, and archive requests use version checks and fail instead of overwriting a teammate's newer change.
- Added customer-entered codes to eligible unsigned sent proposals. Unknown,
inactive, ineligible, and exhausted codes share one non-enumerating error; the rate-limited endpoint is private/no-store.
- Enforced one code per estimate with no promotion stacking. Applying a code
creates one new immutable proposal revision and records exact code, terms, source/result revision, per-option totals, discount cents, and idempotency evidence in the same transaction.
- Defined the W37 multi-option contract as any-service eligibility with
whole-option pricing: any matching authoritative service makes the service rule eligible, then each proposal option receives its own percentage or flat calculation from that option's complete total.
- Kept ordinary options above their retained service floors. Promotions fail
closed for proposals containing a maintenance-tier option because those cadence snapshots do not yet retain a promotion-aware floor envelope; the office can issue a separately reviewed plan revision instead.
- Added truthful card evidence for redemptions, unique clients, and total
recorded discount. These values do not claim campaign attribution, acceptance, collected revenue, profit, or ROI, and later proposal or payment lifecycle changes do not erase the original redemption.
- Intentionally omitted V1's displayed Auto-Apply switch because it had no
complete authoritative runtime. W37 requires explicit code entry rather than silently choosing and applying an offer.
- Bound campaign offers to a real authorized promotion. Campaign copy uses
{{promotion.code}}; approval freezes the selected code and promotion version, and delivery fails closed if that record changes or becomes unavailable instead of sending a stale or fabricated offer.
- W37 is migrated and deployed in production with its matching documentation.
Authenticated office/customer/provider proof still requires an approved synthetic QA organization and safe recipients. No native mobile layout, navigation, label, or interaction changed.
Marketing campaigns and win-back workspace
- Ported the V1 web-office Marketing workflow to V2's tenant- and
location-scoped services, with Campaigns and Win-back templates tabs, status/channel filtering, a guided Setup/Audience/Content/Performance editor, and read-only history for teammates without management access.
- Added one-time, ordered drip, and durable event-triggered campaign contracts
for email, SMS, or both. Mixed-channel drips use one stable recipient enrollment instead of treating an email address and phone number as different sequence identities.
- Added the V1 audience catalog: service history and recency, lifetime spend,
maintenance plan state, lead source, estimate/proposal state and lookback, ZIP, property type, customer health, and CRM tags. Preview returns a bounded sample and consent exclusions. Approval rebuilds and freezes one-time/drip recipients server-side; triggered campaigns freeze each eligible recipient when its durable event enrolls. Proposal status lookbacks use the recorded matching-status date, so an old decline or expiry cannot pass a recent-window filter.
- Added four V1-aligned draft-only starting recipes: We Miss You! (90 days),
Seasonal Reminder (120 days), Declined Proposal Follow-Up (days 7/14/30), and Referral Request (3 days after service). Using a template does not activate or send it.
- Preserved the V1 template names, channels, delays, and audiences while
documenting three safety corrections: legacy markup becomes plain text without a broken # CTA, referral copy does not fabricate an unconfigured discount, and a V1 "triggered" label without an executable event worker is implemented as a functioning audience-driven drip.
- Added immutable approved revisions, scheduled delivery, ordered step delays,
previous-email open/click conditions, pause, resume, cancel, archive, and durable recipient/step/channel idempotency. Preview and test are structural no-send operations.
- Bound final approval to a server-prepared draft revision and recipient
fingerprint. Draft or audience drift forces a fresh review instead of silently approving different content or recipients.
- Campaign delivery uses the V2 marketing-consent ledger, current-recipient
checks, channel preferences, SMS STOP state, unsubscribe support, location timezone policy, provider-bound pause/cancel checks, and retained suppression evidence.
- Customer replies, new jobs or lead appointments, and opt-outs exit the whole
enrollment before a later step. The provider guard rechecks that state under a row lock. Fair worker bounds cap each run to 20 enrollments per organization and 10 per campaign/channel, with 10-minute leases and three attempts per step.
- Completed non-triggered campaigns after every frozen enrollment reaches a
terminal outcome, including exhausted failures. Triggered campaigns remain active after current enrollments finish so later durable events can still enroll eligible customers.
- Enforced an absolute three-attempt delivery cap across provider failures and
expired worker leases. A fourth provider dispatch cannot occur, and terminal failure is recorded once. A paused campaign stays paused; resume atomically completes a non-triggered campaign with no pending enrollment, while a triggered campaign resumes active for future durable events.
- Performance distinguishes queued, sent, delivered, opened, clicked, replied,
suppressed, failed, and exited work. Delivery/open/click reporting uses canonical provider-backed message evidence when supported. Conversion stays hidden until a durable business-outcome projection exists.
- Required a signed-in office session for the Marketing workspace and its
nested URLs. Marketing API responses are private and use no-store caching so campaign and audience data is not retained by browser or shared caches.
- Made campaign location a creation-time choice. Existing campaign drafts show
their saved location without offering a divergent edit; targeting another authorized location requires a new campaign, preserving revision, audience, consent, and delivery scope.
- Released Marketing as a web-office feature. No native mobile UI changed.
Reusable linear automation builder
- Expanded the web Automations workspace from the bounded quote-follow-up
slice into reusable Workflows, Templates, and Runs. Workflows use one typed trigger, up to ten optional all-must-pass entry conditions, and up to thirty ordered actions. This release remains linear and does not claim V1's branching canvas.
- Added five standard recipes, provisioned as Draft, Test, and
Disabled: Estimate sent quote follow-up, appointment reminder, on my way, review request, and seasonal rebook. Existing quote-follow-up definitions and their established copy/timing are preserved during upgrade.
- Added waits (including appointment/job/seasonal anchored timing), email, SMS,
customer tag changes, internal team notifications, and task creation. The trigger catalog now covers the displayed estimate, appointment, job, invoice/payment, maintenance, seasonal, and controlled manual events.
- Connected the durable invoice-payment event to Payment is received and
retained its invoice/customer context. Maintenance-plan creation now carries the owning customer ID into Maintenance plan is created, so these builder triggers resolve a real tenant-owned subject instead of remaining catalog-only options.
- Added entry-condition support for customer vertical, estimate amount,
customer tags, location, and pipeline stage, plus the editor's pending, active, contact-channel, and unpaid presets.
- Added immutable revisions, duplicate, archive, preview, test dry run,
activate, and pause. Preview/test remain no-send operations. Pause is rechecked at provider dispatch boundaries rather than only when a run first enrolls.
- Added run monitoring with durable ordered outcomes, operator cancellation,
bounded retry lineage, retained cancellation evidence, and idempotent event, enrollment, and action identities.
- Manual enrollment is limited to eligible active lead-appointment,
appointment, and job workflows. Quote follow-up continues to enroll from the reviewed durable proposal-send event; it cannot be manually started with an arbitrary estimate ID.
- Customer channel/marketing preferences, SMS opt-out, current-recipient
checks, business hours, and location/workflow timezones remain enforced at execution. Legacy review outreach rechecks active linear ownership after its wait to prevent a duplicate send during activation races.
- This entry documents the completed W34 release scope; it does not claim that
the production deployment has already completed. No native mobile layout, navigation, label, or interaction changed.
Estimate Sent quote-follow-up automation
- Added the first bounded V2 Automations workspace to the web office. It
contains one workflow and included template: Estimate sent → Quote follow-up. The initial proposal remains an office-reviewed manual send; automation begins only after that successful durable proposal event.
- Owners and admins with Manage automations can configure one wait in hours
or days, email and/or SMS copy, safe merge fields, and an optional daily business-hours window and timezone. View automations provides read-only access to workflow state and recent run history.
- Save draft creates an immutable test revision. Activate publishes a
separate live revision for future enrollments, and Pause prevents new enrollments without rewriting settings, history, or in-flight runs. Each run retains the exact revision that enrolled it.
- Added Preview and Test dry run with sample data. Both are structurally
no-send operations: they do not contact email or SMS providers and do not create customer messages.
- Added durable, retry-safe enrollment and step history for wait, email, and
SMS outcomes. A run stops before another customer message if the proposal is accepted, declined, expired, withdrawn, or superseded, or when the customer already replied or booked. Redelivery of the same proposal event cannot create a duplicate run.
- Limited V2 automation copy to customer first/full name, company name, stable
estimate reference, and the secure proposal link. V2 does not invent a sequential estimate number; the V1-compatible number token renders the same truthful stable reference.
- This is the Estimate Sent → Quote Follow-Up slice, not completion of V1's
full automation builder, trigger/action catalog, conditions, canvas, manual enrollment, or analytics. No native mobile layout, navigation, label, or interaction changed.
Team management, invitations, permissions, and activity
- Added dedicated Settings > Team > Members, Permissions, and
Activity pages. The Members roster provides search plus Active, Inactive, and All members views for joined teammates. Pending invitations remain visible in a separate lifecycle block with their own delivery state and actions; they are not part of the roster filter.
- Durable invitations preassign the teammate's company profile, non-owner
role, All locations or one pinned location, and one optional eligible crew. Resend access email and Revoke invite operate on the same durable pending row instead of creating duplicate membership attempts.
- Full name, verified login email, and password remain under Clerk's protected
account flow. The Team editor keeps name and email read-only while allowing an authorized teammate to manage only local phone, job title, and private internal notes. Owner is protected from assignment, and role changes confirm before reseeding that member's V2 permission defaults.
- Team mutations require Manage team, reject self-change, protect owner rows
from editing and deactivation, and remain confined to the manager's current location scope. An all-location owner can also edit another non-owner's individual permission grants. Non-owner managers cannot edit individual grants, and pinned managers can invite and manage non-owner members only inside their persisted location; they cannot inspect sibling-location rows, create all-location or sibling-location invitations, or broaden a member to All locations. Reactivation is the narrow owner-row exception: an all-location owner may reactivate another inactive owner after provider verification.
- Each member remains limited to All locations or one pinned location and
to one eligible crew or no crew. Cross-location crews, sibling-office escalation, and location or crew changes that conflict with protected future work fail closed until the office resolves the assignment.
- Deactivate requires a retained reason of up to 500 characters and
preserves the teammate's profile, assignments, permission record, and historical work. Reactivate restores local access only after Clerk confirms the matching organization membership. If the provider membership was removed, the office must reinvite the same verified email after provider removal reconciles; the durable member history is reused rather than creating a second active teammate.
- Invitation send, resend, revoke, acceptance, and membership-removal outcomes
reconcile against Clerk without guessing. Ambiguous provider results remain reviewable and require reload before another action; a stale deletion event for an older provider-membership generation cannot deactivate the newer membership. Clean Estimate keeps removed-access evidence and restores access only when a newer Clerk membership is tied to a still-active invitation. Missing, older, duplicate, or conflicting provider evidence keeps the teammate inactive, so direct provider re-adds and invitation-revoke races cannot silently restore old roles or permissions.
- The bounded, newest-first Activity feed includes team access changes,
estimate creation, every proposal revision send, aggregate recorded proposal views, exact revision/option acceptance, decline, genuine expiry, and lead pipeline stage history. Member and action filters remain location-scoped. Customer, system, automation, API, agent, and unknown actor labels are used when the stored evidence supports them; mutable assignment is never used to invent a historical teammate actor, and login events are not claimed.
- The approved native mobile layouts, navigation, labels, and interactions
remain unchanged.
- Updated the help navigation to present Adding Your Team and **Team
Management** as shipped guides instead of preview pages.
2026-08-29
Account, company identity, and immutable customer-document branding
- Added My Account under Settings for each teammate's full name, phone,
and job title, with read-only role and login email plus Clerk's verified email and password management flow.
- Expanded Settings with company phone, email, website, mailing address,
default location, reporting timezone, logo, primary color, and standard terms. Company-wide edits require Manage Team and all-location access; other teammates receive a read-only view.
- Added revision-safe account and company saves, bounded validation,
actor-attributed company-profile history, same-organization active-location enforcement, and a clear guard that requires choosing another default before deactivating it.
- Hardened logo uploads by checking the real PNG, JPEG, or WebP image bytes,
bounding the input and decoded image, removing metadata, storing an immutable normalized WebP, verifying storage before activation, and safely cleaning up only a never-activated upload after a failed settings save. Activated logos remain available to retained historical documents after replacement. Public logo delivery now throttles by visitor instead of sharing one global asset budget, and metadata-only HEAD checks avoid downloading the image body.
- Each newly issued proposal revision now freezes the then-current company
identity; a later estimate resend may use updated Settings while prior revisions remain unchanged. An invoice freezes its company identity on its first draft send, and later invoice resends keep that original snapshot. Supported proposal and invoice customer pages, retained delivery naming, and proposal, invoice, and receipt PDFs render the retained customer-safe subset they need, so Settings changes do not rewrite previously issued customer records. Issuing a new proposal revision revokes its prior secure customer link while retaining the earlier revision's record and generated content.
Standalone web job creation and visible job numbers
- Added a permission-gated New job workflow to Calendar and All jobs for one-time work owned by an existing active customer or service site and one active saved property. The office can leave it unscheduled or provide a date, start, duration, and optional location crew; customer and property creation remain separate governed workflows.
- Made the create command retry-safe across lost responses, revalidated the current teammate, Manage calendar permission, tenant, location, customer, property, and crew inside the transaction, and blocked company portfolio parents, inactive/cross-scope records, and conflicting property lineage.
- Reused the shared calendar-capacity lock and conflict audit. A crew overlap creates nothing on the first attempt; Schedule anyway appears only after that conflict and requires a reason.
- Assigned immutable organization-scoped job numbers atomically to new standalone, accepted-estimate, maintenance-visit, and governed repeat-work jobs. Calendar cards, the unscheduled rail, All jobs, job detail, client history, and search now use the number when present; existing legacy jobs remain unnumbered instead of being silently rewritten.
- Pinned each new job to its exact immutable property and customer and made job, invoice, customer portal, and appointment-request readers prefer that direct scope while failing closed on conflicting estimate, plan, or repeat-work lineage. Legacy estimate or plan records with blank properties remain compatible only when their customer lineage also matches; cross-customer null lineage and later source-customer changes are rejected. No native mobile UI changed.
- Corrected rollover and renewal for older estimate-backed maintenance plans whose saved plan property is blank: every newly generated visit receives the validated estimate property when one exists, conflicting lineage fails before any contract or work change, and plans that never recorded any property retain their existing nullable legacy behavior.
Exact-work rebooking with optional prepayment
- Expanded Rebook this exact work on protected paid invoice detail and customer Work with an explicit pay-later or prepay choice for the frozen issued-invoice total. No charge is preselected, and neither path schedules an appointment.
- Recovered unfinished browser commands created before payment choice existed: CE Pro preserves the notes and decision/request keys, waits for an explicit choice, and keeps a server-resumable prepayment on its immutable prepay path.
- When Checkout or payments are definitively unavailable before acceptance, CE Pro discards only the uncommitted browser command, refreshes server truth, and reopens eligible choices without changing an accepted order or payment.
- Added the independent, audited, default-off Let customers prepay when rebooking the exact completed work setting. Prepay appears only when Stripe, the pinned connected account, and frozen total are eligible.
- Added connected-account Stripe Checkout with exact tenant, customer, property, location, offer, order, amount, and snapshot binding; safe session resume/replacement; and provider-authoritative return, cancellation, async-payment, settlement, and late-success handling.
- Kept every provider success in payment history. The first valid success applies to the order; later successes remain visible conflicts and block fulfillment until pending or successful extra attempts are failed, cancelled, or fully refunded.
- Added Refund attention controls with Manage calendar, Manage invoices, and Issue refunds boundaries. The server derives the remaining amount, connected account, and PaymentIntent; the reason and financial identity stay immutable and are redacted from calendar-only users.
- Made refund recovery durable: ambiguous or provider-pending outcomes reuse the same provider attempt and key, an exact Stripe
failedorcanceledresult lets the next authorized retry advance the same command to one fresh provider-attempt key, and exact registered provider truth that a full refund already completed externally supersedes the matching command locally without a second Stripe request and cancels an eligible linked job in the same transaction. - Made applied partial provider refunds return unmaterialized or active repeat work to Refund attention with the remaining balance. Customers see Prepayment needs office review with no second-payment path; CE Pro blocks job creation, scheduling, rescheduling, start, and completion while allowing scheduled work to return to Unscheduled for review, and cancelled or completed historical work is not re-enqueued.
- Extended governed applied-payment refunds to exact linked jobs that are Unscheduled, Scheduled, or In progress. Stripe runs first; failure leaves the job active and blocked, while success finalizes the ledger and cancels that job with the refund reason and normal audit. Complete and Cancelled jobs cannot initiate it.
- Kept full external applied refunds in Refund attention until an eligible linked job is cancelled: matching unresolved commands reconcile and cancel that job automatically on authorized retry, while cases without a matching command require explicit office review. Late duplicate payments remain visible without allowing a second job or cancelling the valid job.
- Added the location-scoped office repeat-work queue with immutable customer/property/invoice/scope/amount facts. A teammate with Manage calendar can create one unscheduled job only from a ready row; ambiguous Won-stage pipelines record reconciliation attention instead of guessing.
- Added customer return/resume states for awaiting payment, asynchronous processing, settlement, resumable cancellation, and unavailable payment. Existing pay-later, residential Suds Club, and commercial custom-maintenance paths remain available. No native mobile UI changed.
- Added exact-work prepayments and net refunds to the owning customer's Payments history, office client payment history, directory Customer payments, and company portfolio Customer payments rollup with exact tenant/location/order/client/account isolation.
- Bound repeat-work invoices and customer/office schedule views to the retention offer's frozen property, preventing another active or generic property from replacing the accepted service address.
- Prevented applied prepaid repeat-work jobs from creating, retargeting, editing, or first-sending a second invoice. Provider history remains authoritative after refunds, while Pay later work remains normally invoiceable.
Residential Suds Club signup from completed Work
- Added a separately controlled Suds Club action to the protected post-job
Work card for eligible completed residential work. Its new organization switch is independent from invoice payment-offer controls and defaults off; invoice detail keeps its existing organization-controlled offer flow rather than gaining a competing card.
- Showed the one server-priced plan for the exact job, invoice, customer,
property, and residential estimate lineage. Customers see included services, visits and seasons, genuine configured savings, the signup amount, later monthly payments, tax-inclusive year total, and terms; the browser does not calculate a plan or price. The existing invoice Suds Club card now shows the same seasonal service details.
- Opened Stripe-hosted Checkout for the membership signup while keeping the
plan and visits payment-gated. A browser return can show Suds Club signup received while settlement is pending; only a verified Stripe event shows Suds Club is active and releases recurring service.
- Made cancelled Checkout resumable from the same protected Work offer. A
retry converges on the same accepted plan and reuses a compatible live session or rotates a provider-confirmed expired session instead of creating a duplicate membership. If the original offer display window has elapsed, Work exposes only that already-accepted pending signup; it does not revive rebooking or another membership offer. The continuation closes after the verified membership settlement. Opening the continuation from either the invoice or follow-up surface always shows the exact frozen plan and price that created the pending membership, even if the other surface is older.
- Kept that Suds Club rollout's scope boundaries: it did not itself add exact-work prepayment, and commercial customers continued to request a custom maintenance proposal without automatic plan creation or payment. The separate exact-work release above later added the optional, default-off prepay path. No native mobile UI changed.
2026-08-28
Same-work and commercial follow-up requests after completed jobs
- Added an authenticated next-service card to the protected Work destination
for eligible completed jobs. It shows the exact frozen invoice scope, subtotal, discount, tax, original total, and offer expiration before the customer acts.
- Rebook this exact work sends one retry-safe pay-later request into the
office lead workflow. It does not schedule an appointment and does not charge the customer; the office confirms the next visit.
- Commercial customers can choose Request a custom maintenance proposal.
This creates one office-reviewed proposal request, not an accepted plan or a payment. Residential work never receives this commercial choice.
- Extended the existing two-hour post-job review email and text with a
protected Work link to the same available next-service choices. The email-code login preserves that exact completed-job destination, while a changed, replaced, ineligible, or already-requested source safely shows the ordinary Work page without exposing another customer's record.
- Renamed the matching customer notification preference to Reviews and next-service follow-ups
so its email/text controls accurately describe both the feedback request and the optional protected next-service link.
- Kept residential Suds Club signup and invoice-side rebooking in the existing
organization-controlled invoice-payment offer for now, avoiding a second competing invoice action. The new completed-work card does not yet enroll Suds Club, and exact-work prepayment remains hidden until its booking, pricing, checkout, refund, and expiration lifecycle is complete.
- Added stable retry identity, exact organization/customer/invoice binding,
same-origin checks, session-wide and per-offer rate limits, customer-safe response projections, final-record-safe note limits, and conflict handling that never invents a booking or charge. Near-limit timing or access notes are preserved instead of failing after the request reaches the office intake record. No native mobile UI changed.
Guarded post-job retention foundation (not customer-facing yet)
- Added tenant- and location-scoped backend records for future post-job
retention offers. The foundation binds each offer to one completed job, its sent invoice, the exact invoice lines and amounts, the customer and property, and the authoritative vertical saved on the linked estimate.
- Added internal, retry-safe service support for a pay-later repeat-work request
and an office-reviewed commercial maintenance proposal request. Source, session, eligibility, presentation, and duplicate-request drift fail closed, and concurrent retries converge on one durable result.
- Corrected customer-originated lead routing so service requests and future
retention acceptances resolve their pipeline, routing rule, and entry stage only inside the customer's exact organization. Another workspace's default or vertical pipeline can no longer make an otherwise valid request fail.
- Reserved a separate future-service prepayment lifecycle with one-charge and
late-settlement refund safeguards, but no retention checkout or prepay acceptance path is available. Residential Suds Club acceptance through this new post-job retention foundation also remains disabled; eligible signed-in customers can still join through the existing invoice payment offer.
- Hardened that reserved prepayment lifecycle so a confirmed full refund
reconciles an internal order from awaiting payment, payment settled, fulfilled, or refund required into its cancelled state. Newly cancelled orders record cancellation confirmation separately while the payment keeps its first-refund timestamp; cancelled pay-later orders cannot carry invented settlement or fulfillment history, and awaiting-payment prepay settlement history requires exact full-refund evidence. A late payment success racing with cancellation still records the required refund without opposing locks. Once the order is cancelled, exact repeated failed, cancelled, or refunded provider outcomes are accepted safely, but changed financial facts are rejected.
- That backend-only foundation release did not add invoice or Work CTAs,
post-job messages, Suds Club enrollment, commercial plan creation, or any native mobile UI. Existing invoice payment offers and their current Suds Club, request-a-time, and next-service prepayment flows are unchanged.
Customer account links survive email-code sign-in
- A customer who opens a link to a specific signed-in account page now returns
to that page after completing the eight-digit email-code sign-in instead of always landing on account Home. Opening /customer/login directly still opens Home after verification.
- The return destination is held only for the short-lived sign-in attempt,
rechecked when the session is created, and then cleared. Only relative /customer account destinations are accepted; external, protocol-relative, malformed, encoded-bypass, backslash, control-character, and altered destinations safely fall back to Home.
- This is secure-link navigation groundwork. It does not add post-job repeat
booking, prepayment, Suds Club enrollment, or commercial maintenance offers, and the approved native mobile UI is unchanged.
Versioned weighted scorecards for call reviews
- Added a web Call scorecard editor under **Settings > Company calls,
recording, and voicemail for teammates with Manage phones**. A custom rubric accepts 1–10 criteria whose weights must total exactly 100.
- Added Passing score and Low-score threshold boundaries from 0–100.
The low-score threshold cannot exceed the passing score, and both boundaries are saved with the rubric version that produced the review. Scores are green at or above passing, amber from the low-score boundary up to passing, and red below the low-score boundary.
- Publishing an edit creates an immutable scorecard version. Each review binds
to its exact criteria, weights, thresholds, and wording; later settings edits cannot rewrite old results. Automatic retries of the same queued revision keep their binding; selecting Retry review after a failure creates a new revision that uses the scorecard active at that time.
- Organizations without an active custom scorecard continue using the built-in
Rapport, Discovery, Clarity, Objection handling, Next steps, and Professionalism rubric. Existing default and legacy reviews keep their saved results instead of being assigned to a new custom version.
- Publishing a scorecard does not automatically re-review completed calls.
Manager calibration, automatic post-call alerts and tasks, and live scripts remain separate work. Version browsing, restoring an earlier version, and switching back to the built-in rubric after custom activation are not in the current editor. The native mobile UI did not change.
Suds Club payment offers require residential completed-work lineage
- Restricted the signed-in invoice payment offer to an authoritative completed
source job whose linked estimate is residential, with coherent organization, service location, customer, and property lineage.
- Commercial, generic, Holiday Lights, missing, mismatched, and ambiguous
sources now suppress Suds Club. An unrelated historical estimate or customer label cannot substitute for the completed job's saved vertical. The approved native mobile UI is unchanged.
Let customers book an estimate visit from a secure lead link
- Added Online self-booking controls to web Settings for appointment
duration, minimum notice, booking window, and per-day hours. Times follow the lead location's timezone, including fail-closed handling for invalid and DST- changing wall times.
- Teammates with Manage calendar can create a one-time-visible secure link
from an eligible unscheduled lead. Only a SHA-256 digest is stored; replacing the link immediately invalidates the previous one, and each link expires with the configured booking window.
- The public page shows only the organization, customer name, service address,
timezone, and safe availability. It is no-store, rate-limited, rejects cross- origin booking posts, and never exposes staff IDs, contact details, internal notes, or token hashes. It stays outside the private app shell even when the visitor is already signed in, and invalid app-link configuration is rejected before a token can be issued without being shown.
- Reassigning a lead to another customer or service property now revokes its
active booking link, preventing the former customer link from exposing or scheduling the replacement customer's visit. Link creation also requires a valid HTTP or HTTPS app origin before it writes the token.
- Public booking reads revalidate the token and customer context atomically, so
a request racing with replacement or reassignment cannot cross into the new customer's details. The lead page also removes its one-time raw-link display as soon as it observes that the link was revoked or replaced in another tab. Archived leads, inactive office locations, inactive customers, and archived service properties cannot expose availability or booked details through the booking routes. The reservation locks its exact customer/property context through commit and rechecks bearer-link expiry plus minimum-notice/window eligibility after contended booking locks. Confirmation delivery locks and revalidates the exact active location/customer/property values used in its message, along with the selected email destination, through provider acceptance. It also locks and rechecks the current transactional-consent ledger at that final boundary, so a revocation prevents the provider call.
- Preserved V1 assignment semantics: a qualified active assigned lead owner is
exclusive, with fallback to the ordered qualified pool only when that owner is no longer eligible. Both the preview and the shared revisioned calendar writer account for member appointments and the candidate's crew work.
- Booking uses an atomic first-valid-slot-wins reservation, records one durable
appointment event, and delivers the transactional confirmation email through a retrying worker. Delivery is idempotent and locks the live lead through provider acceptance while rechecking the schedule revision and original customer, service property, and location. Each delivery attempt also freezes and revalidates its selected email method and address through provider acceptance; an address changed before the first attempt can be selected, but an in-flight attempt cannot be redirected. A delayed event cannot confirm a visit the office already moved or reassigned. Booking does not invent a tenant-specific pipeline-stage move. The native mobile UI did not change.
A bounced invoice no longer says Delivered
- Invoice delivery no longer turns green when an email or text provider merely
accepts the API request. Delivered now requires evidence that the message arrived; provider acceptance appears as amber Accepted while that answer is still unknown.
- Email attempts now incorporate the later provider facts already retained by
the Inbox. A bounce becomes Did not arrive, a spam complaint becomes Marked as spam, and conflicting handoff/provider evidence becomes Records disagree, with an explanation of the fact that decided the reading. Existing recorded bounces and complaints were backfilled so the fix is not limited to newly sent invoices.
- Text attempts remain Accepted after provider handoff because CE Pro does
not receive handset-delivery receipts. The page states that this is unknown, not failed, rather than inventing a Delivered result.
- The invoice headline uses the newest attempt per channel and puts complaints
and other actionable delivery problems ahead of reassuring evidence from a different channel. A customer view can still confirm that the invoice was seen, but it no longer conceals a bounced or complained-about send.
Text messages have no delivery receipt, and the docs said they did
- A SMS delivery status section in estimate-sending troubleshooting listed
a Delivered status meaning "the message reached the customer's phone". No such status exists or can exist: CE Pro sends no delivery-status callback to its SMS provider and has no route to receive one, so a text's record ends at the provider accepting it. Anyone following that page was hunting a status that never appears — and reading its absence as bad news. The section now says there is no SMS delivery receipt, what Sent and Failed do and do not cover, and what to do instead.
- The same phantom status is corrected wherever it implied more than the
record holds: the portal's message history is described as messages the provider accepted rather than "provider-confirmed sent/delivered", the AI draft context as "sent or received" entries, and Inbox delivery guidance as comparing send outcomes, with the two channels explicitly read differently — email delivery is confirmed, SMS is not.
- Four pages told an operator holding an ambiguous text send to **wait for
delivery confirmation**. On the one channel where no confirmation ever arrives, that is an instruction to wait forever. They now describe the retained blocked state: the unchanged SMS cannot be retried safely, waiting does not resolve it, and intentionally editing the draft starts a different logical message rather than settling the uncertain attempt.
- The troubleshooting answer to "the customer says they did not receive my
SMS" promised that a carrier rejection updates the thread with a failed status. It is the worst placement the claim had: the operator is told to wait, on the exact question where waiting produces nothing. It now says the thread will not tell you, and sends you to the provider console, which will.
- A QA instruction told testers that Sent is the immediate state "because
carrier delivery is confirmed later by the messaging provider" — so a tester verifying SMS was checking against a premise that never held.
- The correction to the sent-photo bubble initially went too far the other
way, applying the no-receipt caveat to email as well as text. Email delivery IS confirmed, so the two channels are now distinguished rather than both hedged. Over-hedging a fact the product actually has is its own kind of wrong.
- A PDF was described as going "to your customer as the file you attached".
What is true — and what the warning needs — is that nothing is stripped from what LEAVES the platform. Where it ends up is a separate question the same page answers separately.
- Resizing a photo was described as making it "go through" and as a size
"carriers reliably deliver". It removes a common cause of rejection; it guarantees nothing, and this page says two screens later that no carrier ever tells us either way.
- A sent photo's bubble was described as showing "exactly what the customer
received". It shows exactly what was sent — the same acceptance-as- delivery substitution, in the one place that looks least like a delivery claim. Corrected on the Inbox page, in the release entry that introduced the wording, and in the estimate FAQ that used it about a locked revision.
- The portal excluded messages "because they did not have confirmed
delivery"; the real test is that the provider accepted them, so an accepted text appears in customer history with nothing having confirmed it arrived. The manual-send steps no longer promise that a provider marks a text undelivered later, and the troubleshooting page now points at the provider console, which is the one place a text's carrier outcome can be read.
See whether your messages actually landed
- Delivery in the Inbox header now summarises what happened to the messages
your team sent, over a date range and optionally one location — accepted, refused for consent, suppressed by preference, failed, or held for a missing contact method or sender number. The figures always add up to what was attempted.
- **Email and text messages do not report back the same way, and the page says
so.** Email gives you confirmed deliveries, bounces, spam complaints, delays, blocks, and messages the provider accepted and then failed to send. Text messages give you nothing after the provider accepts them, so instead of a column of zeros the page states that we do not receive delivery reports for texts — and that this is not the same as nothing having arrived.
- "Open signals" replaced "opens", because an open is a tracking image
being fetched: privacy services fetch it with nobody reading, and apps that block images stay silent when someone does. It is wrong in both directions and now says so.
- "No outcome recorded" is kept apart from "failed." A send interrupted
before anything was written down may well have reached the customer; what is true is that we do not know, so it is labelled that way rather than counted as a failure.
Send documents from the Inbox, by email
- The composer button is now Attach, and a reply can carry PDFs as well as
photos — the paperwork customers ask for by name.
- Documents go by email only. Carrier support for texted documents is
uneven, and a file that silently arrives as a broken download for some customers is worse than one never sent. Attachments follow the thread rather than the channel, so composing an email with a PDF and switching to SMS is an ordinary thing to do: the composer warns first, and a send made anyway is refused naming the file so you know which one to move. The document stays attached, so switching back to Email is a click.
- The two kinds are checked differently, and the docs now say which. A
photo is re-encoded, which proves it is a real image and strips hidden metadata including your location. A PDF is checked only to be a genuine, complete PDF and is then sent exactly as you chose it — nothing stripped.
- A staged PDF no longer renders as a broken image in the composer, and the
size messages no longer tell you a document was shrunk when it was not.
Open the PDFs your customers email you
- A PDF emailed to your reply address now arrives as a document you can open,
shown as a chip naming the sender's file with a Download button. It used to be recorded as received and nothing more.
- It says Download rather than View on purpose: a document is handed over as a
file to save and is never opened inside Clean Estimate. Nothing inspects a PDF for safety and nothing claims to — what is verified is that the file really is a PDF and arrived complete.
- The kind is decided by the file, not its name. Phone file pickers
mislabel attachments often enough that trusting the label lost real paperwork, so the bytes decide.
An attachment that did not arrive says which problem it was
- One sentence used to cover four different situations. The thread now
distinguishes a file too large to keep, one the messaging provider no longer holds, and one our own retrieval could not reach — and names the sender's own file when there was one.
- Whose problem it is decides what you say next: ask for a smaller file, ask
them to resend, or say nothing about their file at all because the trouble was ours.
- Nothing claims a file arrived damaged. Nothing we can measure separates a
damaged file from a transfer cut short, so that claim is not made.
Attachments are never opened inside the app
- The route that serves stored attachments now refuses to render anything but
a verified image in place, and hands everything else over as a file to save. This shipped before documents could be stored at all, so the safe path was already closed when they arrived rather than being one line inside that change.
See where a customer's leads have gone, from the Inbox
- The Inbox thread now shows lead pipeline movement alongside the
conversation. Each card names the lead by its own number and says which stage it moved to, which it came from, who moved it, the reason they typed, and what the lead was worth at that moment — so a thread carries the context that explains it rather than making you open the pipeline in another tab.
- A lead created straight into a stage reads as added rather than moved, since
it had not been anywhere yet.
- Two behaviours worth knowing. Stage names are read as they are today, so
renaming a stage also renames it on cards for moves that already happened. And deleting a stage does not delete history — those cards remain and describe the stage as removed.
- Nothing here is inferred: the cards report only what was recorded when the
move happened. This history has been kept since the CRM shipped; it simply had nowhere to be seen.
The Inbox reads in the customer's own time
- Every time in the web Inbox now reads in the time zone of the conversation's
own service location rather than the time zone of whoever opened it. That covers the conversation list as well as the open thread: the "Viewed …" time on a list row is on that row's own location's clock, so a row and the thread it opens into always name the same day — and where the row is about an invoice, both use the time zone the invoice was issued in. A thread about a job in Denver reads in Denver time from a desk in Boston, and near midnight the thread and the customer-facing quote page — which has always used the location's time zone — no longer name different calendar days for the same moment.
- Proposal and invoice cards use the time zone the document was sent with,
which is the one the customer's own copy renders in — the quote page, the invoice link, and the portal all already worked this way. Changing a location's time zone later cannot move the dates on documents already out.
- The due date you set on a post-call follow-up is read on that same clock,
and the field names the zone — Due (MST) — so what you type is what the card shows back. Previously the box was read in your browser's time zone, which now would have disagreed with the card. On the one day a year the clocks skip an hour, that hour does not exist in the location and the composer says so instead of saving a different time.
- Saved calendar dates are left as written. A job scheduled for March 4 is
March 4 in any time zone, so job and appointment-request cards keep showing the stored day rather than converting it.
- A location with a missing or unrecognized time zone falls back to UTC, so the
thread still opens.
Know which proposal, and how long it stands
- Proposal cards in the Inbox thread now name their version. A customer sent
one quote sees no numbering; from the second revision onward every send, withdrawal, expiry, decline and acceptance says which one it was — so three "Proposal sent" cards stop being indistinguishable, and the record shows which version the customer actually signed.
- A sent proposal shows the deadline it went out with, and the thread now
gets a Proposal expired card once the proposal is over without being accepted. Previously a quote that ran out simply went quiet, even though the quote page was already refusing acceptances past it.
- A proposal can end two ways, and the card is dated at whichever actually
happened. Ending one early from the estimate is recorded at the moment you end it, so it is not dated at a deadline weeks later; ending one whose deadline has already passed keeps the deadline, since that is when it really stopped being acceptable.
- The expiry card only appears where the proposal really reached that point.
One withdrawn, declined or accepted first ended another way and already has its own card, and one replaced by a newer version before then was superseded rather than expired. Proposals marked expired before this release have no recorded moment, so they are dated at their deadline where they had one.
See why a customer stopped hearing from you
- The Inbox thread now shows consent changes as cards. You have always seen
the consequence — a channel the composer refuses to send on — but never the cause, so "why can't I text this customer?" was answerable only by whoever happened to remember the STOP.
- Each card names the channel, which way it moved, the number or address it
applies to, and what the record says caused it: the customer replied STOP, the customer used an unsubscribe link, or a named teammate recorded it on a stated basis. Nothing is inferred, and the card offers no advice about asking a customer to opt back in.
- An email unsubscribe appears on a text thread too, because that is where
somebody is about to try emailing. A decision that moves only one kind of permission says which — a customer off marketing is still reachable about their own job.
- One customer action is one card. A stop is recorded several times behind the
scenes — once per matching number on file, and again if the carrier retries the message — and the thread reports the decision that changed rather than each recorded row.
Raise a follow-up straight from a call
- Cards for recorded calls in the web Inbox now offer Add follow-up,
which creates an ordinary task from that call with a title, an optional due date, and an owner. Left blank, the owner is you.
- The task carries the call's customer automatically and links back to the
call itself, so a reminder can always be traced to the call that produced it. It appears in the Tasks workspace with its due date and owner like any other task.
- Each person gets one follow-up per call, so a double click or a retried
submission produces the same reminder rather than two. Two teammates who were both on a call can each raise their own, and the card lists every follow-up on that call — including a colleague's — so two people do not end up ringing the same customer.
- Raising a follow-up sends no notification. Post-call alerts remain separate
work.
- Fixed alongside it: a call card in the web Inbox showed the number the call
came *from*, which on a call your office placed — a callback — is your own company line rather than the customer. Cards now show the customer's number whichever end of the call they were on.
See when a customer opens their portal
- The Inbox thread now shows when a customer opened their customer account.
It has always shown what they did — messages, calls, documents, agreements, payments, requests, photos — but not the quieter signal that they came back and looked, which is often the first thing that happens after you send something.
- The card reports only what was recorded: that they opened it, when, and how
many opens fell inside that hour. Not which pages they read, how long they stayed, or what they were looking for. Repeated opens inside one hour are a single card with a count, so a customer clicking around their account does not bury the conversation.
- Opens of a proposal or an invoice keep their own existing cards, which name
the document. This adds the visit itself.
- Nothing alerts on a portal open. It is context on the thread when you go
looking, not an interruption.
Send photos from the Inbox
- Teammates can now attach photos to an SMS or email reply straight from the
Inbox composer, up to ten per message. Previously a "here's what we found" picture had to be sent from someone's phone, outside the customer record.
- Each photo is verified to actually be an image and re-encoded before it is
sent, which strips hidden picture metadata including your location, and resizes it to fit within published carrier limits — which removes a common cause of rejection rather than guaranteeing arrival. Large phone photos are also shrunk in the browser first, so attaching one works normally.
- If a photo is no longer available when you press Send, the whole message is
held back with your draft intact rather than going out without the picture it promised. A send that is refused before it reaches the carrier — no consent on file, no sending number — keeps its photos attached so you can fix the problem and send again. Photos attached and never sent are cleaned up automatically.
- Texted photos share a carrier size budget covering the whole message, so a
few large ones can be too much together even when each is fine alone. The composer says so up front and holds Send until you remove one, or you can switch to Email. *(Superseded the same day: email now has a whole-message ceiling of its own — see "Send documents from the Inbox, by email" above.)*
- Sent photos appear on the reply's own bubble in the thread, so the record
shows exactly what was sent. (The bubble is a record of the send, not a delivery receipt.) Non-image documents such as PDFs cannot be sent yet — that needs its own verification contract. *(Superseded the same day: PDFs can now be emailed — see above.)*
Customer-emailed photos stay on the thread
- Photos a customer emails to your reply address are now saved with the
message and shown on its bubble, the same as texted pictures — previously that media was lost. Each one is verified to actually be an image and re-encoded before storage, stripping hidden metadata including the sender's location data.
- Attachments the Inbox cannot display — documents such as PDFs — are shown
as received rather than silently dropped, and placeholders now name the sender's own file, so you can tell exactly which attachment a customer is asking about. Outbound attachments remain separate parity work. *(Superseded the same day: an emailed PDF is now verified and opens from a document chip, and outbound attachments shipped — see "Open the PDFs your customers email you" and "Send documents from the Inbox, by email" above.)*
Customer-texted photos stay on the thread
- Photos a customer texts to the company number are now saved with the
message and shown on its bubble in the web Inbox — previously that media was lost. Each picture is verified to actually be an image and re-encoded before storage, stripping hidden metadata including the sender's location data.
- Pictures are served only to signed-in teammates authorized for the
conversation's location, never through a public link. Media that is still being retrieved, is a format the Inbox cannot display, or could not be retrieved says so honestly instead of disappearing. Outbound and email attachments remain separate parity work. *(Superseded the same day: email attachments and outbound attachments both shipped, and a failed attachment now says WHICH problem it was rather than one sentence for every case — see the entries above.)*
Customer requests join the Inbox timeline
- The customer's own asks now appear on their thread: a portal service
request shows the requested service, preferred day, and the customer's notes, linking to the lead it created; a reschedule or cancellation request shows the requested day, arrival-window preference, and reason, linking to the job.
- The office's answer is its own card — approved or declined, with the
recorded decision reason and the deciding teammate — so the thread reads as the conversation it was. A still-pending request shows only the ask.
Agreement and payment history lands in the Inbox
- The web Inbox's conversation timeline now shows every maintenance-
agreement moment on the customer's thread: started, renewed (automatic sweep renewals say so), paused, resumed, extended, cancelled with the recorded reason, and auto-renew changes — each naming the teammate who acted. History is saved durably in the same operation as the change, never reconstructed later.
- Money now appears with amounts: each payment received (including partial
payments, proposal deposits, membership charges, and manually recorded tenders with their method) and each refund gets its own card, derived from the payment record itself so the timeline never disagrees with the ledger. Both card families live under the thread's System filter.
Pay an invoice with a saved card
- When a signed-in customer pays an invoice, Stripe's hosted checkout now
recognizes their account and offers the cards they keep on file for one-click payment; the customer can also choose to save the card they use during payment for next time.
- The saved-card offer is a convenience on top of payment, never a gate:
customers without saved cards see the same secure checkout as before, payment from the emailed invoice link is unchanged, and any trouble reaching the wallet falls back to the standard checkout automatically.
- Portal payment routes were also hardened so that only requests from the
portal's own pages can change anything on a signed-in account.
Customers can keep cards on file
- The signed-in customer portal's Payments page now manages saved
cards: the list is read live from Stripe (brand, last four, expiry, default) and the portal never stores card details. Add card opens Stripe's hosted secure page and returns to the portal; cards can be removed or made the default, with each change recorded for the office.
- Saving a card authorizes no charge — invoice payment keeps its full
hosted checkout, and memberships keep billing their signup payment method. One-step pay-with-saved-card is planned follow-up on this wallet.
2026-08-27
Maintenance plans renew automatically
- Every active or paused maintenance agreement now carries an office-
controlled Auto-renew toggle on the plan detail (requires Manage plans; each change is recorded with the acting teammate). It defaults off, and a monthly membership billed through a hosted Stripe schedule (including portal-sold Suds Club) cannot turn it on: automatic renewal cannot set up the next term's provider billing yet, and Suds Club terms promise no renewal without the customer's say-so.
- With auto-renew on, a daily scheduled task renews the agreement once its
term has elapsed and its final contract year's visits exist — same accepted cadence and price as a manual Renew plan, linked back to the source, with auto-renew carried forward so the program continues year over year, and the daily run works through every due agreement. Paused plans never auto-renew, and a term already renewed manually is left alone. Customer-facing renewal notices are not sent yet; each automatic renewal is recorded for the office.
2026-08-26
Customers can join Suds Club when paying an invoice
- The payment offer flow's Suds Club step is now live: an eligible signed-in
portal customer selecting Pay on an open invoice can join the ONE Suds Club plan their own home resolves to. The tier comes from the house-wash square footage on the customer's real estimate history (the invoiced estimate first), priced from the workspace's Suds Club configuration plus the service property's tax basis — the card shows the included services, only genuine configured savings, the amount due today, the monthly schedule that follows, the tax-inclusive year total, and the plan terms, all computed on the server.
- Joining opens a separate secure membership checkout; today's invoice is
untouched and the customer returns to it to finish paying. The membership and its seasonal visits are created immediately but stay payment-gated until Stripe confirms the signup settled, and only then are the remaining monthly installments created as the same Stripe subscription schedule estimate-accepted memberships use. The presented price is a promise: if the configuration, tier, or tax basis changes while the offer is on screen, acceptance is refused as stale instead of charging different terms.
- The step suppresses itself, with the reason recorded, when the membership
cannot be sold honestly — an existing or pending membership, an unmeasured home, the custom-quote band, an excluded tier, or an invalid configuration. An abandoned signup is reclaimed automatically after its checkout expires so the customer can be offered a membership again; a payment that lands on an office-cancelled membership alerts the office instead of reviving the plan. Portal-sold memberships appear in the Plans workspace under their own label, showing Signup pending until the payment settles (never mislabeled as an estimate deposit).
Maintenance plan groundwork and report fix
- Renewed maintenance plans now appear in the maintenance plan book's
upcoming rollovers on the Reports page; previously a renewal's successor term was silently missing from that list even though its rollovers still happened on schedule.
- Groundwork only, nothing sold yet: the plans engine can now carry a
membership that did not start from an estimate (the seam the payment offer flow's Suds Club acceptance will sell through). The Plans workspace names such a plan by its own label and shows "No originating estimate" in place of the signed-estimate link. Until memberships can be purchased, no such plan exists in any workspace.
Suds Club plan offer configuration and preview
- The payment offer flow's Suds Club offer now resolves from the workspace's
real Suds Club configuration — the Suds Club section of the stored pricing configuration when a complete one validates, otherwise the built-in Suds Club template. An incomplete or invalid stored configuration offers nothing rather than being patched with guessed prices.
- Teammates with Manage invoices can preview every offerable plan under
Settings → Customer payment offers exactly as the offer step will present it — amount due today, monthly payment schedule, yearly total plus tax, included services, and only genuine configured savings, all computed on the server — and untick individual plans to keep them out of the offer. Plan-selection changes join the same append-only settings audit trail as the offer switches.
- Each payment-offer attempt now records the Suds Club step's real
configuration state instead of a fixed placeholder. The customer-facing step itself remains hidden until membership acceptance at payment time ships, so customers are never shown a plan they cannot actually join.
Customer invoice payment offer flow foundation
- Added an optional, office-controlled offer sequence when a signed-in portal
customer selects Pay on an eligible open invoice, in the approved fixed order: Suds Club, get the next service on the calendar, then prepay the next service. Every screen keeps a full-size Continue to invoice payment button, declining everything lands on the unchanged secure checkout with the exact current balance, and the issued invoice document is never modified by an offer.
- Made the whole sequence server-owned and fail-open. Eligibility, the live
portal session, customer scope, invoice state, and Stripe readiness are revalidated when the sequence opens and again on accept; any offer-system problem sends the customer straight to checkout instead of blocking payment. Each attempt durably records which offers were shown or suppressed and why, plus every customer decision, and a just-finished sequence does not replay on an immediate second Pay click.
- Shipped the calendar step as the guarded request-a-time customer service
request — a real office-confirmed lead reusing the existing idempotent intake, with nothing charged today and a preferred day picked from a date control. The Suds Club and prepay steps stay hidden until standalone plan offers and a priced, bookable next service exist; no offer invents a price, discount, or calendar capacity.
- Added independent Customer payment offers switches for the flow and each
offer under Settings: conservative defaults (everything off), restricted to Manage invoices, with every toggle change recorded with the acting teammate in an append-only audit trail. The approved native mobile UI is unchanged.
Cross-device customer message unread state
- Restored unread message indicators in the signed-in V2 customer portal.
Messages shows an accessible exact unread count with a compact 99+ badge, and new office emails or texts carry a New marker in the customer history.
- Bound read state to the exact authenticated portal email instead of a single
browser session. A message read on one device stays read after a new code is verified on another device, without combining the state of two contacts who share a customer record.
- Made read acknowledgments snapshot-safe: the browser submits only rendered
message IDs, the service revalidates the live portal session and customer scope inside the transaction, and the service records the exact messages the customer saw. A newer office reply that lands during the request remains unread, and a failed or queued message that later becomes sent cannot inherit an older conversation read boundary. Internal notes, currently failed/unconfirmed sends, other contacts, and the customer's own portal submissions cannot create or clear this count. The approved native mobile UI is unchanged.
- Kept the unread badge current when the mounted customer account receives a
fresh server payload. A confirmed office reply discovered during invoice or other portal refresh polling now updates the navigation count without a logout or full account remount, while local read acknowledgments still clear immediately.
Private invoice office notes
- Restored V1-style Office notes on the V2 web invoice detail page for
teammates with billing-management access. Staff can retain billing context and follow-up instructions on draft, sent, paid, overdue, or void invoices without changing the customer document.
- Added independent conflict protection so a stale invoice tab cannot silently
replace a teammate's newer office note. Note changes emit a metadata-only audit event; the private note text is not copied into that event.
- Kept office notes out of public invoice links, customer portal projections,
PDFs, email and text delivery content, and payment flows. Saving a private note does not rotate the invoice link, alter totals, or invalidate Checkout. The approved native mobile UI is unchanged.
Invoice sales-rep tracking
- Restored V1-style sales attribution in the V2 web invoice workspace.
Source-linked invoices inherit the estimate's assigned representative; invoice rows and mobile web cards show the rep; and the catalog can filter by one teammate or Unassigned with matching counts and money summaries.
- Added guarded Change sales rep controls to invoice detail. Eligible
owners, admins, and sales reps must be active and compatible with the service office. Attribution uses its own conflict version and audit event, so a stale browser tab cannot silently overwrite another change.
- Kept historical sales reporting usable after team changes. A deactivated,
role-changed, or office-moved rep who still owns a visible invoice remains in the invoice filter as Unavailable, and changing a draft's office now revalidates its current rep before saving. Assigned legacy teammates whose mirror lacks a usable name or email now display as Team member, reserving Unassigned for invoices that truly have no assigned rep.
- Prevented a slower previous sales-rep request from overwriting the invoice
filter after the office changes. The menu clears immediately and accepts only the latest office-scoped response, so it cannot offer reps from another office during rapid filter changes.
- Added permanent, append-only invoice attribution history beside the current
assignment. Every rep correction retains the previous rep, new rep, actor, office, and assignment version even after short-lived delivery events are pruned. That historical office remains factual without blocking or being rewritten by a later valid move of an editable draft.
- Kept attribution separate from immutable customer content and accounting.
Reassignment does not rotate customer links, alter the PDF, invalidate Checkout, change delivery/content versions, or rewrite the payment ledger. The approved native mobile UI is unchanged.
Invoice catalog first-page reliability
- Fixed the first page of the invoice catalog so its stable keyset cursor
parameters stay explicitly typed even before a cursor exists. Empty and first-page invoice results now load normally with or without customer-tag filters instead of showing a database parameter-type error. Due soonest paging now keeps the same explicit date type when it reaches invoices whose due date is blank.
2026-08-25
V1-style customer tags and lead filtering
- Added organization-wide, color-coded customer tags to the V2 web Clients
workspace and client profile. Teammates can search or create labels, replace one customer's exact tag set, and safely add tags in bulk to selected customers without duplicate assignments.
- Added match-any customer-tag filtering to the Clients directory and every
web Pipeline view. Lead cards, list rows, and customer rows show saved tag pills, and Export view applies the same server-authorized tag filter.
- Added the same match-any Customer tags filter and shared tag pills to the
Estimate workbench. Its paging and queue totals use the same server-authorized filter instead of filtering only the current browser page. The wide-screen tag menu stays aligned inside the workbench card instead of being clipped.
- Restored the match-any Customer tags filter and shared tag pills in the
web All jobs catalog. Its server query applies the same filter to rows and the exact pagination total; the shared sales-and-operations calendar remains unchanged.
- Restored the match-any Customer tags filter and shared tag pills in the
web Invoices catalog. Matching count, outstanding, overdue, and collected totals use the same authorized tag scope, and keyset cursors cannot be reused after the selected tag set changes.
- Kept tag assignments organization- and office-scoped with database row-level
security and transaction-time client visibility checks. Marketing campaign audience targeting remains a separate parity follow-up. The approved native mobile UI is unchanged.
- Fixed the tag selector's 20-label boundary, keyboard selection from client
rows, and the client-profile menu placement so the full searchable menu is usable instead of being clipped by the profile header card. Tag creation now preserves selection or search changes made while it saves, and a tag-list outage no longer suppresses otherwise available office locations. Tag assignment writes also avoid maintaining a redundant duplicate index. Bulk additions enforce the 20-tag total across existing and new assignments, and punctuation or international characters no longer collapse distinct labels.
V1-style inventory catalog and item editing
- Replaced the minimal inline V2 inventory entry row with the cleaner V1-style
catalog workspace: summary cards, item/SKU/supplier/color search, category, location, and stock-status filters, low-stock state, and a focused add/edit dialog.
- Restored useful catalog detail for Holiday Lights and general operations:
SKU, description, package sizing, unit cost, supplier, storage location, light type, length, color, reusable status, and last reorder date. Catalog items can be edited or made inactive without rewriting quantity or frozen supplier-order history. Location stays fixed after creation so movement and receipt evidence cannot be detached from the warehouse that owns it. The counted unit also stays fixed after the first movement so existing ledger quantities cannot silently change meaning, including when an edit races a simultaneous stock movement. An already-open movement history refreshes as soon as a stock adjustment succeeds.
- Opening stock now creates an append-only Received movement in the same
transaction as the item. Stock adjustments and movement history remain available, and stock reads/writes are explicitly organization-scoped in addition to database row-level security. The approved native mobile UI is unchanged, and no strand-testing or rent-versus-buy controls were added.
Supplier purchase orders and inventory receiving
- Added Purchase orders to V2 Field > Ops with supplier/contact details,
expected delivery, notes, location-owned inventory lines, quantities, unit costs, extended totals, and atomic organization purchase-order numbers.
- Restored the useful V1 Draft, Ordered, Partially received, Received, and
Cancelled lifecycle. Drafts are reviewed before ordering; unreceived drafts or orders can be cancelled; partial and completed receipt evidence cannot be erased by cancelling the order.
- Made each delivery retry-safe and transactional. Partial or final receiving
updates the line totals, purchase-order status, append-only receipt evidence, stock movement ledger, and on-hand quantity together. Duplicate retries do not add stock twice, while changed retries, over-receipts, inactive items, wrong-location items, and invalid lifecycle transitions fail closed. Shared catalog items are locked in a stable order across supplier receiving, install allocation, and takedown returns, so simultaneous inventory work cannot deadlock by presenting shared items in opposite request order.
- Froze item names and units on each order so later catalog edits do not rewrite
purchasing history. Totals are overflow-safe and limited to $21,474,836.47; oversized drafts are rejected before a number or order record is created. Every purchasing and receiving failure is shown in plain language. Inactive locations remain available for historical filtering but cannot be selected for a new supplier order. Stable, index-backed Load more paging keeps older retained orders reachable, and a late detail response cannot replace the currently selected order or aim its actions at the wrong record. The approved native mobile UI is unchanged; rent-versus-buy pricing and strand testing remain outside the required scope.
Customer-controlled email and text preferences
- Added Account to the signed-in V2 customer portal with the authenticated
contact, connected service-location summary, and email/text controls for optional estimate follow-ups, appointment reminders and schedule changes, and review requests.
- Saved each choice to the exact signed-in portal email identity, so one person
keeps the same opt-outs across every connected property while two people who share a customer record cannot overwrite each other's choices. New identities remain enabled by default, and a property linked later through the same exact portal email inherits that identity's opt-outs immediately. Text sends to a contact shared by several portal emails honor every identity's opt-out.
- Kept direct estimate and invoice delivery, payment receipts, and team replies
outside these optional switches. Existing SMS STOP and email unsubscribe consent remain separate, stronger delivery protections.
- Revalidated the complete portal session binding on every save and checked
each optional message both at its durable claim and again through provider acceptance. A channel disabled while a message is queued is recorded as preference-suppressed without calling the provider or appearing as delivered customer history. The frozen email recipient, or the exact portal identity set associated with a frozen text destination, remains authoritative for the final preference check even if a concurrent office edit deactivates the contact or changes/removes its email methods. The approved native mobile UI is unchanged.
Holiday Lights pricing scope clarified
- Confirmed that the current year-to-year and discounted three-year Holiday
Lights pricing choices are the supported V2 sales model. A separate rent-versus-buy pricing workflow is intentionally out of scope and is no longer tracked as unfinished Holiday Lights work.
- Confirmed that a strand-testing workflow is also outside the required V2
product scope. Its pending build was not promoted to the public test domain and its production migration was not run.
- Kept supplier purchasing, material-demand projection, and end-of-season
operations as separate remaining work. These scope decisions make no visible native mobile UI change.
Customer replies from the V2 portal
- Added Send a message to the signed-in customer portal's Messages
page. Customers can choose one of their authorized service locations, send up to 2,000 characters, and immediately see the reply labeled Portal in their customer-facing history.
- Each submission arrives as normal inbound activity in the exact
location-bound office Inbox, and its subject preserves the selected service address for staff. Authorized teammates answer through the existing provider-backed email workflow; the portal does not falsely claim that the customer's own form submission sent an external email or text.
- Revalidated the active customer session, client, property, location,
contact, and exact authenticated email inside the write transaction. Stable request keys make retries return the original message, changed-content key reuse fails closed, and the same-origin endpoint is rate-limited.
- Kept internal notes and staff-only delivery direction out of customer
history. Follow-up text typed while an earlier send finishes is not erased, and accounts without an active property get a clear unavailable state instead of a permanently disabled composer. Deactivated service-location assignments and operational locations are filtered before the composer is shown, matching the submission boundary. This release changes only the web customer portal and supporting services; the approved native mobile UI remains unchanged.
V1-style customer service requests in the V2 portal
- Added Request Service to the signed-in customer-account Home page with
the established V1 service choices, preferred-date field, 2,000-character notes field, confirmation wording, and service-location selection for customers who have several properties.
- Each submission now creates a normal office lead with source **Customer
portal**. Lead detail preserves the requested service, preferred date, original notes, and received time; Holiday Lights requests use the normal Holiday Lights pipeline routing when configured.
- Hardened request receipts so routine expired-session cleanup remains safe and
existing Customer portal source names work regardless of capitalization.
- Kept a preferred date as a customer preference instead of silently booking
the shared calendar. The office confirms the visit and chooses structured start/end times and the appointment assignee through the normal lead flow.
- Made intake transactional and retry-safe: the current customer session and
property scope are revalidated, an immutable request receipt and its lead commit together, repeated request keys return the original lead, forged properties fail closed, and the endpoint is same-origin, private/no-store, and rate-limited. The approved native mobile UI is unchanged.
Automatic connected-call review and optional transcript redaction
- Made recording and AI review automatic for every connected inbound and
outbound company call. The workspace no longer has recording or AI-review off switches. The required spoken notice still reaches both participants before Twilio bridges and records the call.
- Added Redact common sensitive details in new transcripts for teammates
with Manage phones. It is off by default. When enabled, common email, phone, payment-card, and long-number patterns are replaced before a new transcript is retained or sent for review. Changing the option does not rewrite an existing retained transcript.
- Kept missed calls out of transcription because no conversation occurred;
voicemail continues through its greeting and separate voicemail recording flow. The approved native mobile UI remains unchanged.
- Made new company calls fail closed when the required AI-review provider is
unavailable, including workspaces enabled before this policy. Inbound callers hear a temporary-unavailable message and outbound starts are refused instead of creating a recorded call whose required review cannot complete.
Connected-call recording and AI coaching in web Phone
- Restored the first production-safe V1 call-intelligence workflow in V2's web
Phone workspace. Connected-call recording is off by default and requires Manage phones, an explicit workspace setting, and a 10–500 character spoken notice. The notice reaches both participants before Twilio bridges the recorded call.
- Added retry-safe speaker-labeled transcription and structured AI review with
an overall score, customer sentiment, outcome, six coaching categories, strengths, opportunities, next steps, and an editable follow-up draft. The draft is never sent automatically. Phone now shows reviewed-call count, average score, processing/failure states, retained transcript and playback, and a permission-gated Retry review action.
- Kept the recording callback, review request, and call revision durable and
idempotent. Transcription and review checkpoint independently, stale retries cannot overwrite newer work, and disabling AI review cancels queued work before its next provider step. Basic email, phone-shaped, card-shaped, and long-number content is redacted before the transcript is retained or sent for review.
- Extended the existing 1–365 day private-media retention job to clear
connected-call transcripts and reviews and reconcile/delete provider audio. Provider URLs, recording/account identifiers, credentials, and internal review errors remain out of browser responses. The approved native mobile UI is unchanged.
- This bounded release does not yet add custom weighted scorecard settings,
manager calibration, automatic post-call tasks or alerts, live call scripts, a free-form dialer, or native mobile call-intelligence UI.
Scheduling and job history in the web Inbox
- Added durable job lifecycle cards to each matched customer conversation.
The web Inbox now shows job creation, scheduling, rescheduling, removal from the schedule, work start, completion, and cancellation in chronological order under All and System.
- Scheduled cards show the saved date and arrival time, reschedule and
cancellation cards show the recorded reason when present, and office changes identify the teammate who made them. Each card links directly to the job.
- Writes are append-only, organization- and location-scoped, and occur in the
same database operation as the job change. Existing job creation and exact completion timestamps are restored without inventing older schedule history. Offline crew starts and completions use the validated device tap time instead of the later sync time. The approved native mobile UI remains unchanged.
Personal saved views in the web Inbox
- Restored V1's personal saved-view workflow in the web Inbox. A teammate can
save the exact current search, ownership, reply, channel, engagement, last-outbound, priority, sort, unread, and missed-call combination, then restore it from Saved views on another browser or device.
- Kept every view private to its creator instead of turning it into an
organization-wide default. Organization and member boundaries are enforced in the database and API; case-insensitive duplicate names are refused, each teammate can keep up to 20 views, and deleting a view cannot target another teammate's record.
- Kept the Inbox usable when saved-view loading, saving, or deletion fails, and
left the approved native mobile UI unchanged.
Winter Wonderland nighttime property previews
- Restored V1's Winter Wonderland sales visual inside the V2 single-screen
web Holiday Lights builder. Reps can generate Classic, Colorful, or Elegant nighttime previews from the active Good, Better, or Best package without changing the approved native mobile UI.
- Locked every paid generation to the exact saved package drawing and clean
property image. A Street View preview is also bound to the normalized service address, so changing the property address detaches an older-house preview before it can be sent. Requests are durably tracked for safe retry and private storage recovery, with teammate, workspace, estimate, and monthly spend limits preventing duplicate or unbounded provider charges. A provider call that completes but returns an unusable image still counts against those durable limits and retains its conservative usage record. The workspace monthly ceiling is enforced across every location, including for an office member whose normal record access is limited to one location.
- Kept generated previews independent by package and automatically detached an
older image when its package design changes. Draft saves and the final Send boundary reject missing, cross-estimate, unfinished, or mismatched images.
- Added the reviewed nighttime preview to the public quote, signed-in customer
account, and office/customer proposal PDF. Those views fall back to the exact deterministic property drawing when no generated preview is attached, and never expose a private storage path or provider credential.
Good, Better, and Best Holiday Lights proposals
- Restored V1's Good, Better, and Best package workflow inside the
V2 single-screen Holiday Lights builder, without bringing back the estimate wizard. Each package owns its own priced zones, property drawing, power plan, and optional customer-facing description.
- Added complete-package copy actions so a rep can start Better or Best from an
existing design while regenerated item and zone ids keep later edits independent. Empty unfinished packages are not offered.
- Added the three package cards to both customer proposal experiences with
exact per-package scope, price, deposit, visual preview, and an emphasized recommended Better choice. The selected design also follows the customer into the option-aware proposal PDF.
- Froze the accepted package snapshot through signature, checkout, install job,
takedown job creation, crew checklists, and mobile scheduling scope. A package with no drawing stays intentionally blank instead of borrowing another package's visual. Property-photo retention now protects references in all draft packages and immutable sent options. A zone edit from the approved compact mobile Holiday editor now updates the selected package, detaches links to removed zones, and leaves the other tiers untouched so mobile price, immediate Send validation, sent proposal, and crew scope cannot drift apart. Office proposal PDFs now also use the package the customer actually accepted, rather than the package that happened to be active at initial send. Signed customer PDFs ignore later package-selection overrides, and a blank package in the signed-in customer account no longer borrows another package's visual. Map, manual, and photo-assisted Holiday zone measurements now update the selected package atomically before its price is recalculated. Blank optional area names are normalized to their lighting type, and older frozen package snapshots with blank names remain authoritative in crew checklists and mobile scheduling instead of falling back to another package's mutable scope. Starting a new mobile zone no longer removes package choices before its quantity is entered. Completed edits still project into the selected package while that unfinished row is excluded, keeping an immediate mobile Send on the exact price and scope shown on the phone. The phone also recognizes the server's package projection as its own saved revision instead of creating a false stale-draft conflict. Temporarily clearing an existing quantity preserves its drawing link, while package-bound labels are safely bounded without changing pricing. Draft saves now revalidate the active property photo and every Good, Better, and Best package photo under the estimate lock; a deleted non-active package upload can no longer survive a save, and changing a photo in any tier schedules safe cleanup. The approved native mobile UI remains unchanged.
Permanent-light paths in the Holiday Lights designer
- Restored V1's distinct Draw permanent lights mode beside removable C9
strands in the web Holiday Lights visual designer. New permanent-light paths use the same bounded drawing, priced-zone linking, movement, deletion, and repeating color-sequence workflow.
- Rendered permanent lighting as tighter, compact track-mounted points instead
of hanging C9 bulbs in the office canvas, immutable customer proposal, signed-in customer account, and downloadable proposal PDF.
- Added a Light style control for a selected saved path so an estimator can
correct C9 versus permanent lighting without redrawing it. Existing drawings remain C9 by default, and the approved native mobile UI is unchanged.
2026-08-24
V1 bulb-color sequences in the Holiday Lights designer
- Added the V1 color-sequence controls to the web Holiday Lights designer,
including warm white, cool white, warm white/red, warm white/red/green, red/green, and multicolor presets.
- Added custom repeating patterns up to 12 bulbs using W, C, R, G, B, O, and P
tokens. Reps can choose a pattern before drawing or change a selected saved strand afterward.
- Preserved the exact saved pattern through autosave, send, the public proposal,
the signed-in customer account, and the downloadable proposal PDF. Existing V2 drawings retain their prior solid or multicolor appearance, and the approved native mobile UI remains unchanged.
Individual C9 bulbs in Holiday Lights visuals
- Replaced the dashed roofline preview with evenly spaced, glowing C9 bulb
shapes on the office designer, customer proposal, and downloadable proposal PDF. Solid colors remain consistent, while multicolor strands repeat the same red, blue, green, and gold sequence in every output.
- Bounded each saved line to at most 48 preview bulbs and corrected spacing for
the 8:5 property-image aspect ratio, keeping long or complex designs fast.
- Kept bulbs visible when a valid strand touches any property-image edge by
hanging bottom-edge bulbs above the wire and insetting bulbs at the left and right boundaries in both web and PDF rendering.
- Preserved the existing strand selection highlight, saved coordinates,
pricing links, and approved native mobile UI.
Holiday Lights design in downloadable proposals
- Added the saved Holiday Lights property design to office and customer
proposal PDFs, including the customer-visible outlet/cable overlay and timer details.
- The PDF receives a bounded server-composed JPEG, while the document renderer
continues to accept only customer-safe fields. Private outlet and crew power notes are never rendered.
- If Street View or a retained property photo is temporarily unavailable, the
proposal PDF still downloads with its frozen scope, pricing, and signatures.
- Public and office proposal PDFs now use the fail-closed paid-provider quota
guard before any Street View work, preventing an unavailable shared limiter from allowing unmetered image-provider requests.
Holiday Lights outlet and cable power plan
- Added Place power outlet and Draw cable run to the web Holiday Lights
property canvas, including 15/20-amp and GFCI outlet details, precise movement, deletion, and the existing 1×–4× coordinate-safe canvas controls.
- Added timer location, timer model, and private crew power notes to the saved
estimate design. Older saved drawings open with an empty, hidden power plan.
- Added an explicit customer-visibility control. When enabled, customer web
proposals show outlets, cable routes, and timer details; crew and outlet notes are removed at the customer boundary. The approved native mobile UI remains unchanged.
- Enforced the saved-design limits in the canvas itself—30 outlets and 50 cable
runs—so an over-limit plan cannot fail at save time. Customer-visible power-only designs, including timer-only plans, now remain attached to the sent proposal, and a hidden plan is emptied again at the final public-response boundary.
- Kept the redacted power-plan shape valid after privacy filtering by replacing
required private note fields with blank values. Customer proposal visuals no longer disappear when the public parser revalidates the safe design.
1×–4× Holiday Lights canvas zoom
- Added separate Canvas view controls to the web Holiday Lights designer
for 1× through 4× zoom, bounded four-direction panning, and recentering.
- Kept drawing, selection, and whole-item movement mapped to the original
property coordinates while zoomed, so close-up edits stay aligned in office preview and the customer's saved proposal. Selection includes the full visible wreath or mini-light area instead of requiring a click near its center.
- Left the approved native mobile UI unchanged.
Editable Holiday Lights design items
- Added the V1 24, 30, 36, 48, and 60-inch wreath sizes to the web Holiday
Lights designer and scales each wreath visibly on the property image. Linked wreath zones now carry that exact size through customer pricing and the material SKU instead of treating every wreath as 36 inches. Older or malformed drafts with an unsupported size now safely use the standard 36-inch wreath so canvas placement remains valid and the material SKU and quoted price cannot disagree.
- Added a selected-item editor for changing a completed strand, wreath, or
mini-light area's color and priced-zone link without redrawing it. Wreath size and mini-light strand count can also be corrected in place. Customer and office visuals now label each mini-light area with its exact strand count.
- Kept moved wreaths and mini-light clusters fully inside the design canvas and
also re-bounded wreaths after a size increase. Wreath visuals can link only to wreath-priced zones. The approved native mobile UI remains unchanged.
- Relinking a wreath now adopts the destination wreath zone's configured size
without changing that zone's quote, material selection, or other wreaths.
Mini-light areas in the Holiday Lights visual designer
- Added Place mini-light area to the web Holiday Lights designer for bushes,
trees, columns, and other dense-light areas. Each placement records a bounded strand count, light color, and matching priced zone.
- Rendered mini-light areas as compact groups of individual illuminated bulbs
on office and customer proposal views.
- Extended Move items, Delete selected, and the type-specific remove
actions to mini-light areas without changing the approved native mobile UI.
Private property-photo backgrounds for Holiday Lights
- Added Upload property photo to the web Holiday Lights visual designer.
Reps can now design on a customer-owned JPG, PNG, or WebP when Street View is missing, outdated, obstructed, or framed poorly.
- Bounded original files at 10 MB, reduced them below the hosting platform's
request limit in the browser, then independently decoded and normalized them to a metadata-free landscape WebP on the server before private storage.
- Kept storage paths server-derived and tenant-scoped. Office access, public
quote access, and signed-in customer access each revalidate the estimate or immutable proposal before returning an image.
- Gave every upload an immutable server-generated id so simultaneous or stale
browser tabs cannot replace the property image behind another tab's saved drawing. Every authorized upload records a durable cleanup fallback before external storage begins. A completed storage write starts a one-hour autosave grace, while authorization also schedules a separate two-hour fallback for an interrupted response or follow-up transaction; drafts that are never sent are covered and send checks again. Replacing an uploaded background or switching back to Street View schedules a fresh check. Immediately before deletion, cleanup locks the estimate, rechecks the current draft plus every saved proposal revision, and durably tombstones only still-unused uploads. Late saves cannot adopt a photo after deletion begins, even if private storage deletes the bytes but loses its response. All historical images are preserved.
- Rejected stale uploads before decoding whenever an estimate is no longer a
draft.
- Forced the office canvas to reload a successfully replaced pending photo so
newly drawn lights always align with the bytes that the customer will see.
- Locked background replacement after drawing begins so saved coordinates stay
aligned, and left the approved native mobile UI unchanged.
Movable Holiday Lights design items
- Added Move items to the web Holiday Lights visual designer. Reps can
select and drag a completed strand or wreath while the entire item stays inside the property canvas.
- Added a visible selected-item highlight and Delete selected action so an
exact completed item can be removed without guessing which type an undo action will affect.
- Fixed the wreath preview so its red bow stays attached to the wreath instead
of appearing near the top of the property image.
- Kept normalized coordinates stable during repeated moves and left the
approved native mobile UI unchanged.
First usable V2 Holiday Lights visual designer
- Added a web-only property visual designer directly inside the single-screen
Holiday Lights estimate builder. Reps can use the estimate's saved service address, aim or zoom Street View, draw multi-point colored light strands, place wreaths, and link each strand to a priced estimate zone.
- Saved camera settings and normalized drawing coordinates through the same
revision-guarded estimate autosave. Drafts never store provider keys, arbitrary remote image URLs, or large image data, and strict point/item limits prevent oversized design payloads.
- Locked camera reframing after the first design item so the property cannot
move underneath saved lights. Manual rotation now starts from an explicit north heading, and separate point, strand, and wreath removal controls avoid deleting a different item type than the rep intended.
- Added signed-in, location-scoped office imagery and quote-token-scoped
customer imagery. Both routes keep the Google server key private, validate the provider's image response, and use fail-closed paid-request limits.
- Frozen a non-empty design into the exact immutable proposal revision at send
time. The delivered quote page and signed-in customer account now show that saved visual above the package choices without exposing wholesale pricing or office-only fields. A camera position with no drawn work remains draft-only instead of sending a blank property image.
- Kept the approved native mobile UI unchanged. Uploaded property photos,
Satellite/3D capture, mini-light areas, movable completed items, Good/Better/Best visual packages, Winter Wonderland, electrical planning, and the design inside the downloaded PDF remain explicit follow-up work.
Clearer Holiday Lights estimate setup
- The web Holiday Lights builder now uses the established V1 names for every
roofline, tree, wreath, garland, and accent-lighting area.
- Quantity entry now clearly switches between Linear feet and Quantity
based on how the selected area is priced, replacing the ambiguous combined field.
- Reps can add a customer-friendly name such as Front roofline to each
lighting zone, and roof-pitch choices now show their familiar descriptions.
- This update changes only the web estimate workspace. The approved native
mobile layout remains unchanged.
Saved-customer company-line calling on web
- Added Start a company-line call to the web Phone workspace. A
teammate with Place phone calls can search saved customers by name or phone, review the exact contact and service location, and start the same callback-first company-line flow without waiting for prior call history.
- Kept the destination authoritative on the server: the browser submits only
the selected saved phone-method identifier, then Clean Estimate re-resolves the active customer, phone, authorized location, teammate, and permission before starting the paid provider action. An outdated or missing number must be corrected on the client record; this release intentionally does not accept an arbitrary browser-entered destination.
- Shared the existing per-teammate provider limit, duplicate-call guard,
uncertain-start handling, signed Twilio callbacks, and retained-credential handling across saved-customer calls and call backs.
- Saved the initiating teammate on new outbound call activity and now shows
that factual attribution in the Phone history. Existing activity remains unassigned instead of manufacturing a representative.
- Kept the approved native mobile UI unchanged. Free-form dialing, connected
call recording, transcripts, call scores, coaching, and AI assistance remain separate parity work.
Company-line Call Back on web
- Restored V1's Call back action on completed call cards in both the web
Phone workspace and the selected web Inbox conversation.
- Kept the V1 callback-first safety flow: the configured office forwarding
phone rings first, then the saved customer number is bridged through the shared company line. The server derives the destination from the durable call instead of accepting a customer number from the browser.
- Rechecked the active teammate, Place phone calls permission, and pinned
location before creating an attempt. Paid provider starts are limited to 20 per teammate in 15 minutes and fail closed if the shared limiter is down.
- Made signed Twilio progress and customer-leg callbacks race-safe and
idempotent, including callbacks that arrive before the provider start is finalized and late parent events after the customer outcome. Provider credentials, identifiers, callback tokens, and bridge numbers remain server-only.
- Prevented delayed ringing or in-progress events from reopening an already
ended callback. An exact saved outbound attempt can also finish through its retained Twilio credential if the workspace rotates accounts while the call is underway; mismatched call or account identities still fail closed.
- Prevented a second active callback to the same customer conversation during
a five-minute safety window. An uncertain provider response stays pending and tells the teammate to check for the company-line ring before retrying instead of recording a definite failure and risking a duplicate customer call.
- Logged the outbound attempt and factual outcome in the same scoped customer
conversation. Callback readiness is loaded with the existing Inbox thread facts rather than adding another serial browser request. This release does not record connected callbacks, add call intelligence, or change any approved native mobile UI.
Unread Inbox count in web navigation
- Restored V1's unread badge on the web sidebar Inbox link, including a
compact 99+ display for larger queues and an accessible exact count.
- Kept the count per teammate and limited it to that teammate's authorized
locations. It refreshes in the background, on browser focus, and immediately after a conversation is marked read.
- Used a narrow customer-activity/read-boundary query so shell polling does not
load estimate, invoice, or workflow projections. The approved native mobile UI is unchanged.
Linked estimate history in the web Inbox
- Restored V1's Linked Estimates section at the bottom of matched customer
conversations in the web Inbox. Each row shows the estimate number, service type, updated date, status, and total and opens the estimate directly.
- Kept the conversation fast with a newest-first 25-estimate limit and a
View full customer history handoff when more estimates exist.
- Restricted the history to the matched customer and the signed-in teammate's
authorized locations. The approved native mobile UI is unchanged.
Email delivery and engagement activity in the web Inbox
- Added provider-confirmed delivered, opened, clicked, delayed, bounced,
complained, failed, and suppressed activity to outbound email bubbles in the web Inbox. Open and click activity includes its latest time and a count when the provider reports more than one event.
- Kept engagement facts separate from the core provider-send state and from
estimate or invoice page views. The UI also explains that an email open can be caused by a mail privacy proxy and is not proof that a person read it.
- Made signed Resend webhook processing replay-safe and transactionally
idempotent, including callbacks that arrive before the outbound send has finished saving the provider ID. Ambiguous or conflicting provider identities fail closed instead of crossing customer or workspace records.
- Preserved the approved native mobile UI. This release changes the web Inbox
and backend lifecycle storage only.
Duplicate-send protection in the web Inbox
- Restored V1's manual-send budget for new web Inbox compositions: up to 30
SMS or email sends per teammate in 15 minutes.
- Added a focused customer guard that pauses bursts above 10 new sends per
minute to the same customer and channel, including messages started from that customer's threads at different service locations. The blocked request stops before the provider and leaves the draft intact with a plain-language wait message.
- Exempted stable-key retries of an existing delivery attempt from the
new-send budget and kept the original composition time fixed when a failed attempt is retried, preserving the existing lost-response and provider-lease recovery protections without creating or later recounting another message.
- The cooldown message now identifies whether the teammate reached the
30-messages-in-15-minutes limit or the customer reached the one-minute limit, so the suggested wait matches the actual protection window.
- Kept the approved native mobile UI and delivery contract unchanged.
SMS length feedback in the web Inbox
- Restored V1's SMS composer counter on web. It shows the current draft length
against the standard 160-character SMS length while a teammate types.
- Longer drafts remain editable and sendable, but the counter switches to a
clear amber overage warning that the carrier may split the text into multiple messages.
- Connected the counter to the message field for assistive technology and kept
it out of Email and internal Note modes. The approved native mobile UI is unchanged.
Editable AI Draft suggestions in the web Inbox
- Restored V1's AI Draft action for SMS and email in the web Inbox. A
generated subject and message fill the normal composer for review and edits; nothing auto-sends, and the ordinary Send action still enforces the current recipient, consent, company sender, thread snapshot, provider, and idempotency boundaries.
- Built the suggestion from the matched customer, the newest estimate at the
conversation's exact location when one exists, and up to eight recent received or sent SMS/email entries. Internal notes, calls, failed or refused sends, recipient addresses, provider metadata, other-location estimates, and secure URLs are excluded from the model boundary.
- Added strict structured-output validation, a 15-second provider timeout,
opaque safety identifiers, no provider-side response storage, and a shared 50-call/24-hour workspace limit. Missing configuration, provider errors, invalid output, invented generated links, and exhausted limits return a clearly labeled safe editable template instead of breaking the compose workflow.
- Protected in-progress work: changing the draft, conversation, or channel
while generation is running prevents the late result from overwriting newer text or appearing in the wrong thread.
- Kept the approved native mobile Inbox unchanged. Mobile AI controls, call
scores, coaching, and AI call summaries remain separately reviewed parity work.
V1-style Phone activity workspace on web
- Added Phone to the signed-in web sidebar with a location-scoped activity
workspace for calls received or placed through the company line.
- Restored V1's factual daily summary for calls, answered calls, missed calls,
voicemail, and connected talk time. Each location's contribution to today uses that location's configured timezone; a legacy invalid value falls back to UTC instead of blocking the workspace.
- Added All activity, Inbound, Outbound, Missed, and
Voicemail filters; literal customer, number, and location search; and bounded keyset paging through older calls. Changing filters or search while an older page is loading no longer leaves the refreshed loader disabled.
- Added compact call rows with customer/caller, direction or outcome, number,
location, time, duration, and a direct Open conversation link. Retained voicemail includes its transcript and the existing private, retention-checked audio player.
- Kept provider account, call, and recording identifiers plus provider media
URLs out of the browser. Successful Phone data responses are explicitly private and no-store, and organization/member/location scope remains server-enforced.
- Kept this first workspace read-only: it does not invent callback/outbound
calling, representative attribution, call scores, coaching, or AI results. It changes no approved visible native mobile UI or mobile sync contract.
Secure voicemail playback in the web Inbox
- Restored V1-style voicemail playback directly inside retained voicemail
cards in the selected web conversation. The player loads only when the durable call record still has an unexpired voicemail recording.
- Added a private media boundary that rechecks the signed-in office teammate,
web-role eligibility, authorized locations, and the exact conversation and call before requesting provider media. Provider recording URLs and credentials are never returned to the browser.
- Supports the browser's single byte-range requests for normal playback and
seeking while returning only allowlisted audio headers with private, no-store caching. Returned partial audio must match the exact requested range and byte count and cover the complete span selected from a known total recording length. A seek past the end returns the standard private, bodyless retry response only when that length proves the request cannot be satisfied. Malformed ranges, cross-scope call IDs, expired recordings, missing credentials, and provider failures all fail closed.
- Kept credential rotation safe by selecting the current or retained
historical provider account bound to the authorized call. Legacy calls with no stored account can use only that organization's current credential.
- This web-only release does not add callback calling, the full Phone
workspace, scores, coaching, or AI summaries, and changes no approved native mobile UI or mobile data contract.
V1 conversation filters and factual lifecycle cards
- Restored the selected-conversation All, Texts, Emails, Calls,
Voicemail, Notes, and System controls in the web Inbox. Each available control shows its count, empty channel controls stay hidden, and a newly selected conversation opens on All.
- Kept texts, emails, connected/missed calls, voicemail, team-only notes, and
system activity distinct. The System view includes durable system-channel messages plus factual customer-document lifecycle cards.
- Added Proposal sent, Proposal withdrawn, Invoice sent, and
Invoice voided cards. Proposal send and withdrawal come from immutable revision timestamps; invoice send and void come from the retained accounting document timestamps. The timeline does not infer events from current status or message wording.
- Bound every projected lifecycle event to the conversation's exact
organization, location, and matched customer. A proposal from the same customer at another service location cannot appear in the selected thread.
- This web-only release changes no native mobile layout, navigation, control,
label, styling, information hierarchy, interaction, route contract, or PowerSync exposure.
Whole-Inbox quick counts for web triage
- Restored V1's Needs reply, Estimate views, Invoice views, and
Missed calls quick counts below the web Inbox search. Each total covers the teammate's complete authorized Inbox rather than only the currently loaded page or the current search/filter result.
- Made Needs reply use the same anchored response state shown in list rows
and the selected conversation. Missed calls uses the authoritative outcome of the latest linked call activity. Estimate and invoice counts join through the conversation's exact organization, location, and customer and count conversations rather than raw page-view events.
- Made each count an actionable toggle for its matching existing filter. It
composes with the current search, ownership, unread, channel, engagement, outbound, priority, and sort selections, and selecting an active count turns that filter back off.
- Loads counts independently once when the Inbox opens and refreshes them after
relevant staff workflow changes, including creation through New Message, saved internal notes, send-snapshot conflict recovery, and every saved send result when delivery is queued, fails, or is refused. It does not run a recurring full-Inbox scan for every open browser tab. Reloading the page requests fresh totals after external customer activity. A count failure leaves the last successful values and conversation list intact and shows Quick counts are temporarily unavailable.
- Adds a bounded Retry action when counts fail to load. It is disabled while
retrying and does not add recurring background count polling.
- Starts staff-action count reconciliation as soon as the server confirms the
saved change, so a later thread or list refresh error cannot suppress the count request.
- Verified exact results against a 12,000-conversation PostgreSQL corpus. The
warm four-count query measured about 139 ms in the test environment, below the two-second performance ceiling.
- This web-only release changes no native mobile layout, navigation, control,
label, styling, information hierarchy, interaction, route contract, or PowerSync exposure.
More customer-document controls in the web Inbox
- Added Viewed in 24 hours to the Customer engagement group. It selects a
conversation only when its newest authorized estimate or invoice view falls inside the trailing 24-hour window. Both edges of that window and the document-view order are fixed to the first page's timestamp, so a customer view that arrives afterward waits for refresh instead of entering or reordering an in-progress Load older sequence.
- Added separate Latest estimate view and Latest invoice view orders.
Each order uses only its named document type, keeps conversations without a matching view after those that have one, and preserves the selected order through Load older paging.
- Kept multiple Customer engagement choices as either-match conditions while
continuing to combine that group with search, ownership, reply, unread, channel, missed-call, last-outbound, and priority filters.
- Kept the projection bound to the conversation's exact organization,
location, and customer. This release changes no visible native mobile UI or mobile sync contract.
- Verified all three new controls against a 12,000-conversation PostgreSQL
corpus. Warm first pages measured about 60–66 ms in the test environment, below the two-second performance ceiling.
Customer document engagement in the web Inbox
- Added Estimate viewed, Invoice viewed, and Viewed multiple times
under the web Inbox's Customer engagement filters. Selected values use either-match behavior inside that group and continue to combine with search, ownership, reply, unread, channel, missed-call, outbound, and priority choices.
- Made Viewed multiple times depend on one estimate's authoritative stored
view count being greater than one. It does not infer repeat interest from message copy or from views of separate documents.
- Added Latest estimate/invoice view sorting. Conversations with recorded
document activity are ordered by the newest view, while conversations with no recorded view remain after them. The exact engagement filter and sort scope is bound to paging cursors.
- Added factual row indicators and chronological thread cards for estimate and
invoice views, proposal acceptance and decline, and invoice payment. Cards link to the authorized record and show stored view counts, first-view time, accepted option and amount, neutrally labeled decline reason, or invoice amount when those facts exist.
- Bound every projected document event through the conversation's exact
organization, location, and matched customer. A view from another location cannot appear in the conversation, and polymorphic engagement rows are not trusted without joining back to an authorized estimate or invoice.
- Verified the filters and sort against a 12,000-conversation PostgreSQL
corpus. Warm first pages measured about 63–70 ms in the test environment, below the two-second performance ceiling.
- This bounded release does not add Viewed in 24 hours, document-view quick
counts, estimate-only or invoice-only view sorts, remaining V1 system-event cards, attachments, AI assistance, or saved views. It makes no native mobile layout, navigation, control, label, styling, information-hierarchy, interaction, route-contract, or PowerSync change.
2026-08-23
Authoritative Calls and Missed calls in the web Inbox
- Added Calls to the web Inbox's multi-select Channel group. Calls and
voicemail fold into that one choice, and the filter follows the conversation's latest external activity. A newer call or voicemail matches Calls; a newer Text or Email moves the conversation to that newer external channel. Internal notes do not change it.
- Added the independent Missed calls filter from the authoritative outcome
linked to the latest durable call activity. It matches missed or voicemail and clears after a later connected call. It is not inferred from message text, and a later Text, Email, or internal note does not rewrite the call outcome.
- Made web Inbox priority call-aware without bypassing response workflow.
High still requires the conversation to need a human response, then includes an unresolved missed call or voicemail as well as an unread customer Text or Email. Mark Responded and successful teammate responses remove High instead of leaving an older missed call permanently urgent.
- Added factual call and voicemail presentation to conversation rows and the
timeline. Call cards show the stored outcome, caller, duration when known, retained voicemail transcript when available, and Voicemail recording expired after the retention boundary.
- Applies Calls, Missed calls, and call-aware priority on the server before
paging. Their exact scope travels with the cursor, so Load older cannot mix rows from an earlier filter state. The filters retain the existing organization, location, ownership, search, reply, unread, and sort scopes.
- Hardened call-to-conversation matching across organization, location, and
conversation boundaries. A malformed cross-thread reference cannot make one customer's call outcome appear on another customer's Inbox thread.
- This bounded web release does not add recording playback, Call Back,
outbound company-line calling, scores, coaching, AI summaries, the full Phone workspace, or the V1 Missed calls quick count and per-thread call filters. It makes no native mobile layout, navigation, control, label, styling, information-hierarchy, interaction, route-contract, or PowerSync change.
- Corrected the V2 parity record: the scoped inbound connected-call,
missed-call, voicemail, transcript, and retention lifecycle already exists. The remaining Phone gap is its outbound calling, playback permissions, intelligence/coaching, full workspace, and approved native presentation—not the inbound lifecycle itself.
V1 filter and sort workflow in the web Inbox
- Restored the V1 message-backed filter catalog under Filters & sort in the
web Inbox. Reply status offers All, Needs reply, and Replied; Channel offers multi-select Text and Email; Last outbound offers Human reply and Automation; and Priority offers Normal and High. High is the focused intersection of an unread conversation that also needs a human reply over Text or Email; call/missed-call priority remains follow-up parity work.
- Added the seven message-backed V1 sorts: Latest inbound, **Our last
message, Oldest unanswered, Latest activity, Latest human reply, Oldest human reply, and Longest waiting for reply. Latest activity** remains the default and preserves the existing newest-conversation order. Latest/oldest human-reply ordering compares the qualifying customer inbound and successful teammate reply; automations and internal notes are excluded.
- Filter groups combine with each other, while selected values inside the
Channel group use either-match behavior. Existing All, Mine, and Unassigned ownership controls and the independent Unread control continue to combine with the new choices. Search, filters, and sort are applied on the server before paging, so Load older continues the exact selected view. Channel selection follows the conversation's latest external activity; a newer call or voicemail matches neither Text nor Email.
- Keeps internal notes from changing the customer-facing channel and keeps
automated messages distinct from human replies. The filter catalog does not infer a teammate response from a note or an automation.
- Distinguishes a truly empty Inbox—Your inbox is empty / **New customer
conversations will appear here.—from a constrained zero result—No matching conversations / Try changing or clearing your filters. The latter offers Clear filters**.
- Shows an active-filter count while Filters & sort is collapsed, keeps a
visible Clear filters action, and reports list failures in the left panel with Try again instead of showing a false empty state. A successful refresh or Load older request clears the prior list error.
- Reconciles read state from the server after a conversation opens, so a newer
inbound message cannot be hidden by an older read boundary. Automatic refreshes also retain the complete loaded prefix beyond 500 conversations.
- Calls and missed-call filters, document-engagement filters and sorts, AI
assistance, and saved views remain follow-up parity work. This release adds no native mobile layout, navigation, control, label, styling, information hierarchy, interaction, route-contract, or PowerSync exposure change.
Explicit service-message permission for new customers
- Added separate, unchecked Service texts and Service emails choices to
the web New client and lead quick-create forms. Staff record how the customer gave permission and may add an audit note; entering a phone number or email address never creates consent by itself.
- Saves selected service grants atomically with the customer, contact methods,
property, and lead. A failed customer or lead save rolls the permission history back with the rest of the graph, while an existing-customer lead leaves prior consent unchanged.
- Separates Service messages from Marketing on the customer profile.
Authorized web teammates can append an explicit service grant or revocation for the exact customer contact method, and Review & Send uses that resulting transactional state for estimate email/text eligibility.
- Keeps actor, source, permission basis, optional note, and web surface in the
append-only consent audit record. The server binds the exact customer, contact method, and channel instead of trusting browser-provided audit data.
- Rechecks the exact active teammate and current office access inside the
grant transaction, including concurrent teammate deactivation, role downgrade, office reassignment/deactivation, and customer-office unlink races. New grants require an active office, active customer, active contact, and deliverable destination; revocation remains available for archived identities.
- Converts digits-only saved phone numbers to E.164 at the outbound SMS
provider boundary. Invalid legacy destinations fail locally without calling the provider, while internal matching, consent history, and message identity remain stable.
- Validates every new web phone entry before it is saved. Ten-digit North
American numbers and explicit + international numbers remain supported; extensions, letters, short country-ambiguous forms, and unprefixed international-looking values are refused instead of being assigned a guessed destination. Existing valid normalized international contact methods remain eligible for service-message permission and delivery.
- Adds no native mobile layout, navigation, control, label, styling,
information hierarchy, or interaction change. The approved V1 mobile UI remains frozen.
Start a customer conversation from the web Inbox
- Restored the V1 New Message action and Start a Conversation customer
picker in the V2 web Inbox. Search returns active customers and service sites inside the teammate's organization and location access instead of exposing inactive or unrelated records.
- Requires an explicit Location when a customer is available at more than
one accessible location. The picker never guesses which location owns the conversation or sending identity.
- Adds SMS and Email channel review with an exact, read-only To
destination. An existing thread previews its established reply destination rather than a different primary contact. Missing contact details or consent remain visible before the teammate opens the composer.
- Open conversation creates or reuses the correct location-bound thread and
opens the existing hardened composer with the reviewed channel. The composer repeats the recipient in a read-only To line. Every web email or SMS send, including an uncertain delivery retry, stays bound to the exact contact and destination displayed there. The dialog itself does not send a message or create a second delivery path.
- Search failure stays retryable. Stale location, recipient, or consent review
clears the outdated choice and refreshes the bounded customer results for a new selection. A changed established reply destination is refused instead of silently opening a composer for another recipient. A change after the composer opens is also refused before delivery while the draft is preserved and the current recipient is refreshed. Reopening a thread preserves its existing draft, and provider failure or uncertainty continues using the Inbox's safe-retry behavior.
- Rechecks the exact conversation activity, recipient, active contact,
consent, service-site eligibility, teammate role, and location access at the final provider boundary. A concurrent customer reply, call update, portfolio grouping, access change, or consent/contact edit now stops delivery and keeps the attempt safe for review instead of sending against stale context. The web Inbox identifies whether staff should read new activity, review the contact, or refresh changed access instead of labeling every refusal a contact change, and announces that refusal guidance to assistive technology.
- Adds no native mobile layout, navigation, control, label, styling,
information hierarchy, response-contract, or PowerSync change. Mobile provider-boundary validation is backend-only.
Locked authorization for manual proposal deposits
- Deposit received and Waive now require the same current **Manage
invoices plus Issue refunds** authority as other office money actions.
- The payment service locks and rechecks the exact active signed-in member,
both grants, and current all-office or same-office access inside the deposit transaction. A deactivation, permission removal, or office reassignment that commits first blocks a stale action without releasing any job's payment gate.
- Same-outcome retries remain idempotent and retain one attributed audit event.
Verified Stripe settlement keeps its separate system path, and monthly membership signup still cannot be bypassed with the manual actions—even before plan reconciliation creates its row.
- This security hardening changes no visible web or mobile layout, navigation,
labels, controls, styling, or PowerSync exposure.
Shared web Inbox ownership and response triage
- Restored the V1 web Inbox's server-backed customer search and All,
Mine, Unassigned, Needs response, and Unread controls. The three ownership choices are mutually exclusive; response and unread are independent filters that combine with ownership and each other.
- Added durable conversation ownership with an Owner selector for eligible
active teammates. Assignment is organization- and location-scoped, excludes crew-only members, releases automatically when a teammate becomes ineligible, and uses optimistic revisions so one manager cannot silently overwrite another manager's newer change.
- Added Waiting on us / Waiting on customer status with **Mark
Responded and Mark Needs Response actions. Manual status is anchored to the latest qualifying message or call version, so new inbound activity, a successful staff reply, or a changed call outcome supersedes stale manual triage. Empty and automation-only conversations can also be marked Needs response**; later conversation activity supersedes that event-free manual state safely.
- Applies search and filters before deterministic server paging, including
customer, sender, location, property address, customer-message, and internal note content. Load older continues the same filtered list, while polling reconciles the loaded section without page-boundary gaps or duplicates. An open conversation and its per-thread draft remain visible if an update moves its row outside the active filter.
- Added a one-pane responsive web layout with Back to conversations on
narrow screens. This release changes no native mobile layout, navigation, control, label, styling, information hierarchy, interaction, route contract, or PowerSync data exposure.
Edit proposal email and SMS copy before web send
- Restored the V1-style editable message previews inside the V2
single-screen estimate builder's Review & send and Review & resend dialog. Staff can edit the email Subject and Message, edit the SMS preview, and use the familiar 160-character SMS guide before confirming.
- Builds the starting copy from the current customer and workspace, then binds
the normalized message to the exact selected channels in the same durable proposal delivery event as the immutable revision. Retries therefore keep the confirmed copy instead of rebuilding it from later office data.
- Adds the freshly minted secure proposal link as a protected server-side
suffix. It cannot be removed, corrupted, or replaced by an office edit. Subjects reject header line breaks, selected messages cannot be blank, and server limits keep email/SMS payloads bounded.
- Keeps the editable preview in a browser-safe module while the server remains
authoritative for normalization and validation, preventing communication providers and other server-only dependencies from entering the web bundle.
- Sanitizes workspace and customer display names before placing them in the
generated subject, email body, or SMS preview, and bounds the subject after cleanup. Even long names or names containing pasted control characters now produce valid starting copy instead of disabling confirmation.
- Preserves the reviewed email's intentional line breaks in both the plain-text
delivery and safely escaped HTML delivery, so the received message matches the Review & Send preview without treating customer-entered text as markup.
- Applies edits only to the initial proposal send or resend. The scheduled
unanswered-proposal follow-up keeps its standard transactional copy. Automatic PDF email attachments and separate legacy/fleet-builder migration remain parity work.
- Adds no mobile layout, navigation, control, label, styling, information
hierarchy, or interaction change. Existing mobile send and in-person presentation behavior remains unchanged.
Review exact proposal recipients before web send and resend
- Restored the V1-style recipient and channel checkpoint to the V2
single-screen estimate builder. Review & send and Review & resend show the customer's exact active primary email and mobile number, identify a missing or unconsented channel, and offer Email and text, Email only, or Text only.
- Revalidates the selected contact method, normalized destination, and
transactional consent while the immutable proposal revision is issued. A stale tab cannot silently retarget a bearer proposal link; a changed or unavailable recipient refuses the send with no revision or delivery event created.
- Requires that reviewed delivery selection at the web API boundary as well as
in the visible dialog. A stale or direct web caller cannot bypass Review & Send by omitting the selected channel; the separate mobile/internal service path retains its existing compatibility behavior without a mobile UI change.
- Carries only the approved channel set and exact destination into the durable
proposal notification and delayed follow-up. Office success now says the revision was Queued by the selected channels instead of claiming provider delivery.
- Applies the same checkpoint and rotating secure-link behavior to current V2
single-screen Generic Quotes. Each send or resend issues a new immutable revision and invalidates the prior customer link.
- This recipient-review release initially kept standard transactional message
copy. The later editable-copy release above restores V1 email/text editing; automatic PDF email attachments and bringing separate fleet/legacy builders onto this boundary remain parity work.
- Adds no mobile layout, navigation, control, label, styling, information
hierarchy, or interaction change. Existing mobile sends snapshot currently eligible destinations and retain in-person presentation.
Manager canvassing CSV import
- Added the V1-style Property CSV import workflow to each web territory:
Download sample CSV, Choose CSV file, Preview import, a bounded row/error summary, Confirm properties, and Start over. The existing territory context replaces a redundant second campaign/territory selector.
- Supports up to 5,000 rows and 5 MB, server-side geocoding for at most 100
missing-coordinate addresses, organization-private cache reuse, exact-address matches, within-12-meter matches, already-targeted detection, and explicit invalid/outside-territory errors.
- Keeps confirmation disabled until every row is valid. Previews expire after
one hour, stale territory revisions reload the latest target list, and confirmation repeats the complete authorization, lifecycle, boundary, duplicate, and match checks atomically.
- Keeps partial-coordinate rows in the preview as correctable errors instead
of aborting the file. Differently named rows within the same 12-meter reuse radius are marked as duplicate rows before confirmation, so the approved new count cannot be collapsed silently while saving. Multiple exact/nearby rows that resolve to one saved property are also marked before confirmation, so the confirmed result cannot silently turn a second approved row into Already targeted.
- Normalizes supplied, cached, and geocoded coordinates to the database's
seven-decimal storage precision before every saved match, boundary, and within-file duplicate decision, keeping preview and confirmation identical at the true 12-meter threshold.
- Treats an address whose Unicode normalization expands beyond the private
staging-key limit as a correctable invalid row instead of aborting the whole preview.
- Uses
GOOGLE_MAPS_SERVER_API_KEYas the canonical server geocoder variable,
while temporarily accepting the existing server-only GOOGLE_MAPS_SERVER_KEY alias; the canonical value wins when both exist.
- Preserves the confirmed-property count after the target list adopts its new
revision. Mixed tRPC batches containing any canvassing request now retain the same private, no-store response boundary as a canvassing-only request.
- Locks the CSV picker while a preview is processing and discards a late
success or error after the selected file or territory changes, so an older batch can never appear under a replacement file.
- Uploads raw CSV bytes through an actor-bound private signed path, deletes the
object before returning a preview, and retains only immutable parsed evidence plus a source digest. The path is recorded before the signed URL is returned; a consumed one-day tombstone outlives the two-hour signed PUT capability, so reusing that URL cannot create an untracked file. The hourly app-owned job deletes any recreated or abandoned raw object before it retires the ticket, because Supabase Storage does not provide the required lifecycle backstop.
- Provisioned the private
canvassing-csv-importsbucket for Production and
Preview with a 5 MiB cap and text/csv restriction; raw bytes remain outside PostgreSQL and PowerSync.
- Keeps all four CSV preparation relations—geocode cache, preview batches,
staged rows, and upload tickets—private and outside PowerSync.
- Added database support for retaining missing-field rows as invalid preview
evidence without weakening the complete-text and coordinate requirements for confirmable rows. Oversized invalid text is recorded as an error and safely bounded in private evidence.
- Runs the owner/service-only expired-preview purge hourly in bounded batches,
and claims at most 25 expired raw objects at a time with UUID-owned leases. It treats already-missing objects as cleaned, defers transient Storage failures, and continues safely without placing raw paths, addresses, or owner fields in maintenance results or continuation payloads.
- Kept upload objects, geocode cache, preview batches, and preview rows outside
PowerSync. No mobile layout, navigation, control, label, styling, information hierarchy, or interaction changed.
First canvassing property-loading slice
- Expanded web Campaigns & territories beyond boundaries and assignments.
Authorized managers can now add a complete address at an exact map point or load an eligible active client property from the CRM without creating a placeholder customer. Client properties already linked to leads are eligible, but this slice does not add separate client and lead source selectors.
- Added private reusable canvassing properties, exact campaign/territory
targets, and the Target properties panel. It reports authoritative total and unvisited counts, pages addresses through Load more target properties, and reloads stale target pages before asking a manager to review and retry. Because visits do not exist yet, unvisited truthfully equals total.
- Added an inline satellite point picker with exact Latitude and
Longitude entry as the fallback when the map is unavailable. The CRM loader searches by Customer or address and adds selected eligible properties to the open territory.
- CRM selections now clear whenever a replacement search starts, so a hidden
result from an earlier search cannot be added accidentally. Search and every required manual-address or coordinate field also expose their purpose and required state to assistive technology.
- Reuses CRM properties by their stable source record, even if the CRM address
or map point is corrected later. Exact normalized-address and within-12-meter matches are also reused without replacing saved canvassing history. Concurrent map and CRM additions cannot create duplicate campaign targets, and a territory boundary edit is rejected if it would exclude a saved target.
- Keeps sibling-office property details private. View-only office-side users
can review canvassing history only inside their authorized office scope and receive no manager controls or team directory; view-only sales representatives remain limited to targets in active assigned territories.
- When assignee identities are intentionally withheld from a view-only office
user, the territory shows Assignment details hidden instead of falsely reporting that the work is unassigned.
- Rejects property-loading service calls unless they enter through the
authenticated application database role with the exact organization and member scope. The narrowly elevated compatibility read returns to that role and rechecks access before returning any target rows.
- Serializes member offboarding, campaign and territory changes, assignment
replacement, and property loading in one member-to-organization-to-work lock order, so simultaneous office actions cannot deadlock or finish with stale access.
- Moving unvisited targets, functional Field
view Map/List modes, visits, dispositions, do-not-knock, follow-ups, GPS, reporting, competitions, XP, and AI remain later parity work. This release adds no mobile layout, navigation, control, label, styling, information hierarchy, or interaction.
Private canvassing CSV import foundation
- Added strict CSV parsing plus private staging and atomic confirmation records
as the security and storage foundation used by the manager workflow above.
- Bound confirmation to the authenticated organization and member, kept the
caller under the restricted application role, and aligned teammate, organization, and territory lock order with identity-provider member removal. Replays and rejected cross-organization or manager-impersonation attempts cannot create another target mutation or audit entry.
- Added bounded service-only cleanup for expired, unconfirmed previews.
Confirmed batch and row evidence remains immutable, retained, and excluded from that cleanup.
- Corrected nearby-property reuse across the international date line, in polar
regions, and at the true edge of the 12-meter duplicate radius. Spherical bounds now conservatively include every possible match before the exact distance check, so those rows confirm against the saved property instead of aborting the batch.
- Kept the staging records and secure geocode cache outside PowerSync and
mobile data. The manager web workflow now supplies the private raw-upload, retention, and geocoding orchestration described above; no mobile UI changed.
Downloadable and resendable invoice payment receipts
- Added immutable, customer-safe receipts for successful Stripe and recorded
office invoice payments. Customers can download them from Portal > Payments, while authorized office teammates can download or email another copy from invoice payment history. Emailed copies use a 30-day signed link bound to the exact customer, receipt, delivery, and still-active Billing or Primary email, so the recipient does not need to sign in on that device. The recipient chooser reports a last-moment address or eligibility change in place and does not send to the retired address.
- Automatic and office-requested receipt emails use durable idempotent delivery
records, so Stripe webhook replays, same-command retries, and repeated background work do not create duplicate receipt events. Existing successful direct invoice payments gain downloadable receipts; automatic email starts with newly settled payments. Receipt PDFs and messages expose no card data, provider payloads, credentials, internal notes, or fees.
- Receipts identify the original payment and state that later refunds or
adjustments appear on the current invoice and payment history rather than rewriting the original receipt.
- This release does not yet add a dedicated refund action for deposits carried
forward from accepted estimates; that remains a documented follow-up against the original estimate payment. No mobile UI changed.
Residential margin truth in the single-screen estimate builder
- Restored the V1 residential margin workflow without restoring the estimate
wizard. Authorized pricing managers now see a Margin Calculator directly below Estimate total, with revenue, crew pay, materials, franchise fees, total costs, profit, and margin for every offered proposal choice.
- Added private organization settings for target margin, commission or hourly
crew pay, franchise fees, and per-service material costs. Explicit zero is preserved, while missing cost inputs show Cost needed instead of an optimistic partial calculation.
- Sent revisions keep an immutable internal costing snapshot. Internal rates,
costs, hours, profit, and margin remain structurally absent from public proposals, the customer portal, PDFs, email, and text delivery.
- No mobile layout, navigation, control, label, or styling changed.
Private Residential Margin storage preparation — not yet available
- Staged private, service-only records for organization margin configuration,
estimate costing drafts, and immutable sent-revision costing snapshots. Internal hours, determinants, and results stay outside synchronized estimate, catalog, customer, and mobile records.
- This is inactive schema preparation only. It does not add or change a margin
calculator, pricing settings, estimate behavior, APIs, customer views, or mobile controls. The V1 margin workflow remains a parity target for a separately released V2 service and UI.
Private cache boundary for signed-out appointment work
- Marked signed-out office redirects and unauthenticated or forbidden API
responses as private and no-store, including the Tasks appointment queue. Authentication behavior is unchanged; the hardening prevents browsers and intermediary caches from retaining staff authorization responses.
- Kept successful and public API response caching unchanged and made no mobile
layout, navigation, label, style, or interaction changes.
Guarded customer appointment-change requests in Tasks
- Added a signed-in customer Work action for requesting a future date with
morning, afternoon, or no time preference, or asking to cancel an eligible upcoming appointment. The confirmed booking remains authoritative until the office decides, one pending request is reused across repeated sends, and the customer keeps the pending or final outcome on the job card.
- Restored the V1 office workflow at the top of Tasks instead of inventing
another Calendar view. A current Manage calendar teammate can review only their office scope, must enter an exact time before approving a reschedule, and must provide decline or conflict-override reasons. Stable scoped paging keeps the queue bounded.
- Revalidates customer identity, staff permission, location access, job
revision, and wall-clock eligibility inside the locked decision. Concurrent schedule changes, session revocation, teammate deactivation or reassignment, duplicate sends, and duplicate decisions fail closed without overwriting a newer appointment or forging audit history.
- Added immutable service-role request writes, exactly-once decision events,
database guards, and indexed tenant/location/job history. No Calendar or mobile layout, labels, controls, or styling changed.
Appointment request storage preparation — not yet available
- Prepared the tenant- and location-scoped database records, audit guards,
queue indexes, and migration needed for the upcoming customer appointment request workflow.
- This is inactive storage preparation only. It does not add a customer portal
request form, an Admin > Tasks review queue, Calendar approval actions, API access, or mobile controls. Customers should continue contacting the office directly until the complete follow-on workflow is released.
Fail-closed V2 production migration readiness
- Changed the V2 production migration job so disabled migration controls or a
missing production database connection fail the release check instead of reporting success after skipping schema work. Production migrations remain serialized and wait for the complete CI and production-build gates.
- Corrected the release guidance to reflect that Vercel's Git build and GitHub
Actions begin independently. The production-order guarantee becomes active only when the Vercel project requires migrate-production as a Deployment Check. That production-only blocking check is now configured with a 3,600-second timeout, so normal domain promotion waits for the exact release migration to pass; Force Promote remains an emergency bypass.
Durable Clean Estimate V2 invoice workspace and customer payment flow
- Added a responsive web Invoices workspace with server-backed search,
status, office, issue-date, and sort controls; authoritative 25-record pagination; and real outstanding, overdue, and collected invoice totals. Newest, Oldest, Nearest due, and Highest total each use a stable indexed keyset with an invoice-ID tie breaker, and a cursor cannot be reused with a different tenant, access scope, filter, or sort. Single-office lists use location-leading seek indexes, keeping their page work bounded from sibling-office invoice volume. Past-due partial payments remain in Overdue and contribute their remaining balance.
- Added a one-screen draft builder with guarded customer/property selection,
itemized quantities and prices, discount, exact tax, issue and due dates, customer note, payment terms, server recalculation, optimistic edit safety, service-office timezone defaults, and atomic INV-0001 numbering. First send rechecks the customer, property, and exact linked job/estimate lineage.
- Added invoice detail, immutable issued-party and line-item snapshots,
Send invoice, Resend invoice, safe unpaid voiding, settled payment history, customer link, authenticated office preview, and customer-safe PDF. Paid and partial states come only from the ledger; there is no manual mark-paid shortcut.
- Treats V1 destructive deletion and edits to already-issued invoices as an
intentional safety exception. Drafts remain editable, sending freezes the accounting document, and retained voiding replaces deletion so delivery, payment, refund, and audit history cannot disappear.
- Kept first-send time immutable and added a monotonic delivery version, so
concurrent resends cannot return conflicting links. Resend expires the old link's registered open Checkout first and stops if Stripe says payment is already completing.
- Added a V1-style Review & Send composer for recipient, email/SMS/both,
email subject and body, SMS copy, invoice preview, and secure-link copy. Every selected channel keeps its own durable queued, delivered, or failed attempt, with failed work shown as Action needed; resend creates a new auditable attempt without rewriting the issued invoice or hiding an earlier failure.
- Revalidates and locks the exact reviewed contact method, active contact,
invoice version, and token through provider acceptance. Recipient, consent, or configuration changes are retained as final action-needed refusals instead of retrying forever or falling through to another contact.
- Added a private, no-store, noindex, no-referrer public invoice view plus a
signed-in customer Invoices history with complete cursor pagination, current balances, retained sent-then-voided records, PDF downloads, and Stripe Checkout for the exact outstanding balance. Checkout-return copy says paid only after the webhook-backed ledger reaches zero.
- Kept the issued service address in office lists, detail, search, summaries,
customer history, and PDF after a later property edit. Draft previews use current CRM party details; voiding an unsent draft freezes those current details, while a sent invoice always retains its issued snapshot.
- Bound public and account Checkout to the exact invoice, tenant, customer
return boundary, token or customer session, provider workspace, connected Stripe account, amount, currency, document version, and registered payment row. Reuse, replacement, expiry, completion, delayed failure, settlement, and refunds all keep the original account snapshot, including after a provider reconnect or an account-scoped object-ID collision.
- Revalidates the signed-in customer's session and complete current customer
scope inside the payment transaction. Contact deactivation, reassignment, email changes, and service-location removal cannot race an old session into creating a payable link.
- Keeps estimate deposits and invoice collections as separate accounting
targets. An eligible settled deposit can reduce an invoice only through a separately labeled, auditable carry-forward credit; split and partial allocations serialize so the same deposit cents cannot be spent twice, and refunds reduce paid-to-date by their real refunded amount.
- Added governed admin partial and split invoice collection across cash, check,
ACH/bank transfer, external processor, and card through hosted Stripe, plus controlled partial refunds tied to the original settled tender. Immutable payment/refund attempts retain actor, method, provider/manual reference, and pending/succeeded/failed truth; invoice, portal, client, totals, balance, and earnings projections all use the same ledger.
- Office Collect payment, including hosted Checkout, and Refund now
lock and revalidate that the staff member is still active with both Manage invoices and Issue refunds. A concurrent deactivation or grant removal wins and blocks the stale action. Current all-office or same-office access is rechecked under the same lock, so reassignment-first also denies collection, Checkout, refund, and refund retry. Direct payment inserts, refund-command changes, and delivery-outcome writes fail closed outside their controlled services.
- A collection or refund reaches its terminal money result before the invoice
detail is refreshed. If that follow-up read fails, the office sees a separate refresh notice instead of a false payment/refund failure, and the original idempotency identity remains safe to replay. Pending provider-refund recovery is service-only and must match the exact tenant, invoice, payment, command, actor, amount, reason, idempotency key, connected account, and provider payment before it can resume.
- The office card choice creates or reuses an exact connected-account Checkout
and then shows Open secure card checkout plus a copy link. Opening or sharing it is not payment proof; only the verified Stripe webhook settles the invoice, and a different account, amount, invoice, or return surface cannot reuse that session. A staff-device open may return to the office invoice; a copied link uses a customer-safe public invoice/confirmation return and never sends the customer to admin sign-in.
- Kept freeform staff payment references and notes office-only. Bearer invoice,
signed-in customer, payment-history, and PDF projections expose only allowlisted method labels and customer-safe ledger facts, never the staff text or raw provider payload.
- Serializes customer Checkout, office collection, deposit allocation, and
refunds against the remaining balance, preventing concurrent overcollection. The first zero-balance transition publishes invoice.paid exactly once; retries, later refunds, and recollection do not publish a second first-paid event.
- Added a V1-compatible invoice-management permission for web office roles.
Revoked teammates retain read-only history while create, edit, send, resend, and void controls disappear. Collection and refunds require the additional locked Issue refunds grant described above. /invoices is covered by the private-route and deployment-smoke inventories. The approved mobile UI is unchanged.
- Added client-profile invoice history, valid service-client New invoice
prefill, a Create invoice handoff from eligible jobs, visible-child portfolio rollups, invoice payments in client payment history, and service-location timezone handling for due status and customer events.
- Added idempotent invoice-open engagement events from the secure link,
signed-in account, and direct customer PDF. A raw request, security scanner, link preview, crawler, office preview, or Checkout polling does not count; the interactive customer browser must explicitly acknowledge the rendered invoice first.
- Included settled invoice collections and refunds in the linked estimate
salesperson's earnings without copying the invoice payment onto the estimate ledger, and kept the ledger compatible with Suds Club installments.
- Online payment is hidden and rejected above Stripe Checkout's USD ceiling of
$999,999.99; the customer receives contact-the-office guidance instead of a payment button that can only fail.
- V1 create-from-estimate, reminders, receipts, embedded/keyed card, tips,
credits/write-offs, saved methods, tags, attachments, tasks/internal notes, bulk billing/CSV, branding, global search, recurring invoices, automation, accounting sync, and mobile workflows remain explicit parity work. V1's configurable generic invoice lookup by invoice number and customer email also remains V1-only; current V2 customers use their authenticated Invoices account or secure invoice email link.
- Restored the complete retained V1 invoice procedures as a separate
Invoices (V1) guide beside the current V2 workflow, and clearly labels retained V1 invoice automation and public-lookup documentation as V1-only.
Signed-in estimate acceptance and deposits
- Customers can now open an eligible estimate from their secure account,
compare the same immutable choices as the delivered proposal, select one, sign it, and pay the exact required deposit without searching for the original email or text.
- Preserved the approved customer offer story: requested work first, Suds Club
or maintenance next, and additional coverage last when those roles are unambiguous on both the delivered proposal and signed-in account. Explicit requested/original-scope labels take precedence over a generic “one-time” label. An all-one-time neutral set retains its authored order and neutral Option badges. For a legacy exact three-card set with one maintenance choice and two otherwise neutral one-time choices, the first authored one-time choice becomes As requested, maintenance stays in the middle, and the remaining one-time choice becomes More coverage.
- Added customer-safe included scope, frozen maintenance cadence and visit
values, settled payment totals, and clear manual-resolution or refund guidance to the account estimate detail.
- Kept one authoritative acceptance across the account and public quote. The
active customer session and current scope are checked again under the acceptance/payment locks, closing contact-deactivation, reassignment, and logout races. Identity-changing office statements now take sorted gates only for affected provider workspaces and lock all affected emails in stable order, while customer actions use their provider's shared gate and exact email lock. Real PostgreSQL two-connection tests cover both orderings, insert phantoms, bulk edits, and unrelated providers proceeding concurrently.
- Made settled deposit records authoritative for Checkout amount and customer
copy: partial settled deposits reduce the remaining charge, completed deposits cannot be collected again, refunds return to the office, maintenance installments stay separate, and every extra, wrong-amount, or wrong-return pending Checkout is expired before one safe session is reused or replaced. Every payment and monthly maintenance billing plan now snapshots its connected Stripe account. Completion, delayed failure, recurring invoices, refund, cancellation, and expiry updates require that account and the exact tenant payment or plan row, including after a provider reconnects or when two connected accounts use the same account-scoped Stripe object ID. A failed expiry is mirrored only when Stripe confirms the remote session is expired; open, complete, unknown, or unreachable sessions stay blocking.
- Restricted the opt-in real-PostgreSQL portal race harness to loopback by
default. A separately named destructive remote-test acknowledgement is required for a remote host, and host aliases, effective ports, encoded database names, credentials, and URL options cannot disguise the live app database as a separate test target.
Faster and safer shared Calendar
- Replaced per-event conflict lookups and browser pair comparisons with one
bounded, set-based conflict projection. Organization/date/member/crew indexes keep candidate reads narrow, while a visible event can still be flagged by a sibling-office conflict without revealing that office's customer or event ID.
- Extended capacity candidates two local dates on either side, covering the
full UTC+14-to-UTC-12 timezone difference. Daylight-saving normalization and back-to-back-safe interval boundaries remain intact.
- Limited each board request to a start-to-end difference of 45 days and added
deterministic validation before database work begins.
- Replaced the obsolete global member-mutation batch gate with ordered
per-member calendar gates. Direct determinant updates fail closed instead of blocking in an inverted row/lock order, while identity-provider deactivation acquires organization, member, territory, and assignment locks in one shared order with Canvassing cleanup.
- Added dedicated real-PostgreSQL release coverage for both race orders,
cross-feature cleanup, sibling-office privacy, timezone extremes, and bounded query plans with 50,000 irrelevant appointments and jobs. The destructive harness remains pinned to the exact isolated Calendar database on loopback unless a separately named remote-test acknowledgement is set.
- Permission-gated Manage crews, made Calendar search truthfully include
saved service addresses and appointment types, merged Combined Agenda into one chronological stream, aligned conflict and work totals with active filters, and displayed saved appointment type labels on Calendar events.
- Restored the filtered Scheduled value summary from the existing Jobs
calendar instead of dropping it when sales appointments were added.
- Prevented new job or teammate assignments to inactive crews. Crew scheduling,
teammate reassignment, and activate/deactivate actions now serialize on one crew gate; historical inactive assignments remain visible but disabled, and unsafe direct state changes fail closed behind an active Calendar writer. Crew service locations are now explicitly immutable instead of allowing an unsupported direct move to invert Calendar lock order.
- Closed an atomic lead-takeover path by authorizing sales-runner schedule edits
against the locked current lead owner. A proposed owner change can no longer grant its own appointment authority, and schedule-plus-owner reassignment now requires Manage calendar.
- Lead appointments and service jobs are the current Calendar event types.
V1 estimate appointments and sales follow-up events remain immediate later parity work; they were not removed.
2026-08-22
Shared Sales and Operations Calendar on the web
- Added the protected
/calendardestination, replaced the web Jobs
navigation label with Calendar, and combined sales appointments, service jobs, and accepted work waiting to be scheduled in one workspace. The complete All jobs catalog remains available on the same page.
- Added Combined, Operations, and Sales appointment scopes with Day, Week,
Month, and Agenda layouts; customer/address search; location, crew, and team member filters; a conflict-only view; and separate service-job, appointment, unscheduled-work, and conflict totals.
- Kept lead ownership separate from appointment execution. Web lead forms now
select an Appointment assignee, so a qualified technician can run the sales visit without becoming the opportunity owner.
- Added Manage calendar and Run sales appointments member permissions,
location-aware eligibility, revision-safe concurrent web appointment edits, and auditable conflict overrides.
- Allowed a web-capable teammate with Run sales appointments to schedule
only their own lead for themselves. Teammate assignment and conflict overrides remain limited to Manage calendar, and the mobile-only rep role does not gain the broader web administration surface.
- Added shared capacity checks across jobs and lead appointments, including
back-to-back boundaries, unknown-capacity labels, adjacent-date protection, service-location timezone normalization, and privacy-preserving cross-office conflict badges.
- Preserved the approved V1 mobile layout, navigation, controls, labels, and
styling while making the explicitly approved appointment-save interaction authoritative. Save schedule now waits for the shared calendar, closes only after success, and keeps the existing editor open with a categorized error when the revision, slot, assignee, permission, or connection is rejected. The locked ownership check also prevents a former assignee from saving after an office reassignment wins the race. Mobile users still cannot override a calendar conflict.
- Synced the appointment revision into the mobile lead cache so the approved
editor can open reliably after an app update. Existing-lead schedule changes from released clients that lack the revision now fail closed per operation while offline new-lead appointment capture remains available; the queue continues and sync-down restores server truth.
- Kept mobile schedule authority tied to the actual appointment executor.
Road teammates can preserve or release only their own appointment, and Move others' cards no longer acts as office calendar permission.
- Serialized appointment eligibility against role, permission, active-state,
location, and crew changes, including bulk Clerk deactivations. Location pinning now derives crew membership only after taking the shared member lock, preventing it from undoing a concurrent crew assignment.
- Made scheduled-lead archival advance the appointment revision and made every
scheduler reject archived leads. A stale mobile editor now remains open with a direct refresh-or-restore message instead of reviving hidden work.
Accurate pull-request review gates
- Removed the unconfigured Claude review workflows that were only a proposed
integration and produced false failed checks without a review service. Release evidence remains the required build/test checks, Vercel preview, smoke verification, and exact-head Codex review, plus isolated database race suites when a change adds concurrency-sensitive behavior.
Canvassing campaigns and territories foundation
- Added Canvassing to the V2 office sidebar with the familiar V1
Campaigns & territories workflow. Owners and admins can create draft campaigns, edit details, activate, pause, resume, complete, archive, and restore them without relying on the removed estimate-wizard pattern.
- Added satellite-map polygon drawing, precise coordinate fallback, boundary
editing, territory status, and full sales-rep assignment replacement. New territory creation saves its boundary and assignments in one transaction.
- Added separate View canvassing and Manage canvassing grants,
organization and office-location isolation, forced row-level security, optimistic edit conflict protection, append-only audit history, and scoped indexes. No mobile UI, navigation, labels, or interactions changed.
- Serialized campaign lifecycle and territory writes with one campaign-first
lock order. Database guards now also reject inactive or ineligible assignees, locationless sales reps, forged assignment actors, and member changes that would invalidate an active territory assignment.
- Tightened territory boundaries to simple 3–100-point polygons: repeated
vertices and self-crossing edges are rejected. The workspace now states clearly that neighboring-territory overlap is not detected automatically.
- Bounded campaign, selected-campaign territory, and eligible-member loading
with Load more controls, preventing one large organization from sending every stored polygon to the browser at once. Versioned cursors retain the selected campaign and V1 campaign, team-name, and territory-name orders. If relevant data changes between pages, the old cursor is rejected and the web workspace reloads the latest list instead of silently skipping or repeating rows.
- Restored the V1 campaign-opening priority independently from pagination:
Active campaigns open before Draft, Paused, Completed, and Archived work, with the most recently updated campaign first inside each status. Campaign continuations retain that selected-first and lifecycle order and cannot be reused against a different campaign.
- Added Canvassing to the private office-route boundary and deployment smoke
inventory, including a real canvassing.bootstrap procedure probe, so a missing route, public shell, or unmounted API cannot pass release QA.
- This is the office planning foundation, not full V1 parity. Field view,
target/property loading, visits and dispositions, route/GPS tracking, reporting, competitions, AI assistance, and canvassing mobile screens remain future bounded releases.
- Kept the required PostgreSQL concurrency gate loopback-only by default. A
deliberately remote disposable test database now requires a separate, explicit destructive-test acknowledgement; the standard isolated CI service continues to run locally without that override.
- Canonicalized the destructive race-test database check against the raw URL
identities used by postgres.js 3.4.9. Percent encoding remains available in credentials before the sole literal @ and in ordinary query values, but the decoded query names database, user, and client_encoding now fail in both URLs because they can override the PostgreSQL startup identity. Empty, repeated, and percent-encoded spellings are covered. Every decoded query key and value, including duplicates, now rejects C0/DEL controls so an encoded NUL cannot terminate one startup field and synthesize another. Encoded usernames and passwords remain supported only when their percent encoding is valid and the decoded credentials contain no C0/DEL controls; safe encoded delimiters remain allowed. Any encoded host/port or database pathname and any raw Unicode hostname also fail closed. Authorities with more than one literal @ and raw comma-separated multi-host connections are also rejected in both the destructive URL and DATABASE_URL. Whole-database teardown is allowed only for the literal pathname /cleanestimate_canvassing_test; a generic test marker or encoded spelling is not accepted. Accepted hosts are limited to raw ASCII LDH or punycode names and unambiguous dotted IPv4. All bracketed IPv6 and mapped-IPv6 connection URLs are rejected because the driver does not parse those host identities consistently. Host comparison still lowercases names, removes terminal DNS root dots, and treats localhost, *.localhost, and all of 127/8 as one loopback identity. A destructive 0.0.0.0 target and ambiguous short, integer, hexadecimal, or octal IPv4 forms fail closed. Arbitrary DNS/CNAME lookup remains outside this guard, so every remaining valid ASCII hostname still requires the explicit destructive-test acknowledgement. Canvassing list mutations now serialize revision publication per organization and publish at most one revision per organization and transaction. Statement-level queues collapse bulk changes before a deferred commit gate publishes them, so the work follows the number of affected organizations instead of thousands of changed member rows. The gate keeps visible revisions in commit order, so a relevant late commit still invalidates every older list cursor.
- Preserved selected-first ordering when switching campaigns in the web
workspace without allowing older browser rows to overwrite fresh campaign revisions or details. Bounded campaign continuation now uses a fixed set of per-status index seeks instead of scanning earlier pages through a broad lifecycle expression, with timestamp ordering pinned to the indexed database column. Office-pinned users now require exactly one office scope, and every campaign/default/continuation read includes that office in its ordered index seek; unrestricted administrators retain the organization-wide index path. A forced-generic PostgreSQL plan regression runs as app_user under RLS with a much larger sibling-office population and rejects a broad scan, an added sort, or more than the page limit plus one rows from the index. The operator guide now clearly separates V1 property and territory-reopen controls from the smaller V2 foundation that is shipping.
- Eligible team-member paging now merges four bounded, index-backed streams:
named and unnamed active owners, admins, and sales reps in the selected office, plus named and unnamed active locationless owners and admins. Each covering index path has a fixed 101-row ceiling before the requested final page limit is applied, keeping fresh/named merges at 404 candidates and unnamed continuation merges at 202 regardless of page size. The literal ceiling prevents generic prepared plans from falling back to a broad bitmap scan and sort, while V1 display-name order and same-name ties remain stable.
- Territory pages now apply the requested page limit, up to 100 rows, before
assignment summaries are joined and aggregated, keeping large campaigns bounded. Each territory still carries its assigned-member summaries, and representatives beyond the current eligible-member page stay pinned, named, selected, and removable in the assignment editor.
- Kept ordinary team-member changes blocked when they would invalidate an
active territory assignment, while making identity-provider offboarding complete safely. A Clerk organization, membership, or user deletion now retires the affected active assignments, preserves their history, records an audit entry for each one, updates each affected territory once, and only then deactivates the member in the same transaction. The affected teammate is kept as the audit subject without presenting them as the person who initiated the provider deletion, and the original assigned by history remains intact. Replayed deletion events do not duplicate audits or revisions, and each affected organization publishes one list refresh for the transaction.
- Added a permanent, service-only deleted-user gate for Clerk identities.
Delayed membership and organization events, a stale signed-in bootstrap, or a direct team-member activation can no longer recreate access after Clerk has deleted the user. Organization setup may still finish after a delayed event, but the deleted creator is not restored as an active teammate.
- Every bootstrap now verifies the organization list revision again after all
campaign, member, and territory page queries. A relevant mid-load commit rejects both initial and continuation responses instead of returning a mixed snapshot. The web closes both editors, clears their drafts, and retries once; if that retry is also stale, it stops and offers Retry rather than entering a refresh loop.
- Archived campaigns now hide Edit campaign as well as territory changes
and direct managers to Restore to draft first. Completed campaigns keep campaign-detail editing available but continue to require Reopen before territories can change.
Trustworthy V2 deployment smoke gate and private office routes
- Added a deployment smoke gate that checks the current named V2 office-route
inventory, the public proposal-token API, sign-in, public home, and representative Tasks, Price Book, Plans, and Canvassing procedures before manual QA.
- Closed false-green cases: missing admin pages, anonymously rendered office
shells, generic HTML token misses, missing tRPC procedures, and anonymously readable API results now fail instead of being reported as healthy.
- Moved private office pages behind the Clerk request boundary so signed-out
visitors are redirected before those pages render. The public home, authentication, customer account, and proposal entry points retain their separate public access contracts. The shared Calendar is guarded at /calendar, while /jobs remains protected for job details and compatibility links. Office invoice list, builder, detail, and preview routes are guarded under /invoices; public /i/... links and customer-account invoice pages retain their separate token and customer-session contracts.
- Added executable contract tests for redirects, bearer-token misses, and
structured unauthorized tRPC errors, and included them in both the normal test command and continuous-integration gate.
- Added protected-preview support through an optional Vercel automation bypass
secret that is sent only as a request header and never logged; an SSO redirect cannot be mistaken for an application smoke-test pass.
- Bounded every deployment probe to 15 seconds so a stalled route fails the
release gate instead of leaving the sequential check waiting indefinitely; a stalled response body cannot pass on its already-received headers.
- Kept protected dynamic office URLs behind sign-in even when a record id
contains a dot that would otherwise resemble a static asset filename.
- Tightened sign-in verification to require the mounted Clerk browser loader
instead of accepting generic sign-in text. Optional approved proposal-token checks now validate the successful customer-safe JSON proposal contract directly instead of treating a client-rendered page shell as proof that the underlying estimate can load.
- Pinned public-page identity checks to the real CleanEstimate home shell and
the dedicated Clerk sign-in route bundle, and rejected cross-origin URLs that merely end in /sign-in as valid office-route authentication redirects.
- Required the public home page's own compiled route identity rather than
layout-only markers, and made the proposal deep check reject any unexpected customer fields or invalid customer-field types at the public boundary.
- Made signed-out office navigation use an explicit same-origin sign-in
redirect with the complete requested page retained for return after login. Direct HTTP probes and link scanners now receive the same stable redirect instead of Clerk's non-document 404 protection rewrite.
Price book
- Added the Price Book as a first-class sidebar destination: one searchable catalog of everything the team sells — services, products, materials, add-ons, equipment, discounts, and custom-item templates — with customer prices and units, unit costs for authorized margin reporting, categories, verticals, SKU/description/thumbnail fields, taxable and quote-picker visibility flags. Every teammate can read customer-facing catalog data, while internal unit costs require Manage pricing or View financial reports and changes require Manage pricing. Items archive instead of deleting, so signed estimates keep their frozen line values no matter what changes in the book.
- Added the shared per-vehicle unit and made item-kind edits persist under the
correct filters. Search, kind, category, and archived-item changes now reject older responses, and a create/edit/archive finishing late refreshes the controls currently on screen instead of restoring an earlier selection.
Maintenance plan lifecycle
- Added Pause and Resume to active maintenance plans. Pausing keeps every existing visit job as-is (crews can still complete scheduled work) while blocking term rollover until the office resumes the plan; cancelled plans can now also come from a paused state, with all existing cancellation guards unchanged.
- Added Extend term, appending one to three whole contract years that clone the final year's seasons and visit scope, up to the supported ten-year aggregate term. The added years materialize through the standard rollover guards (open visits, calendar timing, deposit), and extending again stays additive. Existing and future visit allocations are frozen to their exact accepted cents before an extension, so fractional-cent discount math cannot move a cent away from work that was already created.
- Added Renew plan for terms in their final contract year or already completed. The renewal creates a successor plan linked to its source, starts after the source's last real service year, reuses the accepted cadence shape, and materializes first-year visits immediately through the exactly-once job writer; the source agreement is never modified. Renewal chains can continue beyond the first successor while preserving the original acceptance anchor for rollover and mobile scheduling. Renewal supersedes V1's duplicate action.
Three-choice residential proposals restored in V2
- Restored the established residential proposal layout with As requested
on the left, Suds Club emphasized in the middle, and Complete Property Care on the right. The single-screen estimator remains in place; the old estimate wizard was not restored.
- Auto-attached the square-footage-matched Suds Club membership when a saved
house measurement can price it, including its four seasonal visits and frozen included-service cadence.
- Recalculated Complete Property Care with the same server pricing engine as
the requested scope. A new Complete Property Care additions section in the single-screen estimator accepts optional services with their own verified measurements. V2 does not guess roof area, window count, gutter length, or any other quantity from unrelated property data. The expanded option marks those measured services Added and freezes its own scope and total for signing, job creation, reporting, and payment; it is omitted when no measured addition is configured. Disabled measured additions remain excluded and cannot be restored into a published option by builder reopen, autosave, or normalization. Legacy underscore-style price-override keys reopen on the matching current service control, while a stale override from a disabled requested service cannot leak into an enhanced addition that has no override control.
- Included every customer-selectable option in minimum-payment validation and
below-floor approval checks. An automatic Suds Club choice is withheld when a customized organization floor or unmeasured service-rate basis differs from its approved default service value; the estimator sees the reason instead of publishing unsafe pricing. Package discounts are allocated across their included services before Red Line checks, so Complete Property Care cannot bypass approval when a bundle discount crosses a service floor. Approved Club-only catalog items remain eligible from their membership matrix without requiring a nonexistent one-time price-book section. Disabled or historical House Wash rows no longer contribute stale square footage to an automatic membership offer. The automatic hosted three cards are exclusive: the accepted option remains the only scope that becomes the job or maintenance plan; the signature view and PDF retain that option's own frozen scope, and seasonal jobs receive only that visit's frozen services.
- Included the estimate's effective sales tax in automatic Suds Club option
totals and allocated visit charges so the acceptance total, signup payment, installments, and service revenue all reconcile. The restored comparison now shows the real monthly membership charge and three-payment signup amount. Paying that signup saves the payment method through hosted Checkout and creates the connected-account Stripe Billing schedule for nine later monthly charges; the final charge reconciles any cent-level rounding. Webhook replay reuses the saved schedule, installment invoices carry the exact platform fee, and a failed webhook cannot start future billing after the local plan has been cancelled. Refunded installments now update the payment mirror and refund ledger so collected-revenue reporting stays accurate. Office users cannot mark a monthly signup received or waived and accidentally bypass its recurring schedule. A failed later installment records the delinquency and blocks upcoming plan visits until Stripe confirms recovery, without interrupting work already in progress. A different installment's successful payment cannot reopen visits while another installment remains failed or refunded. A signup that settles before plan reconciliation stays retryable until the plan exists and its billing schedule can be created. Customer cards and PDFs disclose any different final reconciliation installment, and PDFs label visit amounts as allocated service value rather than per-visit charges. Authenticated office PDF previews now use the same selected-option signup or deposit amount as the customer PDF and Checkout. Cancelling the membership must stop future Stripe charges before the local plan can be cancelled. Organizations without a charge-ready Stripe connection no longer publish an automatic monthly membership they cannot collect; explicitly authored monthly memberships remain blocked rather than being silently removed from the proposal. The beginner and payment guides now also state that eligible proposals may show one, two, or three choices and that the standard offline deposit override never applies to monthly membership signup payments.
- Persisted the first visible choice for untouched service type and material
dropdowns, including the enhanced Fence option's Vinyl default, so the displayed selection, price, signed scope, and resulting job cannot disagree.
- Brought the customer cards back to the useful V1 information hierarchy:
itemized services, honest Suds Club plan pricing, seasonal scope, savings, and clear option differences, while retaining V2's safer revision, signature, expiration, deposit, and refund handling.
Shared staff tasks
- Added the office follow-up list as a first-class Tasks destination in the web sidebar. Every teammate can create call, email, meeting, deadline, and to-do items, optionally due at a specific time and linked to a customer, lead, or estimate; open work groups into Overdue, Due Today, Upcoming, and No Due Date, with completed history one toggle away. Mine/Everyone scope mirrors the old list, any task can be reassigned or deleted, and completing one emits an auditable
task.completedevent for future automation triggers. - Made Me an actual reassignment choice while editing, so selecting it moves
another teammate's task to the signed-in user instead of retaining the old assignee.
- Cleared the previous task rows while a changed Mine/Everyone, Open/Completed,
or page selection loads, preventing stale tasks from remaining actionable beneath the newly selected controls.
2026-08-21
Maintenance plans web workspace
- Rebuilt Field > Plans with the Clean Estimate service-operations visual system, responsive desktop rows and mobile cards, real summary values, customer/property/office/option search, status and office filters, four sort choices, stable 25-plan pages, and distinct loading, retry, empty, and no-results states. Mobile plan cards retain the service office and deposit requirement shown in the desktop list.
- Expanded each signed plan into a contract workspace with separate signed total and annualized values, current year and term, accepted option and source estimate, service property, acceptance and deposit state, immutable cadence by year, and linked visit jobs with appointment, crew, status, and value context. Plans whose signed estimate required no deposit now say No deposit required instead of appearing unpaid.
- Added a dedicated per-member Manage plans permission for rollover, completion, and cancellation while preserving location-scoped read access. Owners and admins receive the permission by default, and saved individual overrides remain authoritative.
- Prevented rollover while a required deposit is pending or the current contract year has unscheduled, scheduled, or in-progress visits, retained exactly-once visit creation and calendar guards, and kept sparse no-deposit or settled contract years able to advance without placeholder work.
- Prevented cancellation while a visit is in progress. Confirmed cancellation records the required reason, cancels only future unscheduled and scheduled visits, and preserves completed work and agreement history.
- Kept the current plan search, filter, sort, and page selection authoritative when a rollover or cancellation finishes, so an older in-flight refresh cannot restore a view the user already changed.
- Formatted plan acceptance and rollover dates in the service office timezone and kept an invalid legacy timezone value from crashing the Plans workspace.
- Replaced stale plan-status lifecycle codes with clear refresh guidance when another office user changes or ends the plan first.
- Preserved cents in signed contract, annualized, and visit values so discounted maintenance allocations do not appear rounded or out of balance.
- Showed each unscheduled maintenance visit's real target month on desktop and mobile instead of leaving the scheduling column with only a generic unscheduled label.
- Kept standalone manual authoring and draft activation, reusable templates, post-acceptance plan changes through a new customer-approved estimate revision, open-ended and exact-date/RRULE schedules, pause/resume, occurrence editing, duplicate/renew/extend, and plan-specific proposal/PDF actions explicitly on the required V1 parity backlog. Integrated commercial multi-property, per-service/per-year cadence, savings, escalation, term, billing, and live-total controls remain available in the estimate builder.
Secure inbound calls and voicemail foundation for V2 web
- Clarified the release boundary across the Twilio, mobile workspace, and
mobile QA guides: V1 workspace-managed SMS credentials, Android in-app audio/FCM ringing, Answer / Decline, and company-line callback tests remain preserved parity documentation, not shipped V2 controls.
- Added real, organization- and location-scoped Twilio inbound connected-call,
missed-call, and voicemail records to customer conversations. Signed inbound/status/recording/transcription callbacks are idempotent and share a transaction with their call/message updates. Connected and missed calls are logged as activity without connected-call audio; only voicemail creates the private recording in this foundation.
- Added Settings > Inbound calls and voicemail for teammates with
manage_phones, including forwarding, voicemail greeting, 1–365 day retention, the organization-bound webhook URL, and explicit notice/consent/ recording-law confirmation.
- Bound callback verification and recording operations to the call's saved
Twilio account, including retained historical credentials for calls that began before a subaccount rotation. Every active voice-capable company or brand number must use the signed URL shown in V2 settings.
- Added bounded retention cleanup that redacts expired local transcript and
message content, reconciles all provider recordings for the call, treats an already-deleted Twilio recording as successful, and retries provider cleanup without blocking later tenants. A durable delayed continuation wakes at the earliest retry boundary even after a partial batch.
- Limited accepted voicemail recordings to two minutes, matching Twilio's
built-in transcription ceiling so every accepted recording remains eligible for the configured transcript callback.
- Kept organization Settings usable when the signed comms webhook secret is
not configured, with an explicit unavailable state instead of blanking unrelated team and location data. Digit-bearing Twilio Client and SIP identities are also kept out of real customer phone threads. Voice settings now accept the already-saved SMS sender number as the company-number fallback without requiring the same number to be entered twice.
- A later connected inbound call now resolves an older missed-call reply state.
Missed-call/voicemail alerts remain idempotent, late transcript enrichment advances activity once, and matching an unmatched call thread now backfills its durable call ownership together with message attribution. The web voice form also enforces the server's 10-character voicemail-greeting minimum and 1–365 whole-day recording-retention range before Save is available. If voicemail is turned off, its disabled greeting and retention fields are omitted so stale invalid drafts cannot reject otherwise valid forwarding changes. The same form now validates both optional company and forwarding numbers as blank or full E.164 even while voice is off, enforces the full 10–500-character greeting range, and shows each field error before Save. A dirty, unsaved SMS-number draft is no longer mistaken for the saved company number when checking whether inbound voice is ready.
- Split Twilio voice troubleshooting into current V2 forwarding/voicemail and
explicit legacy V1 Android softphone procedures. FCM, Expo/EAS, in-app ringing, live answer, and the Messages call button are no longer presented as current V2 controls.
- Saved only the first signed CallSid's minimum inbound-routing result with its
durable provider claim: an unavailable reason, forwarding destination and caller ID, or voicemail greeting. A duplicate delivery replays that result instead of consulting changed settings, while unrelated settings and a rejected caller's number are not retained in the long-lived result. Legacy completed claims without a saved result fail closed rather than rerouting a live call.
- The mobile Calls/Missed/voicemail presentation, playback, callback, and Text
back proposal is preserved separately but is not shipped by this change. Mobile remains frozen pending explicit user approval. V1 Phone, outbound/ live-answer flows, connected-call recording, scores, transcripts, governed AI summaries/coaching, and editable human-reviewed suggestions remain required parity; AI must never auto-send.
Jobs and Schedule web workspace
- Corrected Quick Start and its linked estimate, pricing, team, integration, Stripe, and Operations guides to use the current V2 sidebar and controls, while clearly versioning the broader V1 wizard, connector, invoice, manual-collection, route, and operations workflows as required parity.
- Corrected the remaining Team, permissions, onboarding, account, SMS, troubleshooting, inventory, locations, quality-control, and maintenance-plan entry points: current V2 now points to the Settings, Inbox, Field Ops, and Field Plans controls that exist, while the richer V1 administration, warehouse, purchase-order, QC, and plan-builder workflows remain clearly documented as required parity.
- Made those V1 boundaries visible in the documentation navigation, clarified that accepted-plan rollover is distinct from the unshipped recurring-template builder, and restored the current per-member equipment permission and Inbox entry paths.
- Made each Month overflow count interactive so dispatchers can reveal every job, open it, and use its status-aware action without changing calendar views.
- Labeled every remaining whole-guide V1 entry directly in the documentation navigation and removed the last V2 invoice sentence that pointed readers toward unshipped billing controls.
- Completed the visible V1 labels for residential estimating, route planning, and lead-ad connector guides, and clarified that V2 already supports server-enforced per-member permission overrides while V1 role-template defaults remain parity work.
- Added separate Schedule and All jobs views. Schedule now offers Day, Week, and Month calendars, local-date navigation, crew lanes, a To schedule queue, crew-time conflict warnings, and scheduled-value totals.
- Added search by customer, job, address, or crew; status, location, crew, and scheduled-date filters; four sort choices; and 25-job pages for active, completed, and cancelled work.
- Kept accepted estimates as the single source for new jobs and kept payment checks, required reschedule and cancellation reasons, status actions, and crew management in the refreshed workspace.
- Restricted location and crew controls to the service locations each teammate is allowed to use, and kept conflict warnings visible even when search hides the other overlapping job.
- Added phone-friendly dispatch cards, a scrollable Month calendar, keyboard-contained dialogs, and clear loading, error, no-work, and no-match messages.
- Kept Month actions aligned with each job's status: scheduled work can be moved, in-progress work can be completed, and every saved job name opens its detail record.
- Kept All jobs current when a scheduling or status action finishes after the user switches views, and made Manage crews select the first available service location even when location data finishes loading after the dialog opens.
- Preserved a scheduled job's current crew when Move opens before the crew list finishes loading, so saving the appointment cannot silently change it to unassigned.
- Made Retry recover the calendar together with crew and location choices after a temporary loading failure, and added the same status-aware Schedule and Move actions to phone-sized All jobs cards.
- Added complete keyboard tab behavior to Schedule and All jobs, including arrow, Home, and End navigation with correctly linked view panels for assistive technology.
- Clarified that legacy V1 truck and route guides link to the matching truck-capable instructions, while the current V2 crew calendar continues to label truck routing as required parity work.
- Labeled recurring templates, bulk-scheduled invoices, truck dispatch settings, and the Operations overview wherever they describe V1-only workflows, so current V2 readers are not sent to controls that have not shipped yet.
- Corrected the remaining getting-started, estimating, client, account, subcontractor, crew-pay, Stripe, invoice, mobile, truck, and route entry points so V1 manual-job, bulk-scheduling, recurring-template, subcontractor-dispatch, truck-day pay, invoice, and collection instructions stay available as clearly labeled history without being presented as current V2 controls.
- Clarified that current V2 payment is the verified required deposit on the immutable signed estimate portal. Full web and mobile invoices, office collection, tips, refunds, and final-balance workflows remain required V1 parity rather than shipped V2 routes.
- Kept Schedule and Move unavailable until permitted crew choices finish loading, preventing a slow reference request from silently saving a job as unassigned.
- Clarified the distinct current V2 Road and Crew job-detail capabilities and stopped directing mobile users to manual-job, truck, multi-truck, route-planning, or full-dispatch controls that remain V1 parity work.
- Added a field-preserving Retry crew choices action inside the scheduling dialog when permitted crew data fails to load.
- Split the mobile QA guide into truthful current V2 Road/Crew job-detail checks and clearly versioned V1 route, confirmation, reschedule, multi-truck, invoice, and collection suites.
- Current V2 limits are listed in the Jobs guide: manual and bulk creation, CSV export, tags, truck routes, multi-day work, extra statuses, appointment confirmation, customer schedule requests, recurring templates, and richer job details are still planned parity work.
V1-parity Clients workspace in the Clean Estimate V2 visual system
- Rebuilt the web Clients directory with responsive desktop rows and mobile
cards, relationship pills, customer/contact/property search, active and customer-type filters, office filtering, stable 25-record pagination, and recent, name, open-pipeline, and customer-payment sorting.
- Added permission-scoped summary cards for total and active clients, company
portfolios and service sites, open lead value, and succeeded customer payments less refunds. Company results retain structural portfolio identity while rolling up only child sites visible to the signed-in member; retained direct company work stays out of service-site totals.
- Expanded New client into one structured save for customer identity,
office ownership, optional primary contact, and primary service property, including address autocomplete/manual entry and possible-duplicate links.
- Rebuilt the client profile as a real customer 360 with relationship-aware
quick actions and metrics, contact roles and consent, structured property editing, visible child service sites, linked leads/estimates/jobs, customer payments with stable 25-row load-more history, internal notes, deactivation, and reactivation.
- Kept company portfolios out of lead, estimate, property, call, and message
targeting. Valid normalized phones and emails receive fixed-scheme Call and Email actions; Open inbox does not invent a conversation or recipient.
- Added database and web regressions for contact/property search, tenant and
office isolation, hidden-child portfolio classification, ownership-guarded edits, phone/email link safety, responsive controls, and company work-target guards.
- Formatted phone searches now match normalized phone records, and monetary
rollups stay wide enough for organizations or client histories above the 32-bit cent limit instead of failing the Clients workspace.
- Customer payment history now excludes pending and failed attempts from its
paid/net rows, and the directory returns to the last valid page when a teammate or filter change shrinks the matching result set.
- Lead and estimate customer autocomplete now uses one bounded work-target
query instead of recalculating Clients workspace metrics and payment rollups on each search keystroke.
- Kept V1 tags, health, segments, custom fields, bulk actions, account
grouping controls, invoice/activity/AI views, direct-message compose, manual job scheduling, and property CSV import on the required parity backlog. The new workspace does not replace them with placeholder data or dead controls.
Secure signed-in customer account foundation for Clean Estimate V2
- Added a separate customer email-code sign-in at
/customer/loginwithout
reusing staff Clerk organization memberships. One-time challenges expire in 10 minutes, allow five guesses, are consumed once, and store only hashes for browser handles, codes, and 30-day revocable sessions.
- Matched active customer emails exactly after canonical lowercase/trim
normalization. Several customer/property records inside one provider can share an account; the same address across multiple providers fails closed. Unknown and ambiguous addresses follow the same saved-challenge and generic response path to reduce account enumeration.
- Bound every session to the exact customer set present when its email code is
verified. Adding or removing a same-provider customer association revokes the older session instead of silently widening or changing its data access.
- Enforced one active session per provider and normalized email at the database
boundary, so simultaneous valid code submissions cannot leave two bearer sessions active. Added a global exact-email lookup index so public sign-in remains bounded as customer contact data grows.
- Added a responsive, provider-named Clean Estimate account shell with focused
Home, Estimates, Work, Documents, Payments, and Messages destinations across phone and desktop.
- Kept the Home Next visit card focused on the earliest scheduled visit
today or later in each job's service-location timezone, so completed calendar days cannot resurface and same-day work stays visible when one account spans locations on opposite sides of midnight.
- Queried that nearest visit independently from the newest Work page, so
high-volume recurring schedules cannot push the true next visit out of the Home card.
- Displayed estimate, acceptance, completion, payment, document, and message
event dates in each record's service-location timezone while preserving scheduled job dates as saved calendar dates.
- Kept the first account slice read-only and backed only by real V2 records:
sent estimates and saved options, proposal revision history, jobs, maintenance plans, payment/refund history, properties, and external email or SMS messages. Drafts, internal notes, system messages, and other tenants are excluded at the query boundary.
- Limited customer message history to received inbound messages and outbound
messages the provider accepted. Consent refusals, missing-recipient or sender failures, provider failures, and unconfirmed queue attempts no longer appear as customer conversations.
- Scoped signed-in Messages to contacts carrying the exact authenticated
email, preventing one contact from reading another contact's message bodies on a shared client record. Messages without reliable contact attribution stay out of the customer account.
- Kept withdrawn/reopened proposal revisions in document history without
labeling them current or linking them to an unavailable detail page. The choose/sign/pay reminder now appears only for sent, unaccepted, unexpired proposals that the secure flow can still accept.
- Made signed-in estimate and current-document status derive elapsed proposal
expiration immediately. An unpersisted expired proposal no longer inflates the Home open-estimate count or appears open elsewhere in the account.
- Made accepted documents display the customer's selected option total instead
of falling back to the first proposal option when those choices differ.
- Added authenticated cursor pagination to Documents. Customers can load
every older proposal revision beyond the first 25 without an unbounded response, including accounts with more than 100 revisions.
- Added the same stable, authenticated 25-record cursor pagination to
Estimates, Work, Payments, and Messages, so every signed-in account history remains reachable beyond 100 records without unbounded responses. The Home open-estimate number now uses a separate complete count of current, sent, unexpired revisions instead of the first estimate page.
- Preserved the existing secure
/qproposal boundary for choosing an option,
signing, and paying a deposit. The account never guesses or reconstructs a public token; it directs an unaccepted customer to the original email/text link.
- Left invoice/receipt records, PDF and attachment downloads, appointment
changes, replies, notification preferences, property switching, reviews, referrals, and configurable portal branding explicitly on the parity backlog instead of presenting placeholder data or dead actions.
- Clarified the V1/V2 boundary in both the residential quote and Jobs guides:
invoice/service-request history and customer appointment-change requests are established V1 behavior, not capabilities of the first read-only V2 account.
- Added hermetic coverage for wildcard-email regression, same-provider
multi-property identity, cross-provider ambiguity and identity drift, same-provider customer-set drift, unknown-address decoys, guess decrementing, expiry, replay, concurrent session replacement, unissued session denial, delivery-state filtering, withdrawn revisions, acceptance eligibility, tenant-scoped reads, more-than-100-record pagination across every account history, exact open-estimate totals, and internal-note exclusion.
V1 message workflow filters and sorting for V2 mobile Inbox
- Restored the message-backed portion of the actual V1 mobile Inbox workflow
on the hardened V2 conversation service. Road users can combine All / Needs reply / Replied, unread-only, multi-select Text / Email, Human reply / Automation, and Normal / High filters. Categories use AND, channel choices use OR, and the filter button shows the number of active constraints.
- Kept channel filtering anchored to the newest external SMS or email. A newer
team-only internal note can remain the card preview without hiding the conversation from its real Text or Email workflow.
- Set V2's focused default to Latest inbound while preserving the current
V1 Latest activity default as an available sort, alongside Our last message, Oldest unanswered, Latest human reply, Oldest human reply, and Longest waiting for reply. Sorting and filtering remain server-side, organization/location scoped, and stable across newest-first and oldest-first 50-card cursor pages.
- Kept human-reply meaning explicit: customer inbound messages and actual
staff replies participate, while automation can appear under Our last message or Latest activity without masquerading as a human reply. Delivered staff replies count as replies as well as sent staff replies.
- Preserved V1's screen-local filter/sort behavior without claiming its visible
but non-persisted Save as a view field is a synced saved view. The V2 quick rail retains whole-scope All, Needs reply, and Unread counts, and active-filter empty results remain distinct from a truly empty Inbox.
- Versioned the expanded native list contract while retaining and translating
the preceding installed build's All / Needs reply / Unread filters, Latest activity ordering, and last-message cursor in both directions. The backend can deploy before devices upgrade without silently removing a filter or breaking older-page requests; mixed and partial cursor contracts fail closed.
- Made each Load older conversations action fetch one authoritative server
window containing the visible rows plus the next 50 before replacing local cards. A thread removed by Needs reply, Unread, or another active filter no longer survives merely because its stale copy was outside both concurrently fetched pages. Request count stays constant as loaded history grows, with a 2,000-row native safety ceiling and search/filter guidance for larger scoped Inboxes. Search, filter, and sort changes now invalidate the preceding cursor at the interaction boundary and block load-more until the new first page arrives, preventing cross-order skips and duplicates.
- Kept the complete remaining V1 catalog as required parity: assignment;
Calls and Missed calls; voicemail, scores, transcripts, AI summaries and coaching; estimate/invoice engagement filters and counts; the three document-view sorts; and human-reviewed suggested replies. These depend on scoped ownership, call, invoice/document-activity, and governed AI contracts. Suggested replies remain editable drafts and can never auto-send.
Recoverable lead-map loading
- Added Retry map to the Pipeline map fallback so a temporary Google Maps library or connection failure does not strand the current session. Saved lead-location links remain usable while the map is unavailable, and a retry recreates the real map and pins without inventing location data.
- Normalized database date values at the permission-gated Pipeline export boundary so authorized CSV downloads sort and serialize created, appointment, and activity dates reliably.
- Kept the default unbounded lead view from constructing unused date formatters and reused one formatter per service-location timezone when created-date bounds are active, reducing repeated work on large pipelines and exports.
- Preserved the selected Sort order in both the visible Follow-up queue and its CSV export instead of silently replacing it with an oldest-activity order.
- Discarded delayed stage-change reason prompts and errors after a user switches pipelines or locations, preventing an old board's hidden lead from being changed through a stale dialog.
Focused customer Inbox for Clean Estimate V2 mobile
- Added More > Inbox to the V2 mobile Road workspace for owners, admins,
office teammates, and sales reps while keeping customer messaging hidden from the crew-only workspace.
- Added location-scoped customer conversation cards with search, **Needs
reply, Unread**, latest message, channel, time, and location context. Unmatched inbound threads use the sender's phone or email as their visible identity until the conversation is linked to a customer.
- Added a customer-first thread with distinct inbound, outbound, and team-only
note presentation plus navigation to an existing linked mobile client record.
- Added online text and email replies through the shared V2 contact, consent,
sender, provider, and idempotency gates. Stale threads refresh safely, and failed, queued, or lost-response retries retain one logical send identity. Text, email, and internal-note retry identities remain separate when the composer changes channels, so one ambiguous attempt cannot replace another. A later inbound or team message also cannot discard an earlier uncertain identity and turn an unchanged retry into a second provider send. Fresh attempts and definite provider failures no longer stay pinned to an obsolete thread snapshot after a conflict, so the refreshed draft can retry without entering a stale-warning loop. Only genuinely uncertain queued or lost-response attempts retain their original boundary. Inbound webhooks and staff replies serialize on the same thread boundary, so an in-flight customer reply cannot be missed by the stale-thread check. Inbound rows are timestamped from the database clock after acquiring that boundary, so a webhook that waited behind a staff reply cannot sort before it or leave unread/needs-reply state falsely cleared. Non-reply automations now take that same conversation boundary and receive a database wall-clock time after acquiring it, so a reminder or notification that waited behind staff work cannot sort before the message it followed. Team-only internal notes now use the same post-lock database clock, so device clock skew cannot place a later note before the customer or staff message it followed. Unread cards, filters, and whole-Inbox counts now compare the full message time and ID boundary, so a later inbound message sharing the read message's timestamp cannot be silently treated as already read. Same-time read updates also remain monotonic when requests finish out of order.
- Persisted each send identity, destination boundary, and retained draft before
its outbound request starts, then kept genuinely uncertain attempts in account- and conversation-scoped encrypted device storage. Returning with Back, reopening the screen, or restarting the app therefore retries the same logical provider attempt instead of minting a duplicate. Mobile blocks a send and offers Retry if that safety state cannot be restored or saved.
- Prevented ambiguous text outcomes from offering an unsafe unchanged retry.
The SMS provider has no provider-side idempotency key, so the composer keeps the original identity for reconciliation and keeps the unchanged send blocked. Waiting does not settle that uncertain attempt; intentionally editing the draft starts a different logical message. Generation-checked encrypted storage also prevents a late result from an older screen from deleting a newer screen's saved send state.
- Kept delivery truth explicit for offline, no-contact, no-consent, no-sender,
recorded-only, queued, and provider-failure states. V2 does not queue mobile outbound messages for later delivery.
- Added provider-free internal notes for matched and unmatched conversations.
Unmatched sender-identity threads remain note-only until they are linked to a customer; they never expose customer-facing Text or Email controls.
- Clarified the current V2 mobile boundaries in the matching guides: Inbox
search covers SMS/email content but not internal-note text yet, Phone remains a V1 parity workflow rather than a current V2 destination, and the current manager Schedule is the focused one-day browser rather than the full V1 organization calendar.
- Moved V2 mobile external message-history search onto an indexed partial-word
path, preserving incremental searches while preventing repeated debounced queries from scanning every stored message body. Ordinary short replies such as no and not yet remain searchable, while private internal-note text stays outside customer search.
- Bounded long conversation payloads to 50-message pages with an explicit
Load older messages action, bounded stalled read-receipt waits so the list still refreshes, and serialized idempotency claims so concurrent requests cannot reuse one key for different message content. Focus refreshes reveal new inbound tails before marking them read, while web and mobile retries retain the original visible message boundary.
- Bounded the conversation list to 50-card pages with **Load older
conversations. Search and All / Needs reply / Unread filtering now run over the complete server-scoped Inbox before paging, and waiting/unread counts no longer stop at the first 200 conversations. A refresh invalidates an in-flight older-list or older-message request so a moving cursor cannot leave a conversation or timeline message missing. A fresh thread refresh also resets an invalidated older-message loading state, keeping Load older messages available after the race settles. Loading older conversations now re-reads and merges the newest page too, so a previously unloaded thread that receives activity and crosses the page cursor appears immediately instead of being absent while its unread or waiting count increases. Reaching the final older page now clears the pagination boundary, so Load older conversations** disappears instead of requesting duplicate final rows.
- Distinguished a zero-result search or active filter from a genuinely empty
Inbox, so existing conversations are never hidden behind Inbox clear.
- Expanded server-side conversation search beyond the latest card preview so
older message content can find its thread before any local history page loads. Search entry and the request helper both enforce the API's 100-character limit, so pasted text cannot turn an otherwise valid Inbox search into a validation error.
- Classified this release as the hardened core-messaging slice, not completed
V1 Inbox parity. Required follow-up includes V1's full composable filter and ten-sort catalog, count badges and filtered empty state, call/voicemail activity and scores, AI call summaries/coaching, and human-reviewed AI suggested replies. Suggested text must remain an editable draft and can never auto-send; a teammate must press Send through the normal V2 safety gates. Attachments, approval/engagement cards, campaigns, and offline outbound delivery also remain outside this release pending safe contracts.
- Clarified related sales, technician, and troubleshooting guidance so current
V2 users follow the focused five-slot Road or four-destination Crew shell, including the one-day More > Schedule browser, while the V1 six-tab calendar, route, and feature/workflow baseline remains explicitly documented for required parity work.
Estimate workbench and detail workspace
- Rebuilt the office Estimates list in the distinct Clean Estimate service-operations design with real queue cards for drafts, customer-viewed proposals, unassigned work, and accepted estimates.
- Added server-backed customer, email, phone, address, and
EST-reference search plus status, estimate type, sales representative, lead source, preset date, and custom date-range filters. Sent and Viewed stay separate, and date filters follow each service location's local calendar. - Added information-equivalent mobile estimate cards, stable
EST-references, customer and property context, payment state, linked-job actions, explicit loading and filtered-empty states, and retryable refresh errors that do not erase already loaded rows. - Reworked estimate detail around the customer, property, revision, source, owner, approval, and lifecycle context while keeping residential, Generic Quote, commercial, and Holiday Lights editing on the existing single-screen builders.
- Added access-controlled Preview and Download PDF actions for office users. The document matches the current customer proposal and its deposit terms without changing the customer link.
- Added accepted-option, signer, payment, refund-aware balance, and job-handoff context to estimate detail. Sent estimates show their saved sent date, expiration, and deposit terms.
- Kept engagement tied to the current sent revision, so a resent estimate stays in Sent until the customer opens the new link. Moving a proposal back to Draft now shows its withdrawal immediately.
- Updated Send to wait for the latest scope and measurement changes to save, preventing an earlier version from being sent after a quick edit.
- Tags filtering is not yet available in V2. The control stays hidden until it can return accurate, access-controlled customer results; V1 Tags filtering remains available.
2026-08-20
Clear proposal option comparisons in Clean Estimate V2
- Replaced the stacked option rows on the secure V2 proposal with responsive
comparison cards that stay legible with one, two, or three real offers.
- Kept each proposal's saved option order consistent on desktop and mobile.
A shared quote-core residential comparison saves As requested first, a maintenance recommendation second, and a later expanded offer third, while office-authored Option A/B/C positions remain authoritative. The separate residential choice page keeps One-Time Clean, Complete Property Care, then Suds Club.
- Used a neutral card label for custom A/B/C choices without a saved semantic
identity instead of guessing which renamed package is requested or enhanced.
- Stopped selecting the first offer automatically. Customers now make an
affirmative choice before they can review and sign, while an already accepted proposal continues to show its stored accepted option.
- Kept every card bound to its existing option ID, total, and exact deposit or
full-payment amount. The comparison is presentation-only and does not change acceptance, payment, or job-creation behavior.
- Showed only saved choices. A proposal with fewer than three options no longer
implies that another package exists.
- Clarified that the original one-time scope is reference-only when a customer
selects a maintenance plan, so it cannot be mistaken for additional included work.
- Used the saved option type, not a customer-entered option name, to identify a
maintenance recommendation and its plan-creation behavior.
- Displayed each selected enhanced one-time option's own customer-safe saved
scope and totals, so the acceptance terms do not point to missing details.
Leads and pipeline return to a full Clean Estimate workspace
- Rebuilt the V2 Pipeline page in the approved Clean Estimate warm-canvas, dark-teal, and orange visual system while preserving its real pipeline, location scope, lead creation, lead-detail navigation, and guarded stage changes.
- Added All leads (renamed from the early prototype's List), Map, Follow-up, and Sources alongside the existing Board. Map pins require persisted valid property coordinates; source cards use actual filtered lead counts and stored values, with unattributed work labeled explicitly.
- Restored real-time search, stage, rep, source, segment, activity, created-date, and location filters; useful sorting; responsive desktop tables/mobile cards; 25-row pagination; exact filtered counts; clear loading/error/empty states; and CSV export of the current view.
- Kept structured appointment dates and start/end times visible and retained chronological same-day appointment sorting and lost-reason enforcement through V2's stage-change service.
- Prevented a completed stage move from replacing a newly selected pipeline/location with the old board, and kept inactive historical reps plus role-changed crew assignees available as labeled filter choices for the leads they still own.
- Kept the real saved-location lead list and lead-detail links visible when the browser map key or Google Maps import is unavailable.
- Made Created from/to filters honor each lead's saved service-location timezone instead of comparing UTC date prefixes.
- Moved lead CSV generation behind the server-enforced Export data permission and hid the export action for members without that grant.
- Published an explicit V1 parity follow-up ledger for client tags, AI lead score/tier, automation enrollment, transactional bulk stage/rep changes, estimate-count filtering, normalized multi-service job types, saved views, and unified activity. These capabilities remain required; no placeholder or fabricated control claims they have shipped.
Service-operations dashboard for Clean Estimate V2
- Converted Home into a responsive service-operations dashboard using Clean
Estimate's warm workspace, dark teal, and orange visual system.
- Preserved the existing live V2 job board, unscheduled accepted work, sent
proposals, pricing approvals, stale-lead follow-ups, and month-to-date financial totals. Counts and queue previews link back to the underlying work.
- Made the next-action queue, today's dispatch schedule, and weekly workload
easier to scan on desktop and mobile while keeping permission-gated data hidden from teammates who cannot access it.
- Added explicit loading, empty, partial-error, keyboard-focus, and responsive
layout coverage. The dashboard does not invent leaderboards, task lists, lead-source charts, ROI scores, or other demo-only data.
- Kept the desktop navy navigation rail visually continuous beside long
dashboard pages instead of exposing the workspace canvas below the rail.
Generic Quote returns to Clean Estimate V2 web
- Restored Generic Quote as a first-class V2 estimate type without restoring
the old estimate wizard navigation. Office users build the quote on one screen with quote details, estimated hours, manual line items, customer descriptions, internal crew alerts, discounts, and sales tax.
- Added Flat, Each, Sq Ft, Linear Ft, Hour, Per Visit, and Per Vehicle units.
Line totals, discount, tax, and the final total are calculated by the shared server pricing engine rather than trusted from the browser. Flat rows use one fixed price and lock quantity to one so the displayed arithmetic always agrees with the signed line total.
- Kept saved drafts editable in the same Generic Quote screen and exposed the
estimate type from the Estimates, client, and lead workflows.
- Added customer-safe Generic Quote scope to the secure proposal and PDF. The
customer sees the quote label, project overview, each line's quantity, unit, unit price, description, and reconciled total; crew alerts, internal notes, estimated hours, pricing controls, and price-book identifiers remain private.
- Added a direct View itemized summary action to synced Generic Quotes in
the focused mobile app. The field view remains read-only and uses the stored server-priced record rather than a different estimator engine.
- Classified accepted manual-quote value as Generic Quote in Top Services
reporting instead of combining it with unknown legacy estimate data.
- Delivered each accepted line's crew alert into the new job as an explicit
crew checklist instruction while keeping it out of the proposal and PDF.
- Carried the accepted Generic Quote's estimated hours into the resulting
job's dispatch duration instead of substituting the generic two-hour fallback.
- Limited positive durations to the scheduler's supported 15-minute minimum
through 24-hour single-day maximum, and validated every computed line, subtotal, tax, and final total against the persisted integer-cents range before any quote price or line item is saved.
- Preserved older residential custom-item draft shapes when they are reopened,
so restoring Generic Quote cannot make an established estimate fail its next autosave.
- Reused the V2 immutable send revision, approval, email/SMS, customer
acceptance, deposit, status, and resend paths instead of creating a separate quote lifecycle.
- Labeled synced Generic Quotes explicitly on mobile and made their saved total
available as read-only field context. Editing, sending, and resending stay on web until mobile has the matching manual-line pricing contract, preventing a generic quote from being mispriced or rendered as another estimate type.
- Kept central price-book selection, optional-item packages, and native mobile
Generic Quote creation visible as remaining V1 parity work rather than claiming those established capabilities have already shipped in V2.
Structured lead scheduling in Clean Estimate V2
- Replaced typed appointment windows in V2 web lead creation with date,
start-time, and end-time controls, and added the same editable schedule to lead details.
- Added V1-inspired native calendar and clock pickers to V2 mobile lead
creation and detail. A new lead can include its appointment while offline and let the normal sync queue upload both together. Existing lead appointment changes require connectivity so a delayed device edit cannot overwrite a newer office reschedule. Android keeps a visible Clear time action beneath each selected time, so users can return to a date-only appointment or remove only the end time.
- Prevented end times from saving without a start or before the start time.
Marking a lead Unscheduled now clears all schedule fields together, and offline uploads reject appointment types outside the same supported list as the web form.
- Kept an existing appointment date populated when the web lead editor receives
the database date as a full timestamp, so changing a time no longer starts from a blank date field or fails until the date is reselected.
- Fixed the mobile sync mapper to forward every appointment field on existing
leads, preventing an apparently saved schedule edit from reverting when the older server row syncs back down.
- Isolated invalid lead-create schedule data to its own offline operation,
preventing one stale or corrupted time combination from blocking later queued mobile writes.
- Kept older typed arrival windows visible as a fallback through date-only
edits. Choosing a structured start or end time replaces the old window, so the rollout does not hide or silently erase existing bookings.
Structured V2 client creation
- Rebuilt the V2 New client dialog to capture the customer name and type,
owning office location, optional primary contact, and a structured primary service address in one workflow.
- Added Google Places suggestions when configured while keeping address line,
city, state or region, postal code, and country available for manual entry.
- Made client, contact, location assignment, and primary-property creation one
atomic save, so a failure cannot leave a partially created customer.
- Preserved the complete display address used by existing web and mobile
workflows while retaining structured address components and optional place and coordinate metadata for future address-aware features.
Accepted-estimate job scheduling for Clean Estimate V2 mobile
- Replaced the Road workspace's New job placeholder with an online workflow
for scheduling the exact job already created from a customer-accepted estimate. The same action is now available from Home and Create.
- Ported the V1 mobile scheduler's compact context-first layout, paired time
controls, and system-safe pinned action. The V2 action says Schedule accepted job because acceptance already created the canonical job; V1's multi-day end date and freeform job notes remain out until the shared V2 job contract can represent them without duplicating or overwriting job data.
- Added V1-inspired native date, start-time, and end-time pickers. Start and end
remain optional as a pair, and the server converts the chosen window to the existing job start-and-duration model instead of adding a second schedule model. Android users can clear either optional picker back to an untimed job.
- Showed the accepted customer, service address, proposal option, scope, value,
location, and acceptance date before scheduling. Frozen proposal identity is preferred so a later CRM rename does not rewrite what the customer accepted. Maintenance-plan cards use each job's season and visit-specific scope rather than repeating an indistinguishable estimate-wide label.
- Kept every production gate intact. Only accepted, deposit-cleared,
unscheduled work is offered; reps remain limited to their own estimates; crew-only users are blocked; and active crew choices remain limited to the accepted job's fixed location.
- Made the final action online-only and opened the exact confirmed job after
save and its first local sync. The confirmation screen prevents a successful schedule from briefly appearing missing while PowerSync delivers the row. Synced schedule revision and row update time reject an older cached schedule while allowing a newer dispatch assignment or reschedule to open, and the exact job still opens when the crew has already advanced it beyond scheduled. Replays of the same schedule do not emit a duplicate scheduling event, while revision checks prevent a stale same-slot phone request from clearing a crew or duration another teammate just assigned.
- Kept lost-response retries idempotent even when a fast crew has already
started or completed the work: later statuses are accepted only when the locked revision and every original scheduling field prove the exact replay.
- Added an authorized server-status watchdog while the first local job row is
syncing. If dispatch unschedules or cancels the job, mobile returns to the current accepted-work picker with clear copy instead of waiting indefinitely for a row intentionally excluded from the schedule stream. The same wait now exits if member or location access is revoked, because that protected row can no longer arrive on the device.
- Kept long-open scheduling screens safe across midnight: the native minimum
date follows the job location's calendar day—even when the phone is on a different date—and refreshes while open or on resume. The server applies the same location-timezone rule.
- Showed the existing target month on holiday takedowns and maintenance visits
so the picker preserves seasonal scheduling guidance.
- Left truck assignment, arbitrary manual jobs, rescheduling, routes, and full
dispatch in the web workspace because the focused V2 mobile job contract does not safely support those inputs yet.
Team-only notes in the Clean Estimate V2 inbox
- Added a Note composer beside SMS and Email in the V2 office inbox, with
an explicit Team only reminder and distinct internal-note cards in the conversation timeline.
- Kept internal notes completely outside customer delivery. Adding one does
not resolve a recipient, call an email or SMS provider, create a delivery attempt, or expose the note on the customer proposal page.
- Stored the note author and time with the location-scoped conversation, and
made retries idempotent so a lost response cannot create a duplicate note. Notes can also be added safely to unmatched inbound threads.
Day-by-day job schedule for Clean Estimate V2 mobile
- Added More > Schedule to both V2 mobile workspaces so road teammates
and crews can browse the jobs already synced to their device for dates other than today.
- Added previous-day, next-day, seven-day, and return-to-today navigation with
a time-ordered agenda plus explicit loading, empty, and retry states.
- Kept access aligned with the active workspace. Road users open the existing
read-only job summary, while crew users see only jobs assigned to their crew and open the existing field-execution detail. This focused browser remains separate from the accepted-work scheduling action and does not add full dispatch or office calendar controls.
- Kept the schedule useful offline through the existing PowerSync job cache.
Directions remain out of this slice because synced V2 jobs do not yet carry a trustworthy job-to-service-address relationship.
- Versioned the mobile release checklist so the current V2 route is tested as
a one-day More > Schedule browser, while V1 week, revenue, filter, directions, and route-planning expectations remain explicit parity work instead of false failures against the focused V2 destination.
Company portfolios and service-site safeguards
- Added the V2 parent-child client foundation so related service sites can sit
under one company portfolio without merging records or moving their leads, estimates, jobs, properties, contacts, plans, or history.
- This release establishes the portfolio data and service foundation. The
current web and mobile Clients screens do not yet include self-service Group sites or Ungroup controls.
- Saved properties remain the right model when only the service address differs.
Child service-site clients are for locations that need their own contacts, work history, visibility, and metrics before rolling up to a company.
- The v1 Bulk Schedule picker remains saved-property only. Independently
managed child sites must be scheduled separately until V2 adds child-site selection to that workflow. In a v1 account batch, each saved property and created job remains owned by its nested contact/customer record; in the V2 portfolio model, the standalone client or child service site owns work and contacts remain communication roles.
- V1 Add Account metrics and profile tabs, including Estimates, Jobs,
Invoices, and Activity, continue to roll up work owned by nested contact/customer records. V2 company portfolio metrics and tabs roll up work owned by permitted child service sites, where contacts are recipients rather than separate work targets. The Accounts, Clients, Leads, and Pipeline guides now consistently define Open Pipeline as the stored values of non-terminal leads, whether entered manually or derived from primary-estimate pricing, without adding estimate options together.
- Restricted the internal child-site classifier to CE Pro's application
database role, preventing unrelated database roles from using it to probe portfolio structure.
- Location-pinned ungrouping now refuses atomically when cleanup would need to
release a grouping-created company link outside the member's scope. No links or hierarchy changes are left half-applied; an all-location owner or manager can complete the cleanup.
- Grouping-created company location links now commit atomically with the
participating site's hierarchy move. Later link repairs must name an active site already grouped under that company, so callers cannot manufacture a trusted cleanup grant for an unrelated standalone client.
- Existing portfolio parents must be visible to the signed-in member before
grouping can adopt them. A hidden older client with inactive location links can no longer be made visible by submitting its ID directly; only a company created inside the current grouping transaction can receive its first link.
- Application sessions can no longer directly insert, update, or delete
client-location links. Client creation and later activation or repair now use one protected boundary that requires prior client visibility or same-transaction creation proof, then restores the child-site link, required parent-union visibility, and matching audit grant atomically. A submitted ID cannot make an older hidden client visible. Removal or deactivation requires an explicit reconciled workflow; protected detach releases inherited visibility only after the final child leaves that location.
- Clarified that V2 company-portfolio Lifetime Value is successful payments
minus refunds attributed through visible service-site work. The legacy V1 client-table metric remains accepted-estimate revenue, so the two version-specific totals share a label but not a calculation.
- Company portfolio visibility now follows the active location union of its
sites. Location-pinned users see only their permitted sites and cannot create an incomplete portfolio by grouping a client that also has hidden location links; an all-location manager must handle that case.
- New web and mobile leads, estimates, and service properties now reject the
company parent and ask the user to choose a service site. Existing standalone commercial clients continue to accept work until they actually have grouped sites. Estimate, invoice, and message recipients follow the service site that owns the V2 work; the company parent is not a communication target. Primary contacts therefore belong on each work-owning child site in V2, while legacy V1 accounts keep their account-level primary contact. Web and mobile lead and estimate customer pickers now remove portfolio parents before a save can be queued. Mobile uses the complete server-side hierarchy for that filter, so a location-pinned device still hides a parent when its service sites are outside the device's synced location. Portfolio profiles now list and link to child service sites in the rep's permitted sync scope, so the rep can open the work-owning site before starting a prefilled estimate, call, or message instead of treating the parent rollup as a customer or communication target. The mobile sales and getting-started guides now scope every client quick action to standalone clients, legacy V1 accounts, and child sites. A V1 account-level work shortcut still requires selecting the nested customer/contact that owns the service property; the account remains the grouping record rather than becoming the work owner. Portfolio names still participate in both mobile lead and standalone-estimate exact-name duplicate warnings without becoming selectable work targets. Web lead quick-create now removes portfolio parents from its clickable duplicate suggestions while the general Clients duplicate warning continues to recognize existing accounts. When a portfolio parent and valid work target share an exact name, the mobile forms prefer the standalone client or child site for Use existing instead of letting the parent suppress that action.
- Qualified the remaining mobile company-name-only guidance: normal direct
call, edit, message, and work actions apply to standalone commercial customers, legacy V1 accounts, and V2 child service sites. A legacy V1 account keeps its account-level shortcut, but the nested customer selected for an estimate or job shortcut owns the resulting work; the web Client Profile and Fusion mobile guides now make that required selection explicit. Fusion directs V2 portfolio users through the permitted child-site list before call, email, message, directions, or create actions. During an extended sales-history outage, the documented fallback preserves those actions for standalone clients and V2 child sites. V2 portfolio parents retain their permitted rollup and child-site navigation without becoming communication or work targets.
- Removed direct Create lead, Create estimate, and Add property
actions from V2 portfolio-parent detail. The parent now presents a compact white navigation card with the child service sites available in the signed-in member's location scope, so the office can open the correct work owner first. Retention Generate Quote follows the same boundary: standalone clients and child sites can open prefilled work, legacy V1 accounts require nested-customer selection, and a V2 portfolio row directs the user to a child site instead of creating a parent-owned quote. An inactive portfolio's recovery notice likewise makes clear that reactivation restores the rollup only and never enables parent-owned work actions.
- Aligned the canonical Client Profile guide with V2 portfolio ownership.
New estimate, New job, Message, and property-launched work begin from the child service site, while the company parent provides permitted service-site rollups. Its Leads view does not imply that hidden sites or direct history retained from before promotion are included. The same scope now applies consistently to Estimates, Jobs, Invoices, billing, and communication history on the portfolio profile.
- Promoting an existing standalone client preserves its earlier direct leads,
estimates, and jobs. Those records remain available in the main workspaces and are intentionally separate from the new portfolio's service-site rollups and site-based account tabs. Promotion continues to support existing top-level records regardless of their editable client type label; hierarchy, not that label, defines the portfolio parent. A retained lead must be reassigned to the intended child site before New estimate, call, or message can preserve that lead's context and use a valid work owner and communication recipient; the canonical Leads procedure now includes that reassignment step. If a sent estimate blocks reassignment, the guide now explains both supported paths: reopen it to draft before reassigning and resending, or preserve the sent proposal as history and start the new action directly from the intended child site without retained-lead context.
- Hardened portfolio isolation and concurrent location repair so hidden client
structure is not exposed through account classification, UUID lock order is consistent, and opposite-order offline uploads cannot deadlock by upgrading a company row later in the batch. Work creation now classifies the client while holding a compatible hierarchy lock, preventing concurrent grouping from promoting the client between validation and save. Queued mobile work now requires the earlier client upload to make its customer visible; hidden and missing IDs receive the same unavailable response instead of crossing a privileged recovery probe. Locally created mobile clients are immediately marked as standalone work targets, so another offline lead or estimate can reuse them and exact-name duplicate actions stay accurate before sync. Retried portfolio-creation requests now reuse the original company safely, while unrelated or hidden UUID collisions are refused. Retry matching normalizes UUID casing, and direct union checks refuse sibling locations outside the signed-in member's scope. Repairing an older customerless estimate now applies the same serialized company guard as new work, and direct parent updates require the parent to cover the site's complete active location union. Parent-pointer writes now cross the audit-aware grouping boundary, so a raw detach cannot leave a location link inherited by the former company after the last site moves away. Company links inherited when an already-grouped site later begins serving a new location are audited through the same boundary and released on the final detach when no remaining work needs them. Application sessions can no longer insert grouping-history rows directly; protected hierarchy and location-link transitions now create their own audit records, preventing a forged history row from removing a pre-existing company location link during a later ungroup. Inherited link release, every audit balance required after repeated deactivate-and-repair cycles, and the final site detach now commit atomically, while sibling-site and company-owned work keep the link active. Hidden, missing, and foreign prospective parent IDs now share one unavailable response so the guarded boundary does not reveal whether an out-of-scope client exists.
Synced customer book for Clean Estimate V2 mobile
- Added More > Clients to the V2 Road workspace so field sales teammates
can browse the customer records already synced to their device without exposing the organization-wide customer book to crew-only users.
- Added customer-name and every-active-service-address search plus a compact
client detail view for active properties and linked leads, estimates, and jobs. Portfolio parents link to child service sites permitted in the signed-in rep's synced location scope. Linked records keep their existing mobile detail routes; clients outside that scope are not cached as an organization-wide list.
- Added New estimate from active standalone-client and child-site detail
with the customer preselected. The legacy V1 account-level shortcut remains available and opens customer selection so the nested customer/contact—not the account grouping record—owns the estimate. Portfolio parents remain browseable as rollups, but their detail page directs the rep to the work-owning child site instead of starting a parent-owned draft. Inactive clients retain visible history but must be reactivated in the office before new work can begin. Client and contact editing remain outside this read-only slice, avoiding conflicting edits while the structured customer-address workflow is being completed.
Redesigned Clean Estimate V2 customer proposals
- Rebuilt the secure V2 proposal page around the established Clean Estimate
customer experience, with a branded summary, customer and service-location context, itemized scope, totals, option comparison, maintenance visits, and clear proposal availability.
- Added mouse, touch, stylus, and keyboard-accessible signature capture while
preserving typed-name acceptance for older links and clients. Correcting a name now clears its generated signature so the visible signature and signed name cannot disagree.
- Cleared a captured signature when the customer leaves the signing step so a
hidden image cannot be submitted after they return to option comparison.
- Kept signature strokes aligned with the customer's finger on narrow screens,
and kept an existing drawn signature valid after an accidental stationary tap.
- Made discounted maintenance-year subtotals reconcile to the exact visit
allocations and signed option total shown to the customer.
- Included minimum service charges in the customer pricing summary so subtotal,
savings, tax, and total reconcile on screen.
- Bound the prominent pricing summary to the active option. Selecting a
maintenance plan now shows its plan total without leaving the one-time total or line amounts presented as the active financial terms.
- Showed the exact deposit or full payment due for each option before the
customer accepts, then kept successful payments and refunds visible beside secure Stripe checkout. Waived upfront payments now show that nothing is due instead of displaying a false remaining balance.
- Made recorded charges override a waiver when an already-open checkout settles
later, while office-resolved payments without provider records now show as handled instead of falsely outstanding or falsely received online.
- Kept fully refunded provider payments distinct from no-record office
resolution, showing gross received, refunded, and restored remaining amounts.
- Kept proposal PDFs, decline reasons, expiration, withdrawal, and accepted
next steps together in a responsive layout. Accepted PDFs now retain the exact captured signature plus the allow-listed customer name and service address shown online.
- Expanded the public data boundary only with an allow-listed customer name,
service address, and office location name. Private contact methods, office notes, access instructions, pricing floors, and commissions remain hidden.
- Reloaded the authoritative accepted proposal when a decline in another tab
loses the race, removing stale decline actions and incorrect error copy.
- Frozen newly sent proposal identity fields with the immutable revision so
later organization, customer, service-address, location, or timezone edits do not rewrite the retained portal or signed PDF. Legacy links keep their prior fallback behavior.
Redesigned Clean Estimate V2 office workspace
- Added a responsive navy sidebar and compact top bar across signed-in V2 web
pages, with saved desktop collapse state and an accessible mobile drawer.
- Grouped Home, Clients, Pipeline, Estimates, Approvals, Inbox, Jobs, Plans,
Ops, Reports, and Settings into a consistent permission-aware workspace.
- Added global search for customers, contacts, service addresses, and leads.
Lead results now open the matching lead detail directly.
- Kept secure customer proposal links outside the private office shell so a
recipient never sees office navigation, organization controls, or search.
- Converted the Clients list to the new cards, table, buttons, badges, empty
states, typography, and spacing while keeping the remaining workflows fully usable inside the redesigned frame during their page-by-page conversion.
Safer equipment, maintenance, inventory, and truck checks
- Added the V2 Ops workspace for location-scoped assets, engine-hour entry and correction, maintenance rules and due alerts, inventory movement history, and Holiday Lights install allocation and takedown returns.
- Maintenance-rule rollout cleanup now preserves the tightest same-basis cadence on the surviving category rule and keeps both schedules when an hours rule overlaps a days rule. Operators can adjust each independent clock while duplicate rules on the same basis remain blocked. If promoting a tighter hourly category cadence makes a sibling asset immediately due, the rollout sends that maintenance alert once while avoiding another alert for assets that were already due.
- Holiday Lights allocation now hides jobs that already have allocated inventory, limits choices to active stock at the install job's location, rejects cross-location or inactive items at the server, requires every displayed material line to be complete before the one-time allocation can run, and reports stale retry attempts without claiming stock moved a second time.
- Added a distinct Equipment permission for operational changes. Existing owners and manager-level V2 admins retain access, while teammates without the permission can review Ops without changing assets, stock, or maintenance rules.
- Meter corrections now keep an append-only audit showing the old and corrected hours, teammate, reason, and whether an offline correction was superseded by a newer reading. Concurrent office corrections compare the exact meter snapshot under a row lock, so an older form cannot finish last and overwrite a newer manager's correction. The latest applied downward correction also rebases a later service reading that would otherwise suppress the next maintenance interval, while preserving the original service log for audit; restoring the original meter through an ordinary upward entry releases a mistaken rebase.
- Mobile truck checks clamp future device timestamps, keep lower readings from advancing the server observation clock, ignore refused readings when calculating the local meter floor, resume using later server advances after a correction syncs, discard device history superseded by an authoritative office correction, and show the operator when the latest queued reading or correction was not applied without pinning an older refusal after a newer attempt. If an offline correction is rejected because equipment permission, crew access, or vehicle status changed before upload, the phone now preserves the rejected vehicle and hours in a local-only warning before acknowledging sync; the meter stays unchanged, the rejected local check is excluded from the next-reading floor immediately instead of waiting for sync-down, and the warning remains visible until dismissed. The same immediate exclusion now covers a stale correction whose audit row was accepted but meter change was refused, closing the gap before its server refusal note syncs down. Dismissing either warning records banner visibility separately and keeps the rejection tombstone, so an offline dismissal cannot make the refused correction influence later readings.
- Maintenance rules now respect location scope, reject blank or overlapping task names across asset and category rules, and protect logged maintenance history when a rule is renamed. Rule renames and Mark done now serialize across asset and category rules: completed service locks the old task name, while a rename that wins first causes a stale mark-done click to ask for an Ops refresh instead of writing invisible history under the old name. Lowering a cadence across the current elapsed value or reactivating an already-due rule now sends its maintenance alert immediately instead of waiting for a future meter entry that could never recreate the crossing.
- Team settings can reactivate an inactive teammate while preserving explicit permission choices, and operational error codes are translated into actionable messages. Reactivation now fails closed until the company sign-in membership is confirmed, so a teammate removed from the provider—or one whose membership cannot currently be checked—stays locally inactive instead of regaining access through an older valid session token. The final activation is conditional on the teammate row remaining unchanged during that provider check, which prevents a concurrent deletion webhook from being overwritten after it has already removed access.
- Pricing configuration now identifies sales tax as the fallback for states without a maintained rate, labels it in percentage points, and validates the supported range, so
6consistently means 6%. Red Line floor percentages are capped at 100%, concurrent saves use an atomic version check so one manager cannot silently overwrite another, and the page explains that existing unsent drafts reprice on their next edit while sent quotes keep their locked-in price. A 1-to-365-day quote-expiry default now pre-fills fresh estimate sends, while resends continue preserving the prior quote's terms.
Role-aware Clean Estimate V2 mobile navigation
- Added a real Fusion bottom navigation shell to Clean Estimate V2. Road users
now have Home, Leads, a raised center Create action, Estimates, and More connected to the existing V2 screens.
- Gave crew-only users a focused Today, Truck, Earnings, and
More shell. Today remains the current assigned-job schedule instead of adding an unfinished schedule or jobs placeholder.
- Made dual-role navigation follow the saved Road/Crew workspace choice
across both Home and the full tab bar. Existing list and detail URLs remain stable, and record details still open outside the tab navigator. If a role or crew assignment changes while an old workspace tab is open, the app now returns to the correct Home or Today screen automatically.
Safer estimate approvals and proposal resends
- Added a dedicated below-floor approval queue with the customer, requester,
one-time pricing, and maintenance-plan details needed to make the decision.
- Bound every approval to the exact priced content that was reviewed. Saving a
changed price, measurement, or maintenance plan on web or mobile now retires the old pending request immediately and requires a fresh approval. The web builder now also labels an approval as granted only while it matches the current pricing; historical and legacy approvals remain audit history.
- Closed matching pending requests when an authorized teammate sends the
below-floor proposal directly, preventing decisions on an already-sent gate.
- Required every new web and mobile estimate to resolve to a real customer and
added a repair path for older customerless drafts before they can be sent. A draft linked to a clientless legacy lead now requires the lead to be repaired first, keeping both records on the same customer.
- Made declined proposals from the single-screen estimate builder resendable as
a new revision and customer link. Those resends preserve the prior expiration or no-expiration setting, fixed deposit, or custom percentage deposit instead of silently weakening the payment terms; other proposal builders keep their existing documented link-reuse behavior.
- Added Resend proposal to the single-screen estimate builder for proposals
that are still sent. The office must first refresh and review the estimate under current workspace pricing, then explicitly confirm delivery. Each refresh is read-only, so abandoning it does not publish the unsent amount to office estimate lists. A confirmed resend creates a new immutable revision and customer link through the normal pricing, approval, expiration, and payment-term gates. A pricing change or same-revision status correction after the preview blocks delivery until the refreshed total and lifecycle state are confirmed again. A resend awaiting Red Line approval also keeps the last sent amount in office lists, and read-only service cards cannot stage accidental edits before returning a proposal to Draft.
- Added confirmed office status corrections to the single-screen estimate
builder for open proposals: move to Draft, mark Declined with an optional reason, or mark Expired. Returning to Draft closes the old customer link before editing without dropping saved add-ons, custom items, notes, discounts, holiday-light setup premiums, or other draft details, while accepted estimates remain locked to their signed acceptance and payment history. A resend or status change from an older screen is refused if another tab or device already resent a newer revision, and competing decline reasons no longer report a false success. Manual expiration closes the proposal without rewriting the immutable revision's original expiration terms.
- Updated dashboard date windows to use the organization's default-location
timezone and stopped expired proposals from remaining in Awaiting an answer indefinitely.
- Added direct navigation from accepted estimates and maintenance plans to
their linked jobs and plan records.
- Kept office-created estimates aligned with their saved property's location.
Changing location clears the previous customer and property for reselection, the property picker shows only choices in the customer-validated location, late results from a previous customer are ignored, and the server rejects properties owned by another customer or location.
- Added the customer's optional decline reason to the office estimate, with a
clear note when the customer did not provide one.
- Made the Leads gone quiet dashboard badge show the complete stale-lead
count instead of stopping at the 20-row preview limit.
Safer lead reassignment and source ordering
- Reassigning a lead to a different client now carries the selected replacement service address into linked draft estimates instead of removing their property.
- Moving lead sources now swaps both positions in one save, preventing partial updates from leaving an ambiguous source order.
- Mobile-created leads now receive automatic lead numbers without consuming a second number when an offline upload is replayed.
- Older lead cards use their saved title when no customer, property, or lead number exists, keeping each record identifiable during cleanup.
- First-time workspace setup now tolerates simultaneous bootstrap requests without failing while default lead sources are created.
- Global lead search results now show the lead number, customer, and service address that matched instead of repeating the customer name alone.
- Retired source-based routing rules no longer block an unused pipeline from being archived.
- New lead appointment details now require a date when a time window or type is entered, preventing saved details from disappearing behind Not scheduled.
- Simultaneous lead-source creation now assigns distinct positions, keeping the source list deterministic and reorderable.
- Lead assignment now excludes crew-only teammates in both the picker and the server validation, preventing leads from being routed to an inaccessible workspace.
- Archived service properties are now rejected during lead creation and client reassignment, including stale or direct requests that bypass the picker.
Reliable proposal PDFs
- Proposal PDFs now measure every supported Latin character with the font's real width, keeping accented customer names and long company names inside their document columns.
- Proposal PDFs now retain the saved revision's Prepared date and keep the full paper-approval block above the page footer.
- Proposal PDFs now wrap every maintenance visit service, keep a complete multi-option maintenance choice together when it fits on a fresh page, and keep each scope price on the page where its item begins.
- Accepted proposal PDFs now include only terms that belonged to the signed revision, preventing later default clauses from being attached to an existing signature.
- Proposal payment summaries now keep Paid to date and Remaining balance together instead of leaving the balance by itself after a page break.
- Proposal pages and PDFs now format prepared, signed, expiration, and payment dates in the service location's timezone, keeping evening activity on the correct local calendar day.
- Customer proposal pages now show the complete scope retained in the signed PDF, including residential add-ons, additional items, and holiday-light installation zones.
- Proposal scope and option headings now stay with the first item below them. Very long scope items begin above the footer and keep their price beside the first line, while multi-page maintenance schedules keep the section heading with the first year and each visit price with its visit.
- Customer proposal pages now include a visible Download PDF action for the secured revision, available before or after acceptance.
2026-08-16
Faster estimator, proposals, and office lists
- The public residential estimator now loads pricing, service catalog, cost,
and lead-source inputs together. Later wizard steps and internal measurement tools are warmed after the first screen instead of enlarging the initial customer download.
- Publishing Pricing Manager or Suds Club changes now clears the matching
customer-facing cache immediately. Failed price-book synchronization also clears any rejected version that may have been read while rollback was in progress.
- Temporary organization, pricing, or cost-setting query failures are no
longer cached as though the estimator were unconfigured or missing.
- Proposal record lookup now checks supported proposal types concurrently and
reuses the organization settings already loaded for residential offers.
- Estimates and Pipeline now receive their default first page in the initial
secured response. Section-specific loading screens provide immediate feedback for Pipeline, Jobs, Leads, Invoices, Team, and Settings navigation.
Accurate residential proposal choices and pricing
- Residential customer proposals now open with no package or Suds Club choice
selected. The customer makes the first choice before loading final review.
- Draft customer views are labeled Draft preview. Reps can select and
compare One-time work, Complete Property Care, and Suds Club, adjust Club upgrades, and check or uncheck one-time services without recording a customer decision. Final review, decline, and signature remain closed until the estimate is sent. Opening a draft preview no longer records a customer view or triggers a customer-view alert.
- One-time and Complete Property Care prices retain office price overrides and
current service quantities. Requested-service rows use the saved one-time price instead of a stale fallback from another package.
- Manually entered service measurements now survive builder reloads and remain
protected from property autofill, preventing saved roof and gutter scopes from reverting during autosave.
- Preview Quote and Send Now now wait for the current residential builder
save before opening the customer proposal or send modal. Newly enabled services such as Patio and Fence, current measurements, and price overrides therefore reach the one-time selector instead of showing the previous save.
- Fixed the residential service-selection staging function that could fail
while copying canonical line items, preventing Final review from loading after the customer adjusted one-time services.
2026-08-14
Faster estimate workspaces and revisions
- Estimate workspaces now render the saved estimate in the initial secured
page response instead of showing a loading screen while the browser repeats the same authentication and estimate request after startup.
- Opening Revision now loads independent pricing, catalog, team, cost,
workspace, and lead-source data together. Duplicate estimates also transfer only the editable fields the residential builder needs, reducing the number and size of sequential reads before the editor appears.
- Customers now arrive with account rows, contacts, account choices, and
health context in the initial secured page response. The page no longer renders an empty customer shell and repeats authentication through two startup API calls, and health aggregation begins alongside the account reads.
- Job workspaces and the central Price Book now arrive with their primary data
in the initial secured page response instead of rendering a loading shell and repeating authentication through a browser startup request.
- Lead workspaces now load the selected lead and assignable team members
together in the initial secured response instead of displaying a spinner while two separate browser requests repeat workspace resolution.
- The Settings overview now waits for navigation intent before warming its
large destination grid. Opening Settings no longer speculatively starts every visible settings page and competes with the page the user actually selected.
- Schedule now receives crews, team members, trucks, vehicles, services, and
crew-pay settings in one tenant-scoped response. A cold Schedule visit no longer waits on a chain of separate setup requests before the calendar can render.
Accepted-estimate workflow recovery
- Accepted-estimate follow-up workflows now receive a client created or linked
during acceptance before email, SMS, or task steps begin. This prevents a valid accepted quote from entering recovery with a missing recipient merely because the client link was finalized moments after the acceptance write.
- Fixed Round Robin task assignment in workflow task steps. The executor
now resolves the configured strategy to an active workspace member before validating and creating the task instead of treating round_robin as a user ID and moving the run into recovery.
2026-08-13
Faster startup and durable automation recovery
- Fixed accepted quotes repeatedly checking the same deposit status until the
customer-facing rate limit was reached. The approval page now keeps one payment preflight mounted, so card, bank, and offline payment options become available without a false Too many requests error. Accepted-estimate recovery also accepts every valid database UUID, including older IDs without RFC version bits, so those follow-up effects are not rejected as invalid.
- Fixed queued commercial proposal delivery rejecting older organization IDs
that are valid PostgreSQL UUIDs but do not contain RFC version bits. These jobs now reach the normal tenant and recipient checks instead of being moved to dead-letter before delivery starts.
- Stopped the Sales lead list and authenticated navigation from eagerly loading
every visible destination and lead detail page. Sales now fetches a lead only after pointer, keyboard, or touch intent, avoiding the large request burst that made the tab appear to load for many seconds. Existing Sales rows remain visible with Refreshing leads... while updated results load.
- Replaced repeated scheduled-trigger history scans with a durable queue and a
separate persistent catch-up cursor for every workflow. Appointment, estimate, invoice, and contract timing events are recorded once, claimed in small batches, retried with backoff, and moved to a visible dead-letter state after their retry limit.
- Fixed scheduled catch-up failing when a historical signed estimate still had
a mutable-looking Draft, Sent, or Viewed status. Acceptance, signature, and accepted-maintenance evidence now prevents both the expiry status update and expiring/expired automation, while genuinely mutable overdue estimates continue normally.
- Made record-driven workflow triggers durable in the same transaction as the
business change. Estimate, job, invoice, payment, client, inbound-message, phone, review, and external-webhook events now enter an independent queue that continues to drain when only the time-based scheduler is paused. Stable occurrence keys let the immediate path and recovery worker converge on one run instead of losing or duplicating the event after a timeout.
- Kept circuit-breaker pauses recoverable. A scheduled-trigger pause does not
turn off workflow definitions or discard due work; processing resumes from each workflow's own saved cursor in bounded batches, with current record state checked before an old reminder is allowed to send. Pausing one workflow preserves its cursor and queued events for reactivation.
- Added lease recovery for delayed workflow runs. Recent runs stranded after a
worker stops can resume, while a run with an executing provider step or one stranded in Running beyond the safe replay window is paused with an audit record for manual review instead of being replayed into a possible duplicate or unexpectedly old send.
- Added a transactional safety net for eligible connector, web-form, and
server-API lead automations. The lead and its recovery job are committed together, and the exact workflow or legacy sequence chosen at admission is saved with the job so a retry cannot switch outreach modes after settings change. Stable dedupe keys prevent the immediate speed-to-lead attempt and a recovery worker from starting the same workflow or follow-up enrollment twice. A lead is still captured when no deliverable route is configured, but automatic outreach is left off for that lead. Manual mobile lead creation continues to suppress automatic outreach on both its normal and outage-fallback paths.
- Added a durable reservation before every workflow provider step and made run,
step, cursor, enrollment, and audit persistence fail closed. Recent work can recover before a provider call, while an executing or otherwise ambiguous email, text, or call is quarantined for review instead of being blindly sent again. Stale generic background contact jobs receive the same quarantine treatment; only explicitly replay-safe jobs are reclaimed automatically.
- Fixed legacy follow-up steps advancing after a required email or text failed.
A two-channel step now retains a successful channel, retries only the failed channel with bounded backoff, and pauses with a recorded reason after a permanent failure or exhausted retry limit. Follow-up email retries also use a stable provider idempotency key to avoid duplicate email after partial delivery.
- Removed the unfinished generic Recurring Schedule trigger from the
builders and reject it at workflow creation and activation. Existing drafts stay inactive instead of appearing enabled without a durable scheduler.
- Reduced authenticated web load by removing eager prefetching from dense
record lists and aggregating Home dashboard metrics inside one database summary call instead of downloading a month of estimate rows to the app server.
- Consolidated mobile workspace, access, feature, and Home startup data behind
one shared bootstrap request. The app can show its verified cached workspace shell and last-known same-workspace Home data while refreshing, and concurrent expired-token responses share one refresh before each safe read retries once.
- Removed the remote authentication round trip that was repeated before each
mobile API read. Current signed sessions are verified locally against the project's cached signing keys, while expired or invalid sessions still fail closed as authentication rejections so the app can refresh once through the existing sign-in recovery flow instead of treating expiry as a server outage.
- Increased the bounded mobile Leads and Pipeline page sizes to fit the current
workspace in one data request while preserving pagination for larger workspaces. Clients also stopped requesting an unused exact total on every list load.
- Changed the mobile Inbox default to Latest activity. New customer and
team conversation events, document views, missed calls, approvals, and other recorded system activity now share one newest-first opening view; the directional Inbox sorts remain available when needed.
- Moved mobile Home metrics and role-filtered collections into one compact,
timezone-aware database summary. Home no longer transfers thousands of raw records during long-period views, and an optional Home timeout or dependency error no longer blocks the workspace: startup returns current access and features while retaining an in-memory same-period Home snapshot when safe.
- Added immediate, idempotent alert emails when a scheduled or business event,
background automation job, or legacy follow-up delivery reaches its dead-letter state, plus daily maintenance email coverage for dead letters, overdue work, stale leases, and quarantined runs. Request timing and correlation logs omit customer and token data, and database-outage notifications are deduplicated by incident so a cold-start monitor cannot create another hourly alert storm.
- Prevented old catch-up work from contacting customers unexpectedly. A due
scheduled event, record-driven business event, or lead-recovery job outside the 24-hour automatic replay window becomes a visible dead letter instead of sending days or months late.
Simpler, reliable Suds Club approval
- Removed the separate Confirm Signature button from the residential final
review. The drawn signature is ready when the customer lifts their finger or mouse, and the existing scope, price, and terms checkbox remains the explicit approval step.
- Fixed Suds Club-only proposals incorrectly reporting that the estimate had
changed after it opened. The final security check now compares the correct stored estimate revision without changing the membership amount shown to or approved by the customer.
Instant customer quote selections
- Preloaded package, service, and Suds Club pricing now updates immediately when
a customer changes a package, checks a one-time service, or adds a window or screen upgrade.
- Moved persistence into a serialized background save that keeps only the
newest queued choice, preventing rapid clicks from restoring an older selection.
- Removed a redundant acceptance-review request from package and one-time item
changes while keeping the final signature review server-verified.
Suds Club page included in customer estimate PDFs
- Customer PDF downloads now follow the current package choice: Suds Club-only
downloads contain only the membership document, combined choices contain both documents, and one-time-only choices exclude Suds Club.
- Added the full membership document to eligible residential quotes even when
the offer is visible but the membership has not been saved yet.
- The added page uses the same monthly price and seasonal service schedule as
the customer quote, and reflects selected exterior-window and screen upgrades once those choices are saved.
- Kept long provider names clear of the membership badge and fitted full
four-season schedules onto one print-friendly page.
- Added the normal estimate customer and service-location header to Suds
Club-only PDFs, plus an itemized signup charge, sales-tax rate and amount, total due at signup, and ongoing monthly membership price.
Window and screen upgrades on customer Suds Club quotes
- Replaced the customer quote's per-window quantity field with separate
Exterior window cleaning and Screen removal, reinstall & cleaning buttons.
- Matched each button's monthly price to the public Suds Club website's six
home-size tiers; customers can select either upgrade or both.
- Included selected upgrades in the live monthly membership, three-month signup
payment, approval scope, and saved estimate pricing.
2026-08-12
Automatic protection against database slowdowns
- Added a minute-by-minute project health and database-capacity monitor with
structured logs, 30-day probe history, and transition email alerts.
- Added an automatic circuit breaker that pauses high-fanout scheduled work
after repeated database failures or pressure and resumes after two healthy checks.
- Consolidated protected-route organization and permission resolution into one
database call, reducing sequential middleware reads when opening estimates and admin pages.
- Grouped appointment-reminder workflows so shared candidate windows are read
once, capped dispatch concurrency, and replaced historical JSON dedupe scans with an atomic indexed workflow-run key.
- Expanded the incident runbook with exact routing-timeout, scheduler-pause,
recovery, and post-recovery verification steps.
Faster recovery from data-service slowdowns
- Added a single-worker safety guard to scheduled appointment reminder and
lifecycle automation processing so a slow invocation cannot overlap with the next one and multiply database traffic.
- Added an eight-second limit to Supabase calls made by protected-page routing,
preventing estimate, lead, and admin navigation from hanging in middleware for several minutes during a provider outage.
Residential quote page now matches the approved design
- Replaced the tabbed screen for eligible residential estimates with the
approved three-choice proposal: One-Time Clean, Complete Property Care, and Suds Club.
- Moved the decision to the top of the page and added the saved package scope,
seasonal Suds Club schedule, comparison, project photos, final review, signature capture, and a phone-friendly Review and sign bar.
- Kept customer-account links out of the proposal decision itself. Customers
still reach earlier work, invoices, documents, service requests, and messages after signing in to their account.
- Matched checkout and the accepted PDF to the signed residential choice. The
Club signup and applicable tax are due for Club-only approvals; combined approvals add the one-time deposit. Future monthly dues stay separate.
One customer viewer from quote through payment
- Rebuilt sent residential, commercial building, fleet, generic, and Holiday Lights quotes in one branded, mobile-friendly customer viewer while preserving the scope and pricing each kind of work needs.
- Connected quote links to the signed-in customer account for earlier quotes, service history, downloadable documents, invoices, service requests, and direct messages to the office. A public quote link still unlocks only that quote; account history requires passwordless sign-in.
- Kept portal messages, service requests, documents, and work orders scoped to the signed-in provider and property, and kept internal office notes out of customer views and PDFs.
- Kept signature and approval separate from payment. On standard estimate approval flows, customers can approve first, then pay a signed deposit by card or bank account, choose check/cash/Venmo or another offline method, or leave the deposit pending for the office to confirm.
- Made the accepted quote's saved payment schedule authoritative. Holiday Lights and option-based deposits use their signed percentage or amount, while quote types without structured deposit terms no longer receive a guessed 25% charge.
- Kept legacy fleet and commercial proposal approval sign-only; the office handles their deposits from the linked estimate or invoice.
- Kept accepted quotes available for payment after their original validity deadline; only unanswered quotes expire.
- Added secure customer invoice PDFs and work-order PDFs, plus refreshed estimate PDFs. The standard layout now uses a compact unboxed header, prepared-for and service-location columns, itemized scope, readable terms, page numbers, and paper signature or acknowledgment lines where appropriate.
- Updated the invoice viewer with payment history, balance, hosted checkout, and view/download/print actions. Pending offline deposits stay out of paid totals and receipts until the office records the money received.
2026-08-11
Clearer customer proposals and one-click estimate delivery
- Estimate detail pages now include a prominent Send estimate or Resend estimate action with recipient-channel confirmation and delivery feedback.
- Customer proposals now lead with a short review guide and clearer navigation: Review Estimate, Adjust Services, Compare Packages, and Project Photos.
- Residential customers can choose the one-time work, Suds Club membership, or both before signing. The selected combination is included in the exact approval scope and total.
- Approval links now explain how to get a fresh secure link instead of showing a temporary-unavailability dead end, and eligible unsigned legacy estimates are repaired for canonical online approval.
2026-08-10
Connect Stripe directly from Brand Settings
- Replaced manual Stripe account-id entry with a visible Connect Stripe
action for every saved brand.
- Added Stripe-hosted authorization for existing accounts, signed and expiring
anti-forgery state, tenant-scoped callback handling, and automatic brand account assignment after authorization.
- Brand forms no longer ask owners to paste Stripe account ids or API keys.
Safer multi-brand Stripe payment routing
- Customer-facing Stripe payment intents now use payment methods enabled and
eligible in the connected account instead of a hard-coded method list.
- Brand Stripe account ids are validated before saving, and refunds and voids
now stay on the connected account that processed the original brand payment.
- Checkout sessions now include per-flow integration identifiers for clearer
Stripe Dashboard tracking.
Brand-aware inbound calling
- Added inbound voice routing for active brand Twilio numbers while preserving
the workspace's existing primary number, mobile ringing, forwarding, and recording behavior.
- Calls and voicemail received on a brand line now retain the brand in Phone
and Inbox activity. Brand voicemail names the called brand instead of replaying a custom greeting saved for another brand.
Visible, reliable automation progress on every contact
- Added an Automations card to lead details and an Automations tab to
client details showing active and historical workflows, the last completed step, the next action and scheduled time, completed-step history, and who manually enrolled the contact.
- Fixed manual enrollment so an engine startup failure is returned to the user
and recorded as a failed run instead of closing the dialog with a false success message or leaving an unrepeatable active enrollment.
- Corrected the scheduled automation endpoints to accept Vercel cron requests,
allowing delay and business-hours holds to resume when their wake time is due.
- Added the same automation visibility to native mobile lead and client
details, including contact enrollment for teammates with Automations manage.
Manual enrollment for existing clients and leads
- Added Enroll Clients to active Manual Trigger workflow detail pages, with
search and filters for name, source, status, and tag plus multi-select enrollment.
- Added bulk Enroll in Automation actions to the Clients and Leads queues.
- Added server-side checks for required email or phone information, SMS
opt-outs, duplicate active enrollments, and enrollment history.
One price book for every estimating module
- Added a central Price Book for services, products, materials, equipment,
discounts, saved items, customer prices, unit costs, descriptions, tax status, and thumbnail photos.
- Separated category from module availability so one item can be shared by
Residential, Commercial, Fleet, Holiday Lights, Asphalt Maintenance, and Generic Quote.
- Added scalable inventory packages and connected accepted, completed,
invoiced, and cancelled jobs to inventory reservation and consumption.
- Kept saved estimate line items stable when a Price Book item is edited or
archived.
Live Holiday Lights quote editing and reliable winter previews
- Added direct line-item editing with live totals to Holiday Lights estimates.
- Kept design editing and remeasurement as separate, explicit actions so
ordinary price or description changes preserve property imagery and drawings.
- Made the line-item total authoritative and added a live quote panel below the
design canvas.
- Corrected private Winter Wonderland image references on preview domains.
End-to-end multi-brand leads, estimates, messaging, and payments
- Added a durable brand context to leads and carried it through Holiday Lights
designs, proposals, shared estimates, contracts, accepted jobs, invoices, communications, and payment records.
- Added Service Line and Brand choices to web and mobile lead creation.
Christmas Lights defaults to the brand assigned to the Holiday Lights module, and estimates launched from a lead retain that brand on desktop and mobile.
- Customer estimate pages, Holiday Lights portals and previews, PDFs, email
sender/reply identities, and SMS sending numbers now resolve from the saved record brand. Branded SMS fails closed when its sending number is missing.
- Added immutable send-time brand snapshots so later logo, color, name, or
contact changes do not rewrite previously sent estimates, contracts, or invoices.
- Routed branded Stripe checkout, keyed, embedded, and terminal payments through
the brand's connected account and retained both brand and connected-account attribution on the payment ledger.
Commercial mobile regression safeguards
- Made recurring-program discount calculations default safely to zero while an
older or preview draft is missing the newer discount field, preventing a valid commercial scope from displaying a NaN total.
2026-08-07
Mobile pipeline service filters and compact opportunity details
- Added a permanently visible mobile pipeline filter for Residential,
Commercial, and Holiday Lights. Fleet estimates are included in Commercial instead of appearing as a separate business line.
- Generic Quote now asks which of those three pipelines the quote belongs to
and saves that classification with the estimate. Existing generic estimates use their saved customer type or recognizable scope before falling back to Residential.
- Rebuilt the mobile opportunity detail sheet with a shorter value-and-stage
header, full Proposal/Call/Text/Email buttons, a compact record summary, and a horizontal stage selector. Removed the large empty activity filler and the tall one-stage-per-row menu.
Lead pipeline, scheduling, assignment, and estimate launch cleanup
- Added a compact pipeline progress strip to the top of every mobile lead. It
reads the active subscriber workspace's configured pipeline, stage order, names, and colors instead of imposing a fixed Clean Estimate stage list.
- Made the lead appointment card actionable. Authorized users can tap it to
schedule, change the date or time, or return the lead to Unscheduled.
- Replaced truck-and-crew lead assignment with salesperson assignment. Dispatch
resources remain part of the job workflow after the estimate is won.
- Rebuilt the empty Estimate tab as one unambiguous state with a single action.
- Lead Create and New Estimate actions now open the estimate-type chooser and
retain the customer, property, lead, and contact prefill for the selected Residential, Commercial, Fleet, Asphalt, Holiday Lights, or Generic builder.
- Fixed lead-originated mobile navigation so choosing an estimate type opens
that exact builder instead of activating the general Estimates tab. Nested Commercial, Fleet, and Holiday Lights builders retain their lead context through the first-step redirect.
- Saving any of those lead-launched estimate types now writes the originating
lead relationship into the estimate. The result appears on that lead instead of becoming an unrelated proposal in the general estimate list.
Faster Create launcher and continuous lead dictation
- The mobile Create launcher now warms the effective permission payload as
soon as the signed-in workspace loads and retains the last verified result securely on the device. Opening Create no longer normally leaves every row disabled behind a five-to-ten-second Checking state.
- Removed the second network retry from this lightweight background permission
refresh. A slow refresh keeps using the last verified access state rather than blocking all Create actions.
- Lead voice intake now accumulates finalized speech segments and merges
cumulative recognition results. Dictating the address, phone, or requested service no longer replaces the customer name captured earlier in the same conversation, and restarting the microphone continues the current dictated lead instead of clearing it.
Commercial auto-detection and full measurement workflow
- Added commercial building auto-detection on mobile. Reps draw a search
boundary, review the detected building footprints, include or exclude each outline, and apply the combined measurement and price-book quote.
- Added the same authenticated auto-detection capability to desktop
MapMeasure. Detected buildings are added as separate editable footprint measurements instead of flattening the property into one manual number.
- Expanded commercial measuring to building walls, roofs, flatwork polygons,
sidewalk paths with width, linear runs, and countable items, with manual tracing available whenever public building data is incomplete.
- Building-wall estimates now use footprint perimeter, stories, floor height,
openings deduction, and gable allowance. The satellite tool also supports a tilted 3D view and keeps the accepted geometry in the proposal snapshot.
- Added soil-condition and access-difficulty controls to mobile commercial site
conditions and connected both to production hours, job cost, margin, and the measured quote calculation.
Maintenance cadence is customizable by service
- Replaced the single global recurring frequency calculation with a cadence
matrix for every property and price-book service on mobile and desktop.
- Each service can now be included or excluded and assigned its own annual
visit count and schedule timing, such as monthly sidewalks and semiannual building washing in the same agreement.
- Year-by-year programs can override visits and timing for each service in each
contract year.
- Annual totals, customer options, canonical line items, review summaries, and
the saved maintenance-plan draft now all use the individual service schedule rather than multiplying the entire scope by one frequency.
2026-08-06
Commercial customer addresses and draft creation fixed
- Selecting an existing customer in a commercial estimate now copies the full
service address into the active property when its address is blank, allowing Street View and Satellite imagery to load without duplicate entry.
- Fixed the commercial proposal create failure caused by saved CRM customers
being written with an unsupported database source value.
- Applied the customer-address mapping and production client-field mapping to
both desktop and mobile commercial builders while preserving any property address the rep already entered manually.
- Reworked mobile Commercial Setup into a compact customer-and-property
workspace: removed the oversized selected-client card and clipped badge, removed the redundant single-property tab, shortened the add-property action, and separated primary measurements from optional surface and access details.
- Restored a visible Measurements card directly below the mobile service
address. Known building, floor, and paved-surface values can still be entered manually, and Open Measure Tool now launches the visual satellite drawing workspace from the same card.
Commercial estimator and maintenance planning are now integrated
- Removed the external commercial-estimator and maintenance-plan website links
from mobile and desktop Scope & Pricing.
- Added a first-party desktop Measure and price workspace using the active
property's MapMeasure data, tenant production rates, commercial operating costs, site modifiers, job minimum, and target margin.
- Applying an estimator result now creates or updates the commercial
price-book line item and saves estimated hours, job cost, margin, and the calculation snapshot with the active property.
- Added an integrated maintenance-program builder supporting simple recurring,
tiered, year-by-year, earned-rate, and frequency-menu structures with custom visits, one-to-five-year terms, discounts, and escalation.
- Added the corresponding inline pricing and maintenance controls to mobile so
the active commercial draft retains the program and pricing inputs without opening another app or browser.
- Replaced the old mobile maintenance selector and duplicate visit-frequency
accordion with the integrated maintenance-plan workspace. Reps can now set the plan name, start date, frequency or exact visits, term, billing cadence, savings, escalation, included price-book services, and see per-visit, annual, and contract totals in one place.
- Commercial proposal sync now preserves those choices as a tenant-scoped
maintenance-plan draft snapshot instead of reducing them to display-only frequency controls.
- Mobile now loads the signed-in tenant's saved commercial surface production
rates and operating costs, calculates production hours and job cost from the measured property, and can apply the target selling price directly to the editable price-book scope.
- Added the actual visual mobile Measure tool: reps can trace building area,
roof area, flatwork area, or a linear run on satellite imagery, see the live square-foot or linear-foot result, choose a tenant price-book service and production surface, and apply the automatically calculated quote. The saved commercial draft now retains the measurement geometry as well as its amount.
- Maintenance structures now persist canonical customer-selectable options for
the portal and signature workflow. Recurring program discounts are included in the server-verified per-visit and annual totals instead of being saved as display-only configuration.
- Added tenant-safe persistence fields and tests for estimator calculations,
program-year pricing, and first-party commercial-builder routing.
Cleaner mobile commercial workspace and accurate margin state
- Removed the oversized Commercial Setup hero and the redundant Scope &
Pricing introduction cards so customer, property, line items, and tools begin higher on the screen.
- Moved Save draft and phase navigation into the bottom of the scrollable page
instead of pinning them over the estimate. Removed the duplicate draft action from Review & Send.
- Added Custom frequency with an exact visits-per-year field.
- Initially linked Scope & Pricing to the standalone commercial tools. Those
temporary handoff links have now been replaced by the integrated workflow described above.
- Replaced the false 0% margin badge with Cost needed until a real estimated
cost is entered. The workspace then recalculates gross margin against the current per-visit total as scope or pricing changes.
Desktop commercial builder now matches the three-phase mobile flow
- Replaced the remaining five-stage website commercial wizard with Setup,
Scope & Pricing, and Review & Send so desktop and mobile use the same workflow.
- Combined customer and property selection in Setup; consolidated tenant
price-book scope, exact pricing, site conditions, scheduling, and terms in Scope & Pricing; and kept final totals and delivery in Review & Send.
- Renamed the desktop catalog controls to Tenant Price Book and removed the
hard-coded service fallback. If the tenant price book cannot load, the builder now reports the failure instead of presenting unrelated fallback rates.
Three-phase, price-book-first mobile commercial builder
- Replaced the broken nine-screen mobile commercial wizard with three focused
phases: Setup, Scope & Pricing, and Review & Send.
- Removed the live route's standalone hard-coded commercial service list.
Services, products, equipment, units, descriptions, tax settings, and default prices now load from each tenant's shared active price book, including its verified offline fallback.
- Added a compact per-property scope workspace with Street View/Satellite
context, editable line-item cards, exact quantity and price editing, optional items, custom items, and per-property totals.
- Moved site conditions, visit frequency, seasonal scheduling, portfolio
discount, payment terms, and scope notes into expandable advanced sections so simple one-time commercial quotes no longer have to cross irrelevant screens.
- Made One time the default commercial schedule and aligned mobile and
server totals so one-time quotes send without a fake annual frequency. For catalog-backed rows, the server now verifies the item against the effective tenant price book and preserves its price-book ID, kind, category, unit, description, taxable setting, and optional-item metadata in canonical quote line items.
- Added phase gates for missing customers, service addresses, and line items,
while preserving commercial draft auto-save, canonical proposal delivery, and existing multi-property data.
Desktop Holiday Lights draft saving and refresh recovery
- Added a visible Save draft action to the desktop Holiday Lights header and
beneath the design canvas. Existing estimates save in place, while the first save of a new estimate creates its permanent editable record.
- Added continuous browser recovery for customer information, package scope,
canvas placements, pricing, schedule preferences, and notes. A refresh now restores a newer local draft instead of silently returning to the last server version.
- Browser recovery excludes oversized inline image data to avoid exhausting
browser storage; server-backed property and Winter Wonderland image URLs are preserved normally.
- Replaced the desktop 3D property's experimental map element with the stable
Google satellite map renderer and bounded compressed snapshots. Opening 3D, capturing the positioned property, or protecting it in browser recovery no longer risks exhausting the editor page.
- Replaced legacy low/high ranges throughout desktop Holiday Lights review and
customer preview with the selected package's exact one-season price and exact discounted per-season three-year price. Removed internal materials and labor estimates from that customer-facing review.
Safer Holiday Lights canvas editing and reliable protected images
- Changed the Holiday Lights canvas trash control to remove only the selected
strand, wreath, or mini-light area. It no longer silently wipes the entire package design. Desktop Clear all is now a separate confirmed action.
- Fixed protected Winter Wonderland images failing to reload in the signed-in
web designer when the configured application host differed from the current browser origin. A real load failure now clears the stale canvas and gives a useful recovery message.
- Enabled the top Approve action for connected, unsigned remote estimates so
it opens the same approval-choice flow as Approve & Sign instead of being grayed out until a snapshot was created later in that flow.
Build Better and Best from an existing Holiday Lights package
- Added Copy to Better and Copy to Best actions to the mobile and
desktop Holiday Lights designers. Reps can finish Good once, copy its full visual and priced scope into a higher tier, and continue adding upgrades.
- Package copying preserves the destination's Good/Better/Best identity,
ordering, and recommendation badge while cloning design items, custom items, measurements, colors, placement points, and exact pricing with independent item IDs.
- Added an overwrite confirmation whenever the destination already has scope.
The existing duplicate action remains available for creating an additional custom package instead of replacing one of the standard options.
Accurate estimate-view counts in mobile Inbox
- Fixed opened Inbox conversations attaching every historical estimate-view
event in the customer thread to the current estimate. The context card and repeat-view hot signal now use only the displayed estimate's persisted view count.
- Grouped rapid refreshes, browser retries, and repeated public-page requests
within ten minutes into one engagement session. This prevents a single open from generating many view events, notifications, or artificial hot-lead signals.
- Changed Last viewed to record the latest qualified viewing session rather
than retaining the first-ever open time.
Correct Holiday Lights approval pricing and complete estimate details
- Holiday Lights approval and signature screens now identify the selected
three-year rate as a per-season agreement price and show the higher standard one-season price for comparison. A one-season selection remains labeled as a one-season estimate.
- Removed the hidden Suds Club validation blocker from Holiday Lights and other
non-residential estimate types. Suds Club remains available only on eligible residential estimates, while every supported estimate type can save its own one-time or agreement approval choice.
- Added the selected installation week, takedown preference, requested takedown
details, customer preferences, and internal crew notes to the mobile estimate record so the office and field team can review the saved plan after reopening.
- Expanded Holiday Lights attachments to show every saved property mockup with
its lighting overlay and the separate Winter Wonderland render. Protected tenant media continues to load through authenticated organization-scoped image access.
Holiday Lights mobile canvas precision
- Added bounded two-finger panning to the zoomed mobile Holiday Lights canvas.
The zoom badge now exposes the gesture, one-finger drawing and item movement remain unchanged, and placement coordinates account for the current pan so lights continue landing on the point the salesperson touches.
- Fixed Holiday Lights line items doubling after reopening. Each designed zone
now syncs as one customer-facing service line instead of separate materials and installation rows; production time remains internal metadata.
- Prevented a stale or temporarily empty reopened builder from replacing a
priced Holiday Lights estimate with an empty scope. Mobile estimate detail now prefers canonical line items, collapses older duplicated projections, and falls back to the saved editor zones if an older flat projection is empty.
- Fixed mobile 3D Satellite failing when Android location permission was not
granted. The picker now resolves the saved service address through CE Pro's Google address path and no longer invokes the device geocoder or location services.
- Locked Winter Wonderland input to the complete 1× property frame regardless
of the salesperson's current drawing zoom, then restored that editing zoom after capture. Generated images now use fit-to-frame presentation in both the builder and customer proposal so the house cannot be shifted or cropped by the preview container.
- Removed editable C9, mini-light, wreath, outlet, and cable overlays from the
completed Winter Wonderland preview so the generated customer image displays by itself instead of appearing underneath the original drawing canvas.
- Made the power layer mutually exclusive with Move items. Opening the power
layer or selecting Place outlet or Draw cable returns to the original property image, exits move mode, and gives the electrical tool the next tap.
- Corrected zoomed-canvas coordinate handling by collecting drawing taps in the
outer canvas coordinate system and mapping them back through the current zoom. Editing bulbs, wreaths, outlets, and cable paths now remain smaller and screen-consistent at higher zoom levels for more precise placement.
Adjustable property imagery and faster Holiday Lights drawing
- Upgraded Winter Wonderland from draft-quality rendering to a medium-quality
professional-installation finish using strict photographic constraints. C9 bulbs now target small teardrop points with restrained bloom and wreaths target natural irregular evergreen texture instead of oversized glowing disks or perfect artificial rings. Customer architecture and marked scope remain locked.
- Split Winter Wonderland inputs into a clean customer-property edit target, a
placement-only marked canvas on web and mobile. The photographic style target is expressed as rendering rules instead of a second property's reference photo, preventing unmarked rooflines and landscaping from leaking into the result.
- Replaced the hidden AI placement image's oversized bulb dots and wreath icon
with thin strand centerlines, hard endpoints, and a small wreath center/size marker. This produces smaller installed bulbs and natural evergreen wreaths without changing the familiar drawing overlay used by the salesperson.
- Matched the mobile clean-property capture to the exact zoom and crop used by
its placement guide, preventing shifted lights when generating above 1x zoom.
- Numbered each hidden generation strand independently and assigned contrasting
guide colors and square endpoints. Winter Wonderland now treats short side and ridge runs as required strands instead of merging them into the main roofline.
- Added explicit RIDGE, UP GUTTER, LOW GUTTER, and SIDE guide
labels. Parallel ridge and upper-gutter runs must remain separate, short side runs cannot extend down the wall, and unmarked siding no longer receives an invented decorative light wash.
- Added an explicit required-strand inventory to every Winter Wonderland
request. Ridge and upper-gutter rows are now required to remain visibly separate, while designs without a side strand explicitly prohibit vertical or side-wall lights.
- Preserved every connected segment and corner in multi-point Holiday Lights
strands. Winter Wonderland can no longer drop a short upper-left or diagonal section while keeping only the longer horizontal ridge section.
- Corrected Winter Wonderland installation realism. Drawn dots and symbols are
now temporary placement guides: generated C9 bulbs, wire, clips, and wreaths are realistically scaled and snapped to nearby rooflines, gutters, fascia, columns, doors, or other physical mounting surfaces instead of floating over the original mockup. Strand endpoints and exact item counts are scope-locked so the preview does not add lights to unmarked rooflines or landscaping.
- Added a desktop 3D Satellite property preview with orbit, tilt, rotation,
and zoom. Reps can capture the positioned Google 3D scene from the current browser tab and use the cropped result as the actual Holiday Lights drawing canvas; screen sharing stops immediately after the snapshot.
- Fixed the web Holiday Lights Street View preview so its initial camera
bearing points from the Google panorama toward the selected property, matching the property-targeting behavior already seen on mobile.
- Replaced the fixed, immediately saved Street View snapshot with an adjustable
camera preview on mobile and web. Reps can drag to pan and tilt, zoom or use fine-position controls, and explicitly lock the acceptable property view before it becomes the design canvas.
- Fixed mobile Street View's endless property-image spinner by sending Google's
required location parameter, prefetching the initial frame, and exposing a 12-second failure state with Retry. The reported property returned from Google in under half a second during direct verification.
- Added a mobile 3D Satellite snapshot picker with drag, pinch, rotation,
tilt, zoom, reset controls, and durable saving of the exact framed view used for the Holiday Lights design.
- Added desktop Move items mode for dragging completed strands, wreaths, and
mini-light areas, matching the existing mobile interaction.
- Added double-click-to-finish for desktop strands and long-press-to-finish for
mobile strands. Mini-light areas now render as dense illuminated clusters instead of circular spirals.
- Corrected Winter Wonderland storage validation for the seeded production
tenant, preserved one request across retries, and capped the visible attempt at four minutes so a permanent persistence failure cannot spin indefinitely.
- Changed Winter Wonderland to a faster landscape preview render and added a
live phase, percentage, and elapsed-time progress bar on mobile and web. Strengthened the prompt to preserve the exact home and drawn light geometry and reject blank, gray, monochrome, or incomplete output.
- Fixed the gray Winter Wonderland canvas after successful generation. Mobile
now supplies its signed-in bearer token and active tenant when loading the protected mockup, while the storage route validates that mobile organization before returning the image.
2026-08-05
Smoother Holiday Lights canvas and realistic previews
- Rebuilt the office web Holiday Lights estimator around the same three phases
as mobile: Setup, Design & Packages, and Review & Send. The old nested Design, Power Wash Bundle, and Generate Quote tabs no longer control the workflow.
- Replaced the web phase's remaining legacy map-and-zone designer with the
direct mobile-style package canvas. Web reps can now draw real C9, mini, and permanent-light strands, place lit wreaths, outlets, and cable runs, change color sequences, edit exact item pricing, and generate Winter Wonderland from the actual composed design.
- Added 1x–4x design zoom on web and mobile, working Undo/Delete actions for
both lighting and power layers, deletion for generated mockups and copied packages, selectable wreath sizes, and separate strand-priced mini-light areas for each bush, tree, or planting bed. Mobile design actions now sit at the bottom of the page rather than floating over the canvas.
- Extended mobile Winter Wonderland generation to the full three-minute image
window. Slow or storage-pending results now poll the same request identity without replaying the original transport or creating a second billable image.
- Fixed private Winter Wonderland persistence by rebuilding the upload path
from the authenticated organization and request identity. Cross-tenant references are rejected and customer render links remain organization-scoped.
- Corrected the private-path validator for production's seeded organization
UUID, which previously generated the image successfully but rejected its storage path forever. Mobile now retains the pending request with the estimate, resumes that same paid result after reopening, and stops an attempt after four minutes instead of spinning indefinitely.
- Connected Preview customer proposal on mobile. It now shows the selected
lighting package over every included property image, the generated Winter Wonderland scene, package pricing and deposit, and customer notes. Delivered customer proposals use that same workspace image set instead of dropping mobile-created Holiday Lights visuals.
- Fixed the production Winter Wonderland crash caused by an unavailable native
Linux image binary. The endpoint now validates the bounded PNG header without loading that native dependency. Mobile also adds Move items for dragging existing strands, wreaths, and mini-light areas to a new position.
- Added an editable estimate title, persistent Good/Better/Best selection,
full installation-week ranges, customer notes, and internal crew notes to the web workspace. Review no longer duplicates the customer contact form.
- Removed a repeating half-second autosave loop that could make text and the
header move continuously or leave the Holiday Lights builder unresponsive.
- Kept the Save/Saving/Saved header area a fixed width so normal draft saves do
not shift the centered estimate title.
- Made Winter Wonderland resize oversized design previews before upload and
honor its full generation timeout instead of failing at the former one-minute transport cap.
- Stabilized the mobile design canvas so drawing, typing, and scrolling no
longer recreate the live overlay or repeatedly write unchanged layout state.
- Replaced solid roofline strokes with individually spaced glowing bulbs and
replaced generic placement dots with recognizable illuminated wreaths.
- Connected Winter Wonderland to the secured AI mockup workflow so mobile
reps can generate a snowy blue-hour property scene and keep the editable lighting plan layered above it.
- Replaced free-form installation dates with clearly labeled full-week choices;
the exact installation day can be assigned later during route planning.
Three-step Holiday Lights estimate builder
- Rebuilt the mobile Holiday Lights workflow as Property, Design & Packages,
and Review & Send instead of splitting customer, products, and pricing across disconnected pages.
- Added default-property selection with alternate/new property support, Camera
and Gallery uploads, Satellite and Street View snapshots, and Satellite as the default map view.
- Added tap-and-draw design tools for C9 rooflines, mini lights, wreaths,
permanent lights, power locations, cable runs, timers, and custom repeating color patterns.
- Added editable exact-price calculations, Good/Better/Best packages, a
recommended three-year agreement with the configured discount, a higher one-year option, 50% approval deposit, installation-week scheduling, and editable email/SMS review before sending.
- Persisted the versioned Holiday Lights workspace through mobile sync and the
web API so the design, selected package, pricing, schedule, and notes survive reopening on either surface.
Mobile estimate disposition reliability
- Fixed manual estimate approval, decline, archive, pending, and draft changes
failing because the mobile endpoint selected a retired notes column.
- Estimate notes now save through the canonical customer-note field, while
disposition failures report a disposition-specific error.
Mobile dispatch assignments, large photo sets, and portal job history
- Added owner/manager Trucks & crew controls to mobile job pages. A job can
use multiple trucks, and every selected truck keeps its own field run.
- Added truck and crew reservation to mobile lead appointments. The assignment
is visible on the lead and included in owner schedule data.
- Removed the eight-photo field-proof limit. Gallery selections upload three
files at a time with visible progress, and proof galleries virtualize large sets such as 200 before/after photos.
- Made signed-in customer accounts honor their explicit organization/customer
link, so completed jobs remain visible even when an older client record has no matching email address.
Company contact routing and multi-photo selection
- Changed mobile Text actions to open the customer's Clean Estimate Inbox
conversation instead of the user's personal SMS application.
- Changed mobile Call actions to open the company phone system first. The
call sheet retains a clearly labeled Use device dialer instead option for deliberate outside calls.
- Added multi-select Gallery uploads for before/after job photos. Selected
files upload independently and successful photos remain saved if another selected file fails validation or upload.
Field ETA, photo uploads, flexible completion, and crew handoff
- Added Send ETA to mobile jobs and leads. On the way is the default,
with optional 15-, 30-, 45-, and 60-minute arrival windows. Available SMS and email channels are both used and logged; jobs move to En route.
- Added Gallery selection beside Camera for before/after job photos and
replaced Android local-file network reads with direct binary uploads.
- Made checklist and before/after proof recommended instead of mandatory. A
confirmation allows a legitimately finished job or truck run to close when proof is missing.
- Added persistent service-level Crew instructions to residential
estimates. Instructions survive reopening, copy into jobs, and display as a named crew alert.
Private attachment storage and retention
- Added organization storage usage under Settings > Storage & Attachments.
- Moved record attachments to immutable private paths with verified metadata
and short-lived signed downloads.
- Added soft deletion and a 30-day recovery window, followed by a daily purge
of expired objects and metadata.
Linked jobs are visible from won estimates
- Replaced the generic Linked job: Created line on mobile estimates with
the actual linked job number, current status, scheduled date, and scheduled time.
- Added a clear View job action so staff can open the attached job directly
from the estimate without returning to the Jobs list.
Visible job scheduling, follow-up delivery, and combined approval totals
- Kept Create & schedule job pinned above Android's system-navigation area
while the dates, times, and notes scroll, restoring the missing scheduling action on shorter screens.
- Moved the follow-up Send SMS, Send Email, or Send SMS + Email
action into a fixed full-width footer so an editable draft can always be delivered without scrolling past a clipped action row.
- Updated one-time-plus-Suds-Club signature review to show the full amount due
at approval: the one-time estimate, the three-month membership signup charge, and applicable membership tax. The canonical estimate total used for concurrency verification remains unchanged.
Approval-sheet and mobile payment reliability fixes
- Kept the residential Approve & Sign choice screen inside Android's top
and bottom safe areas, including edge-to-edge devices that report a zero top inset inside a modal and devices with gesture or button navigation.
- Made the Suds Club signup charge exactly three times the displayed monthly
price. The estimate and approval choice now state that amount clearly; for example, a $189/month membership shows $567 due at signup.
- Increased the native Stripe request window for slower mobile connections.
After Stripe accepts a card, a delayed balance refresh now warns staff not to charge the card again while the ledger catches up.
- Test-mode Stripe workspaces now use a simulated Tap to Pay reader. Live Tap
to Pay continues to require Android Developer Options, USB debugging, and wireless debugging to be turned off.
Clearer estimate progress, scheduling, and payment actions
- Added a readable label beneath every estimate progress dot: Draft,
Sent, Viewed, Approved or Declined, and Paid.
- Added separate Start date and End date fields to Copy to Job, including
multi-day job persistence. Moved the solid blue Create & schedule job button into a fixed footer so it stays visible and clearly tappable.
- Split the estimate payment area into Request and Add. Request opens
the editable email/SMS delivery screen and resends the customer portal directly to its Payments tab instead of creating a separate checkout link.
- Choosing Card under Add now opens Tap to Pay or **Enter card
details**. Tap to Pay uses Stripe Terminal on a compatible NFC device; entered cards use Stripe PaymentSheet inside the app. Check and configured Other methods remain manual payment records. Deposit and full/partial payment amounts use the same global workflow across estimate types.
- Android build 73 explicitly follows the production update channel, so later
JavaScript and UI releases can install over the air without another native download. Changes to Stripe or other native SDKs still require a new signed build.
Review estimate and invoice messages before sending
- Changed every mobile estimate Send and Resend action to open a full
Review & Send screen before delivery. Staff can choose email, text, or both; edit the email subject, email message, and SMS copy; preview the exact content; and choose whether to include the PDF.
- Preserved reviewed estimate copy through offline queueing and automatic
retry. Editing a message creates a distinct idempotent delivery request, so an older queued send cannot silently replace the reviewed version.
- Confirmed all mobile invoice Send, Resend, and Reminder entry points continue
to open the invoice Review & Send screen rather than delivering directly.
- Renamed the estimate review footer actions to Cancel and Send again
after the first delivery. This makes it clear that Cancel exits without changing the already-sent estimate, while Send again delivers another copy.
Suds Club restored to mobile residential approvals
- Restored the tenant-priced Suds Club card to eligible residential estimate
screens with the monthly charge, four yearly visits, and included services.
- Added a required approval-scope step before signature so the customer can
approve the one-time estimate, Suds Club, or both.
- The signed acceptance records the exact choice. Suds Club-only approval does
not create a one-time job or show a one-time payment balance.
Automatic estimate disposition progression
- A successfully delivered estimate now presents as Pending instead of
Draft. Customer views keep it Pending while recording view activity; customer signature or representative approval moves it to Approved; and successful Copy to Job moves it to Won.
- Customer or representative decline moves the estimate to Declined.
Expiration processing now archives expired canonical estimates for every estimate family, not only commercial source records.
- When a customer accepts one residential package, the other unselected
one-time package mirrors are archived automatically. Suds Club remains independent and is not archived, because customers may accept both the one-time restoration estimate and recurring maintenance.
One disposition and closing workflow for every estimate type
- Standardized Residential, Holiday Lights, Commercial, Fleet, Asphalt, and
Generic Quote estimates on Draft, Pending, Approved, Won, Declined, and Archived. Won is applied automatically only after Copy to Job succeeds.
- Added an estimate-screen disposition control for representative approval,
decline, archive, returning to draft or pending, and eligible unsigned deletion, plus a visible Client shortcut back to the customer record.
- Replaced Close Kit and Present & Close with a direct sequence: **Save &
review, Approve & Sign, Request/Record Payment, then Copy to Job** with separate start/end dates and real start/end times.
- Made Copy to Job available before signature when the representative
explicitly confirms manual customer approval. Removed the redundant estimate options, arrival-window, four-day suggestion, and Back to rep steps from the active mobile route.
Cleaner mobile estimate status and property view
- Moved estimate progress directly beneath the mobile header and reduced it to
connected, labeled status dots, so the current lifecycle position is the first estimate content visible without a large summary card.
- Removed the repeated Delivery and Customer cards from mobile estimate detail.
The top customer card now owns the full service address, email, phone, and call/text actions in one place.
- Made Satellite the default property-map mode and added a Street View action
beside the standard Map and Satellite controls.
- Consolidated deposit and balance information into one Payment card. Separate
Request and Add actions now distinguish a shareable Card/ACH request from a check/manual payment already received.
- Added direct Add signature and Add notes actions, tappable line items,
uncapped residential price entry, and removed redundant estimate-details, estimate-options, deposit, and duplicate payment sections.
- Replaced the broken three-dot behavior with an Estimate actions bottom sheet,
made customer lookup available even for an unlinked estimate, and made a residential copy open directly in the residential builder.
- Corrected the Android bottom-sheet width so every estimate action now renders
its label and supporting description beside the icon.
- Rebuilt follow-up delivery around real SMS, Email, and Both send
actions with shorter customer-friendly default copy.
One-time estimates and Suds Club can be selected together
- Changed Suds Club enrollment so it no longer clears the customer's selected
one-time package. Customers can select the one-time estimate, Suds Club, or both when restoration work or add-ons sit outside recurring maintenance.
- Updated customer-facing copy to explain the separate one-time and four-visit
recurring scopes and pricing.
Named signatures on customer estimate acceptance
- Added a required signer-name field beside the signature pad on residential
package acceptance. The customer name is prefilled for speed but can be corrected before signing, and the accepted estimate now retains both the signer name and drawn signature instead of allowing an anonymous signature.
2026-08-04
Owner and manager truck-run execution
- Restored the mobile job execution card for owners and managers. The server
already authorizes management on organization-owned truck runs; the mobile UI now exposes the matching Start Job, checklist, photo-proof, and Complete Job controls instead of hiding the entire card behind a field-role check.
- Technician and crew-lead execution remains limited to runs assigned to their
user, crew, or truck.
Commercial roof pricing, building bundles, and price adjustments
- Added roof-wash pricing from each building's saved roof measurement, with a
choice to include it in the bid or show it as separately priced work.
- Added selected-building proposal groups, so large properties can show one
line per building type while preserving every map outline, measurement, height, condition, and internal cost.
- Price changes now show the original and adjusted totals, per-unit rates,
per-square-foot rates, and line allocations together.
- Adjusted proposal line prices now add up to the saved customer price, so a
manually changed bid no longer carries stale per-building pricing into the maintenance proposal.
Bluetooth stays idle outside real calls
- Stopped the Android voice layer from opening Bluetooth headset monitoring
whenever Clean Estimate merely launches. Vehicle displays and headsets now enter call mode only for a real incoming or outgoing company call, and the route is released after reject, cancel, connection failure, or hangup.
Invoice actions stay above phone controls
- Raised the mobile invoice detail action bar with both device-safe-area
spacing and a minimum bottom clearance. Send invoice, Send reminder, and Collect payment no longer sit partly below the screen on phones whose Android navigation bar reports a small or missing inset.
Smaller Android permission footprint
- Removed the unused Display over other apps permission from production
Android builds and blocked future Expo prebuilds from restoring it. Incoming calls continue to use the supported full-screen call-notification path.
Complete mobile notification destinations
- Made every supported activity-center alert actionable on mobile. Holiday
Lights, fleet, and commercial proposal alerts now open their estimate detail; overdue-invoice summaries open Invoices; and email-open, missed-call, and voicemail alerts open the matching customer conversation or Inbox.
Unobstructed controls at the bottom of mobile workspaces
- Added clearance below every shared More workspace so its final report and
navigation tiles stay above the persistent tab bar and raised Create button. Tapping a bottom analytics tile now opens that report instead of accidentally opening the Create launcher.
- Applied the same protected scroll clearance to Team, Timecards, Time Clock,
Availability, Training, and My Pay. The live Team directory now refreshes its shift-status snapshot every 30 seconds while the page is focused and online.
- Timesheet review now checks the effective Manage Team permission before
presenting its approval action. Managers with view-only access retain the live timesheet detail without receiving a control the server will reject.
- Time Clock now resolves today in the organization's timezone and accepts a
job-linked clock-in only for a current, non-cancelled job assigned directly to the employee, their crew, or their truck.
Faster Android cold launches around voice calling
- Stopped the Twilio background voice service from receiving Android's normal
launcher intent on every cold start. Only genuine call and call-notification actions now reach the service, preventing an unnecessary service start from contributing to launch-time ANRs while preserving incoming and outgoing calling.
Live Inbox isolation from API test estimates
- Excluded communications linked to
api_environment = testestimates from
the live mobile Inbox. Integration and sandbox quote testing can no longer create customer conversation cards among a tenant's production messages, while unlinked communications and messages tied to live estimates remain visible.
Embedded card and ACH deposits on accepted estimates
- Added Stripe's embedded Payment Element directly to the customer estimate
page. After signing a standard estimate, package option, or customized residential proposal, the customer can pay the required deposit by card or ACH without leaving the accepted quote for a separate checkout page.
- Card payments now confirm inline, while ACH submissions display a clear
processing state and reconcile automatically through Stripe webhooks. A Pay later option preserves the accepted estimate without forcing payment.
- Hardened the flow with server-calculated deposit amounts, signed customer
links, rate limits, payment-intent scope checks, idempotent retries, and pending payment records so refreshes and repeat taps cannot collect or attach the same deposit incorrectly. Already-paid and bank-processing refreshes now resolve as normal page states without a false browser network error.
Restored residential packages and direct Suds Club enrollment
- Restored the customer-facing package experience when a residential estimate
contains only one package. The hosted quote now opens on a full-width package card and visibly lists every saved inclusion instead of hiding Package Options and falling back to the plain estimate plus Customize tabs.
- Expanded the Suds Club offer into a compact membership landing section on
every residential estimate. It shows four visits per year, the home's tier, the tenant-configured seasonal inclusions, and only the customer-facing monthly charge.
- Added a signed Add Suds Club to this estimate action. Enrollment is
recalculated from the tenant's published matrix and the home's square footage, leaves the estimate unapproved until the customer reviews and signs, and requires custom-tier homes to receive a staff-confirmed price. Expired estimates reject enrollment until the service provider refreshes the offer.
Immediate visibility for offline estimate drafts
- Fixed the mobile Proposals screen so it reads device-local estimates even
while the phone is offline and refreshes whenever the user returns from an estimate builder. Newly saved drafts from every estimate type now appear immediately instead of remaining hidden behind a previously cached list.
- Offline proposal loading now skips remote requests instead of waiting for a
server timeout. A failed background refresh keeps the previously cached estimate list usable, and focus refreshes wait for the signed-in workspace identity before requesting data.
Clear first-send estimate confirmation
- Corrected the mobile estimate-detail delivery confirmation so an unsent
estimate says Send estimate and Queue delivery. Previously, the first delivery was incorrectly described as a resend; sent estimates still use the appropriate resend wording. The first-delivery success message now also says the estimate was delivered instead of calling it a resend.
Immediate Generic Quote synchronization and consistent numbers
- Fixed online Generic Quote saves so a newly queued draft starts syncing
immediately instead of waiting for the connection to change or the app to restart.
- Kept the Generic Quote proposal mirror aligned with the server-issued,
workspace-wide estimate number. Provisional device numbers no longer remain on the linked server proposal after synchronization.
More reliable mobile AI photo measurements
- Extended the signed mobile photo-analysis request window to accommodate
detailed vision responses that finish after 45 seconds. The app now waits up to one minute before offering a retry, while retaining the same idempotent analysis request so a retry cannot create a duplicate AI charge.
Consistent Generic Quote draft saving
- Both Save Draft actions on the native Generic Quote Review screen now
queue the same durable proposal for synchronization and open it from Proposals. The header action no longer stops at a device-only builder snapshot while the bottom action creates the shared estimate.
Steadier mobile Global Search
- Global Search now waits briefly for typing to pause before sending its live
lookup. Entering a customer or record number no longer starts a separate API request for every character, reducing request bursts and avoidable mobile timeouts.
2026-08-03
Exact-cent mobile payment receipts
- Corrected mobile payment amount formatting so collection confirmation and
successful receipts preserve cents. Small and partial payments such as $0.01 now remain exact instead of displaying as $0 after collection.
Organization-local mobile Schedule follow-ups
- Corrected mobile and admin Schedule follow-up dates to use the workspace's
configured timezone. Organization-wide owner and manager views and assigned sales-rep views now keep follow-ups on the proper business day between UTC midnight and local midnight instead of dropping or duplicating them at the boundary.
Cleaner estimate selection and safer invoice actions
- Rebuilt the mobile Create > Estimate type picker with compact, aligned
horizontal rows so every estimate type is easier to scan and select. Signed Android visual coverage now verifies the production picker layout.
- Updated mobile invoice detail, Review & Send, and payment screens so
bottom actions stay above the device safe area. Compact phones can scroll expanded Check and Other payment states without hiding alternate methods or the final action behind the footer or system navigation.
Client-safe mobile Inbox conversations
- Split the Inbox list into separate conversations for each explicitly linked
client, even when multiple client records share the same phone number or email. Unlinked messages remain in their own conversation, and estimate, invoice, and missed-call activity no longer attaches to an ambiguous shared contact.
- Kept an opened Inbox conversation scoped to the selected client ID when two
client records share a phone number or email address. The selected name, profile link, timeline messages, and internal activity no longer switch to or merge with the other matching contact.
- Scoped message loading and Text, Email, and Internal note validation to the
selected client before delivery or logging, preventing cross-client activity from appearing as a successful send.
Clearer mobile agenda, Schedule continuity, and deterministic QA evidence
- Restored the approved populated Home summary treatment so the next scheduled
visit appears inside its own white bordered card on the blue hero. Time, service, customer, full address, record action, and navigation remain intact.
- Collapsed untimed Day follow-ups into one accessible yellow or orange summary
by default, keeping timed purple appointments and blue jobs immediately visible. Expanding the summary reveals every original follow-up card and action without changing the underlying tasks.
- Kept the resolved Day payload visible while Week loads so Schedule does not
briefly claim $0 or No work before the expanded range arrives. Final daily and weekly revenue continues to count scheduled jobs only.
- Made signed Inbox evidence deterministic by deriving and searching for the
exact token-scoped QA conversation before verifying its SMS bubbles and aligned internal event. The core Schedule fixture now uses the supported site_visit appointment type, and signed Day evidence requires both the compact follow-up summary and seeded timed work before capture.
Holiday Lights estimate identity
- Replaced the generic customer-card badge in the Holiday Lights builder with
the real allocated estimate number. New unsaved quotes say Draft estimate, and autosave retains the server-returned number for the rest of the builder.
- Strengthened signed visual evidence so Holiday Lights verifies
Estimate #E-2043, while Fleet evidence now scrolls through the complete monthly pricing summary above its pinned action bar.
Safer invoice-payment retries
- Blocked card, keyed-card, and manual payment writes when an invoice has no
remaining balance, closing a shared edge case that could otherwise accept a positive payment against an already-paid invoice.
- Reused active customer-portal checkout sessions so repeated taps cannot mint
multiple payable links. A session whose hosted amount no longer matches the invoice is expired first; discounted invoices also reconcile to the exact payable total instead of trusting stale service rows. Expired or asynchronously failed sessions can create one generation-safe replacement, while a confirmed paid session remains closed.
Compact empty Home summary and denser Inbox
- Removed the duplicate scheduled-visit empty panel from the blue mobile Home
summary. Empty periods now keep the approved compact appointment, job, and revenue metrics, while the Agenda section remains the single empty-state explanation.
- Tightened mobile Inbox cards and Call/Text controls so six populated
conversations fit the approved Android viewport without removing identity, assignment, unread, activity, document-view, or quick-action information. The active sort now uses a separately spaced chevron instead of a character joined to the label.
Generic Quote review starts at the header
- Fixed the native Generic Quote builder so selecting Review & send from a
long, scrolled line-item list opens the review at the top with Review Quote visible. The change resets only the shared screen's scroll position; draft persistence, review content, and save/send behavior are unchanged.
Compact Create launcher fidelity
- Tightened the mobile Create new sheet to match the approved production
layout: all six Client, Lead, Estimate, Job, Invoice, and Task / Event actions now use consistent compact rows and lighter icon treatments. Job and Invoice prerequisite guidance remains available to assistive technology without making those two cards taller, while routes and backend permission checks are unchanged.
Non-persisting Holiday Lights release preview
- Added a guarded, read-only Holiday Lights fixture for signed Android visual
verification. It opens the approved Stone Ridge HOA three-zone design with the $5,160 total entirely in memory, captures top and lower evidence, and disables auto-save, draft saving, editing, and estimate delivery. Normal Holiday Lights estimate routes remain unchanged.
Non-persisting Commercial and Fleet release previews
- Added explicit read-only, in-memory populated previews for signed Commercial
and Fleet builder verification. The approved Commercial fixture now shows its three-service $14,600 scope and crew/truck summary, while Fleet shows all 50 vehicles, schedule, site requirements, selected add-on, $2,810 per-visit price, and $11,240 monthly estimate. Preview routes cannot auto-save or save a draft, ordinary builder routes are unchanged, and Commercial top/lower evidence now proves two materially different scroll positions.
Interactive Asphalt satellite tracing
- Added editable satellite tracing to the native Asphalt Measure step: reps can
add multiple pavement polygons and independent crack paths, undo or clear the working trace, and explicitly add each verified zone or run to the estimate. Versioned v2 draft JSON now persists the geometry so reopened Measure and read-only Review screens redraw saved overlays. Legacy v1 drafts retain their numeric totals without inventing missing geometry, while manual overrides and service-address changes clear stale incompatible shapes and only the trace-backed totals, preserving independently entered manual measurements.
Populated Asphalt release preview
- Added an explicit non-persisting Asphalt preview for signed visual release
validation. It renders the approved Greenway Offices satellite overview with saved pavement and crack overlays, all four measurement metrics, priced service rows, phasing, and the $13,295 total. The guarded preview cannot auto-save or manually save, while ordinary Asphalt routes retain the full editable tracing and estimate workflow.
Physical-first signed runner coordination
- Core surfaces, CRM/operations, billing/delivery, and estimate-builder signed
Android gates now require an explicit device serial, verify that the selected device is reachable and fully booted, reject emulators unless -AllowEmulator is deliberately supplied, and share one exclusive lock per device so concurrent suites cannot collide on the same phone.
Non-persisting Generic Quote release preview
- Replaced the signed Generic Quote gate's local draft creation with an
explicit read-only, in-memory populated preview. Customer, item, total, delivery, and review fidelity remain covered, while navigation bypasses persistence and Save Draft and Send Quote remain disabled and guarded, so a release pass leaves no proposal or sync item behind.
Controlled signed job-lifecycle gate
- Added a physical-first signed Android gate that consumes only an explicitly
confirmed QA-LIFECYCLE-* fixture: it verifies accepted-estimate/job entry, completes two QA truck runs independently, and creates one draft invoice. Exact release identity, UUID and QA-label validation, the shared device lock, exact on-screen fixture matching, and a documented one-time seed contract prevent the gate from targeting customer records; communication, checkout, collection, and payment actions remain out of scope.
Release-agnostic signed core-surface gate
- Added a signed Android runner for read-only Home, Schedule, Inbox, Phone, and
Create verification with exact native version/build and Expo OTA, channel, runtime, and source checks plus JUnit, logs, and screenshot evidence. Physical hardware remains the certification default; -AllowEmulator enables only a clearly labeled emulator preflight and does not replace physical-device QA.
Signed CRM and operations release gate
- Added a release-agnostic signed Android runner for read-only Clients, Leads,
Pipeline, Jobs, and multi-truck job verification, including pinned sales notes. It checks the exact native and Expo release identity, isolates JUnit, logs, and screenshots per run, requires physical hardware by default, and treats -AllowEmulator as preflight evidence rather than device certification.
Signed billing and delivery release gate
- Added an ordered, non-mutating signed Android gate for Invoices, payment and
tip choices, the hosted-checkout handoff, provider-backed delivery history, offline retained-session behavior, and online recovery. It verifies the exact native and Expo release identity, requires physical hardware by default, and stores isolated screenshots, JUnit results, and logs for each run.
Truthful Inbox activity and compact assignments
- Removed malformed legacy note and email placeholders from Inbox cards and
opened conversations while preserving real content and using truthful event- type labels when no valid body or subject exists. Compact assignee spacing and two-line conversation-header assignment wrapping keep names readable without sacrificing the approved six-card Inbox density.
More complete realtor listing-photo analysis
- Expanded retained Zillow galleries from an eight-photo front truncation to a bounded 40–80-photo set and changed vision input to a 20-photo, tail-heavy selection. The agent now sees the hero/front angles plus the end-of-gallery backyard and exterior photos instead of mostly early interior rooms.
- Deduplicated Zillow's multiple size, crop, and file-format URLs by stable photo identity, keeping the highest-quality version so one property photo cannot occupy several gallery or AI-analysis slots; existing cached galleries are normalized when opened.
- Corrected manual reanalysis to refresh through the Zillow property-detail actor whenever the listing retains its Zillow URL. Search-card results, which may expose several versions of only the hero image, are now fallback-only.
- Refined window counting to reconcile overlapping photos facade by facade and total unique window openings across every visible side without counting panes, reflections, or sliding doors.
- Tightened fence measurement so deck and porch railings, pergola structure, landscaping, shadows, crop rows, distant boundaries, and unseen inferred lengths are excluded.
- Added explicit pergola detection, condition assessment, and cleanable-surface sizing. Pergola scope is priced through the configured deck-cleaning rate and labeled separately on native estimates, while patios beneath pergolas remain their own line item.
- Added Reanalyze listing photos to estimate review so an operator can refresh the gallery and replace an incomplete AI-generated scope before approving outreach.
- Added the same refresh action directly to each lead's Photo estimate card so an incorrect gallery or measurement can be replaced without first navigating into the estimate editor.
- Versioned the exterior analysis contract so stale pre-refinement assessments without a quote can be reconsidered instead of reused indefinitely.
Realtor outreach copy rotation and relationship follow-up
- Added five complete variants of every four-email realtor quote sequence. Each property receives one stable variant from first quote through final follow-up, reducing repetitive copy without breaking thread continuity.
- Made every variant independently editable and previewable in the tenant Admin area, with protected estimate fields and per-variant default restoration.
- Added the event-triggered Another listing copy to the editor with five rotations. It references the realtor's earlier listing and the newly detected property before presenting the new approved quote.
- Updated the final touch with a concise referral request that invites the realtor to reply with another property address for pricing.
- Updated every first-touch realtor quote to introduce the company and its power washing, window cleaning, and gutter cleaning services before the estimate breakdown.
- Added a consistent P.S. to every realtor email offering $50 for each completed referral, including tenant-customized copy and the event-triggered Another listing message.
- Kept copy rotation secondary to the existing sender safeguards: a 25-email daily limit per inbox, paced delivery, verified contacts, reply suppression, and automatic compliance content.
Truthful mobile Generic Quote delivery
- The native Generic Quote review now lets reps choose email, SMS, or both,
with the PDF option available only when email is selected.
- Save Draft keeps unfinished work as a resumable draft, while **Send
Quote** saves with sent intent and durably queues the selected delivery channels behind the canonical quote write.
- Offline sends now report Queued. Online sends report Sent only after
delivery is confirmed, and clearly distinguish provider failures from work that is still queued.
- Newly created Residential, Generic, Commercial, Fleet, Holiday Lights, and
Asphalt records now carry their provisional canonical revision into the delivery queue. Their first send waits behind the canonical write instead of failing because the server revision has not returned yet.
- Today-based job cards now take one stable local-date snapshot when the card
opens, preventing a render refresh from changing which jobs appear current.
Canonical PDF delivery and organization terms
- Include PDF now creates a signed canonical estimate attachment for every
linked proposal module, including Generic Quote and Asphalt Maintenance, instead of limiting canonical attachments to Residential estimates.
- A missing or invalid requested PDF now returns Review required rather than
silently sending without the attachment.
- Customer links and canonical PDF attachments now fail closed unless the
configured application origin uses HTTPS, preventing signed tokens from being sent over a plaintext connection.
- Requested PDF readiness is checked before CE Pro reserves a delivery ledger
entry, so an invalid attachment cannot consume delivery quota while the send is waiting for review.
- Estimate and Generic Quote PDFs now load the organization's current terms and
conditions for both the PDF route and direct email attachments.
Customer quote scope and acceptance consistency
- Estimate and lead appointment tracking now use table-specific database
triggers, preserving confirmation timestamps while preventing unrelated estimate backfills from resolving job-only scheduling fields.
- The signed Android estimate-builder runner now matches the rendered
INSTALLED RELEASE About heading exactly before exercising any builder, treats successful native-tool stderr progress as logs rather than failure, safely escapes seeded builder deep links for the Android device shell, and reads live Maestro hierarchy data while save indicators animate.
- Generic quote optional items remain visible and clearly labeled on the web
quote and PDF, but unselected options no longer inflate included subtotals or enter the signed acceptance scope. Explicitly selected options are labeled and included consistently.
- Customer change requests can no longer turn an optional item into free signed
scope or overwrite authoritative totals. Submitted feedback is bounded and stripped of caller-supplied prices, stale legacy feedback totals are cleared, and optional scope enters acceptance only after the canonical estimate subtotal includes that item.
- Canonical Generic and Asphalt quotes now save a valid versioned pricing
snapshot for online acceptance.
- Asphalt customer quotes and PDFs now render the canonical named line items
instead of relying on internal service identifiers.
- Mobile Generic Terms & Deposit text now appears alongside organization
terms on the customer quote and PDF, with both included in signed acceptance.
- Asphalt phasing nights and notes now appear on the customer quote and PDF and
are bound into the signed, non-priced project scope.
Canonical retry integrity and estimate numbering
- Asphalt Maintenance, Holiday Lights, Commercial, and Fleet retries now count
as duplicates only when the complete authoritative estimate, proposal mirror, and canonical line-item set still match the committed mobile mutation. Missing or changed records return a conflict instead of a false success, including reconciliation after a lost server response.
- Proposal mirrors now adopt the estimate number issued by the server during
first sync, keeping proposal and estimate identifiers aligned.
- Canonical Asphalt estimates retain the native builder snapshot, so they reopen
in the mobile Asphalt workflow after local cache loss or a device switch.
Durable mobile Commercial and Holiday estimate photos
- Commercial site-condition and Holiday Lights property photos now copy into
organization- and user-scoped durable device storage as soon as they are captured. Each image keeps a stable UUID plus verified filename, content type, byte size, dimensions, and capture time.
- Offline saves create the canonical estimate first without a device-only URI,
then reuse the authenticated estimate-photo queue to upload the image. The queue retries idempotently, verifies the stored bytes, and preserves queued removal or replacement work.
- After upload, the app writes the protected HTTPS image address back into the
local builder and queues an optimistic canonical revision. Delivery waits for that revision, so another device and the customer quote receive a working photo rather than a file: or Android content: address.
- Stable client photo IDs and private storage rows remain authoritative after a
signed URL expires. Mobile estimate loads and customer photo views generate a fresh authorized URL from that row instead of depending on the cached URL.
- Fixed successful Commercial and Holiday photo uploads so completion always
queues the canonical photo-link revision. Upload responses now expose only a short-lived signed URL; later authorized views re-sign from the private row.
- Missing or changed durable metadata still stops before the canonical write,
and the server continues rejecting local-only image references as a final defense against modified clients.
Canonical Commercial and Fleet customer quote access
- Fixed customer links for mobile-created Commercial and Fleet estimates so
source-less canonical records render their complete shared quote instead of opening a not-found page.
- Preserved the specialized customer proposal layouts for older Commercial and
Fleet records that still have their legacy source-table links.
- Canonical customer quotes retain their saved line items, totals, terms, and
approval controls through the shared estimate view.
Targeted recovery for legacy proposal sync failures
- Added a one-time authenticated startup recovery for Commercial, Fleet, and
Holiday Lights proposal saves that failed only because the former direct table-write contract included unsupported proposal columns.
- Safely identified legacy failures return to the queue and use canonical
server validation. Unrelated validation, delivery, malformed, cross-workspace, and other estimate failures remain preserved for review.
Canonical mobile Commercial synchronization
- Commercial drafts now cross the authenticated estimate boundary instead of
writing proposal rows directly from the device. The server verifies organization access, ownership, linked clients, mutable status, and optimistic proposal and estimate revisions.
- The server recalculates every property service from its pricing approach,
quantity, rate, property square footage, portfolio discount, and recurring frequency. It then atomically saves the shared commercial-building estimate, canonical line items, proposal linkage, and retry marker.
- Exact retries are idempotent, stale or forged totals stop safely, and queued
delivery waits for the canonical estimate transaction to succeed.
Canonical mobile Fleet synchronization
- Fleet drafts now cross a server-authoritative persistence boundary that
validates estimate and Fleet permissions, organization scope, ownership, client linkage, and optimistic record revisions.
- The server canonicalizes vehicle definitions and add-on names, recalculates
vehicle, add-on, custom-item, discount, per-visit, and annual totals, then atomically writes the shared estimate, canonical line items, and proposal linkage. Exact retries are idempotent and stale or forged writes stop safely.
Canonical mobile Holiday Lights synchronization
- Moved native Holiday Lights saves behind the authenticated estimate endpoint
and removed direct mobile Holiday proposal-table writes.
- The server now validates real lighting zones and add-ons, recalculates
material, labor, add-on, zone, and estimate totals, checks ownership and revisions, and atomically persists the estimate, line items, and proposal mirror.
Canonical mobile Asphalt synchronization
- Routed native Asphalt drafts through the authenticated mobile estimate
endpoint instead of allowing direct proposal-table writes.
- The server now validates organization access, draft schema, measurements,
service quantities, totals, ownership, and saved revisions before atomically updating the shared estimate, line items, and proposal mirror.
- Exact retries remain idempotent, stale edits stop with a conflict, and direct
authenticated Asphalt proposal inserts or updates remain unavailable.
Generic Quote optional-item consistency
- Preserved optional Generic Quote line items through mobile sync and estimate
reopening. Optional choices remain visible and labeled while staying outside the included subtotal and tax calculation.
Residential item-specific AI tool handoff
- Photo to price and Measure property now ask the rep to select one Residential
service when the estimate does not have one selected yet, then open the chosen tool for that single item instead of applying its input to the whole estimate.
Commercial estimate builder workspace
- Rebuilt the mobile commercial estimate builder around four visible phases:
Property, Scope, Pricing, and Review, while retaining the detailed client, property, service, site-condition, terms, pricing, scheduling, and review steps.
- Added the approved Scope workspace with property imagery, Street View and
Satellite fallbacks, site notes, selected services, and truthful property, service, visit, and per-visit pricing metrics.
- Added an explicit Save draft action so an incomplete estimate can be
preserved without advancing the workflow.
- Hardened mobile draft reopening so missing records and local read failures
resolve to a recoverable state instead of leaving an estimate builder on an indefinite loading screen.
Fleet and Holiday Lights estimate workspaces
- Rebuilt the mobile Fleet inventory screen into the approved **Fleet
selected** workspace with real location imagery, one customer identity card, full address, compact vehicle controls, service-plan and add-on previews, site notes, and live per-visit and monthly pricing.
- Grouped the Fleet builder's detailed workflow into Client, **Fleet
selected, Frequency, and Review**, with explicit draft saving and recoverable saved-draft loading.
- Rebuilt the Holiday Lights Design phase around the approved property visual,
compact color and bulb controls, slim scope rows, installation/removal/storage summary, estimate total, night preview, and a full editor behind Edit design.
- Added explicit Holiday Lights draft saving, guarded blank drafts, persistent
design preferences, stronger Review & Send validation, and reliable send loading feedback.
- Corrected commercial Scope addresses to include the city/state separator used
by the approved full-address design.
Commercial Workiz estimate handoff
- Replaced the misleading Push this plan to Workiz action with **Copy for
Workiz**.
- The proposal tool now tells reps to create a native estimate on the linked
lead or job, paste the prepared line items, and verify the total before saving.
- Disabled the old description-only update so it cannot be mistaken for
estimate or line-item creation.
Mobile CRM, Pipeline, and invoice fidelity
- Restored the Android Create sheet to six full-width bordered action cards so
icons, labels, prerequisites, permission states, and navigation controls no longer collapse into a narrow vertical stack.
- Closed stale offline-sync lifecycle listeners during sign-out and workspace
replacement so only the current mobile session can react to foreground and connectivity changes.
- Removed the redundant tenant price-book row from Residential Estimate setup,
exposed all six estimate types, including Generic Quote, in a two-row table, rebuilt Photo to price and Measure property as a bordered two-cell action table without detached arrows, and kept the full-width start button visibly present in its disabled state.
- Tightened Residential Estimate setup to the approved compact hierarchy with
a clean empty-property state, visible Save Draft control, a scrollable estimate-type rail, boxed AI quote tools, and a clearly disabled start action until customer context exists.
- Corrected card collection terminology and state handling: mobile now calls
the flow Stripe hosted checkout instead of Tap to Pay, never claims payment success merely because the browser returned, and lets staff refresh the server-backed invoice or choose another method.
- Hardened push-token registration and removal so failed Supabase writes are
not cached as successful. The device retains retry state until the server mutation succeeds.
- Kept mounted Jobs screens current across local midnight so Today and
Tomorrow schedule labels roll forward without requiring a reload.
- Rebuilt the mobile Schedule Week view as compact day columns with daily
revenue, chronological real-data cards, approved lead/job/follow-up colors, and truthful empty-day states while preserving filters and role visibility.
- Hardened compact Lead and Pipeline cards for long production names and record
numbers, keeping the customer and value readable and restoring the approved rounded, scannable pipeline hierarchy without inserting sample data.
- Corrected invoice delivery semantics so drafts and unsent invoices say
Send invoice while only delivered invoices say Send reminder. Restored separate collapsed Payments, Reminders, Attachments, and Internal notes accordions with real counts and empty states.
- Restored the approved individual Client hierarchy with the identity and full
clickable address above five actions and dedicated Overview, Activity, Estimates, Jobs, Invoices, and Properties tabs. Current work, balances, and recent activity remain available without one oversized continuous page.
- Bounded the Android Pipeline stage rail, tightened its stage cards, and
reorganized deal cards around customer, estimate number, full address, service type, value, assignee, days in stage, actions, and truthful estimate view activity. Viewed records remain in their actual workflow stage and are still discoverable through Hot filtering.
- Tightened invoice ledger rows and added Unsent plus amount-due controls while
preserving the linked job, full address, send/due dates, balance, and status-specific activity. Invoice detail now keeps customer/service context, viewed and overdue metadata, delivery/preview/call/download controls, and the existing collection actions together.
- Matched the individual Client quick-action row more closely to the approved
mobile reference with five compact bordered tiles, a primary blue Create action, tighter identity spacing, and an unclipped six-tab rail.
- Made Invoice details collapsed and Line items expanded by default, exposed
the Overdue filter within the initial compact filter rail, and fixed overdue totals so an invoice that has never been sent is not treated as overdue just because its due date is in the past. Empty invoices now retain a truthful Line items section, and secondary record links stay under collapsed More actions instead of appearing as oversized duplicate rows.
- Added the approved dedicated Leads list with compact cards, full readable
names and addresses, visible sorting, and real-only source, estimate, activity, and rep context. More and Pipeline now open this list while older Clients-based deep links remain compatible.
- Rebuilt Lead detail around the approved identity, five-action row, four tabs,
conditional HOT/view signal, live metrics, next action, appointment, and sales-notes hierarchy.
- Tightened Job detail and populated Job rows: full addresses no longer
truncate behind Call/Text, empty property-map fallbacks are compact, truck and crew summaries remain real, needs-review rows are amber, and terminal jobs cannot expose a stale Start Job action.
- Added a signed, populated generic quote QA path through customer information,
a custom service line item, tax totals, and Review Quote without saving or sending the test quote.
Signed-device core-tab fidelity
- Tightened Inbox to the approved six-card phone density while preserving the
full identity, activity, estimate/invoice view, Call, and Text information. The signed-device flow now opens conversations by a stable record identifier instead of a screen coordinate.
- Tightened recent Phone activity, action buttons, icons, and call-score badges
to match the approved compact Phone reference.
- Matched Schedule's approved funnel control and made the middle-dot separators
encoding-safe across Schedule and Phone so Android does not show corrupted characters.
- Added a populated signed-production visual gate: each routed screen must be
captured at the approved phone viewport and compared directly with its approved PNG before it is accepted.
2026-08-02
Mobile estimate, job, and invoice safety refinements
- Corrected compact Estimate cards so the status band shows the real workflow
status, the estimate type appears once beside the full address, and Commercial, Fleet, Holiday Lights, and general records use recognizable visual markers. The overflow control now opens a real action menu, and the viewed-count separator renders consistently on Android.
- Added working mobile Job filters for service and truck, earliest/latest start
sorting, and state-aware actions from Start through Open invoice. Cleaned corrupted separators from Jobs, Inbox, and opened conversations. Tightened the filter toolbar to keep Today, Upcoming, In progress, Review, Filters, and Start time visible together like the approved PNG, and moved filter and sort sheets below the Android status bar.
- Replaced direct invoice delivery with a Review & Send screen for Email, SMS,
or both, editable message text, attachment and payment-link choices, and a final destination confirmation. Combined delivery now records partial provider success safely, and unavailable scheduling is visibly disabled.
- Removed the duplicate native route header found during signed-Android visual
verification and tightened the invoice summary so long invoice numbers never push the balance beyond the phone edge. The disabled scheduled-send choice now uses a full-width row so its complete label remains readable.
Simpler self-service signup
- Reduced the new-account password minimum from 12 characters to 6 characters across both the signup form and server-side checkout validation.
- Routed successful paid-signup notifications to the verified Clean Estimate operator inbox.
Tenant-editable realtor email sequence
- Added a per-workspace copy editor for all four realtor outreach emails, including editable subjects and bodies, sample-data previews, merge-field references, individual save controls, and per-email default restoration.
- Connected saved tenant copy to future autopilot drafts while preserving already-created messages as the exact version previously reviewed.
- Protected only the sequence-critical data fields—the first email's plain-text price breakdown, the second and third emails' signed quote URL, and the fourth email's property identity—so customers can rewrite every sales sentence around them.
- Kept the physical business address and unsubscribe link outside the editable copy so customers cannot accidentally remove required compliance content.
- Added validation for unknown merge fields and unsafe removal of sequence-critical blocks.
Approved mobile CRM, estimating, jobs, billing, and More workspace
- Rebuilt Clients and Leads around compact, property-aware cards with search,
sorting, filters, lead numbers, full address context, assignment, value, and call/text actions. Hot leads now use real new-lead and estimate-engagement signals, while client detail keeps one identity block and the next job, open estimate, and balance within reach.
- Added a compact List/Pipeline switch with Today through Year periods, Hot and
Follow-up filters, pipeline and weighted value, close rate, slim stage cards, and real representative names and avatars.
- Consolidated native creation under the center Create action and made the job
and invoice prerequisites explicit. New-estimate setup now keeps the full property address, satellite imagery when available, first-save estimate numbering, collapsed detail sections, a default Suds Club offer for residential work, and native item-specific photo or map pricing tools.
- Refined mobile Jobs with compact status filters, property-first detail,
pinned sales notes, separate multi-truck progress, scope and resource cards, and linked invoice collection. Job detail now follows the approved compact status timeline, schedule/resources, collapsed-details, and expanded-scope hierarchy. Property imagery uses real coordinates or the complete address, falls from Street View to satellite, and never substitutes a sample image. Refined Invoices with slimmer rows, Unsent filtering, sent and due dates, linked job numbers, permission-aware detail actions, and a final payment confirmation.
- Updated field payment collection so Card, ACH, Check, and Other stay in the
standard row; Other expands to Cash and configured methods such as Zelle or financing. Check fields and the configured three tip choices plus no tip are shown only where they apply.
- Reorganized More around the direct Work rows and expandable Sales,
Operations & Pay, Growth, Business, and Account groups, with destination search and independent record permissions. Added a live Notifications center with All, Unread, and Needs action views, record links, and direct access to notification preferences. About now reports the installed native build, release channel, runtime, update source, and update check.
- Fixed signed Android About screens that could omit the native build number
even though the installed APK identity was available to the app.
- Pinned the Estimates sort control beside the scrollable status filters so
Recently viewed remains visible like the approved mobile reference.
- Corrected the mobile Clients fidelity gap by restoring the approved compact
bordered-card geometry, city-first address line, Residential or Commercial type badge, and right-aligned Call/Text controls. Recently active, Last contacted, and Name A–Z remain available from the collapsed sort control instead of permanently consuming a second toolbar row.
- Fixed the Android client-card surface so the border, avatar, customer details,
and right-edge actions stay in one horizontal native layout instead of falling back to an unbordered vertical stack.
Signup alerts and platform customer dashboard
- Added an immediate operator email after a paid customer workspace is successfully provisioned, including customer, plan, inbox capacity, Stripe references, and a direct dashboard link.
- Kept the notification tied to first-time workspace creation so Stripe webhook and browser retries cannot produce duplicate successful-signup alerts.
- Added a protected platform-owner dashboard with customer and subscription totals, estimated MRR, combined daily capacity, onboarding readiness, provider connections, inbox counts, tenant usage, signup dates, and direct Stripe customer links.
- Restricted cross-tenant dashboard access and navigation to an authenticated administrator whose email matches the configured operator allowlist; normal tenant administrators remain isolated to their own workspace.
Approved mobile Schedule, Inbox, and Phone refinements
- Matched Schedule more closely to the approved chat-generated PNGs with a
compact owner revenue summary, colored event accent bands, role-specific sales summaries, and a numbered technician route preview.
- Tightened Inbox cards around real directional activity: inbound age, the last
human or automated reply, estimate/invoice engagement age, estimate view count, and compact call/text actions. Newer document views no longer replace the customer's last actual message preview.
- Refined Phone activity cards with compact metadata spacing and explicit
green, amber, and red call-score numbers while preserving honest missing-score states.
- Rebuilt mobile Estimate detail around the approved property-first PNG: one
customer identity block, full address, Map/Satellite control, compact primary actions, collapsed estimate details, open photo line items, and a pinned total/follow-up/present-and-close bar. Estimate list cards retain the approved status-header band and now repeat their type beside the full address.
Live onboarding-call booking
- Connected the landing-page and completed-onboarding call-to-action buttons to the official 30-minute Google Calendar booking page.
- Opened booking in a separate tab so customers do not lose their completed workspace or setup state.
Automatic realtor email enrichment
- Added automatic enrichment for listing agents whose source record includes a phone number but no email, using the customer's existing Apify account.
- Exhausted structured email fields returned by the listing actor before falling back to a bounded public-business search.
- Required a full-name identity match, rejected generic office addresses, and verified the email domain; the system never guesses email patterns.
- Added a 30-day retry window for unresolved contacts, preserved the first-email approval gate, and surfaced attempted and found totals after a manual monitor run.
Quote-to-booking hero illustration
- Replaced the landing-page outreach-queue mockup with a concise price email and a positive realtor reply asking to book the work.
- Kept the visible $425 house wash, $175 driveway cleaning, and $600 total while removing the sequence tiles from the hero so the customer outcome is immediately clear.
Estimate approval queue and delivery controls
- Added an automated, bounded estimate-generation queue that turns eligible listing photos into editable draft estimates using each customer's connected OpenAI and Apify accounts.
- Added a clear Waiting for review queue, review and approved totals, and an explicit Approve for outreach action on every estimate.
- Blocked a property's first outreach email unless its estimate is explicitly approved. Editing an approved estimate before email one sends returns it to review; an already-started sequence continues normally.
- Added a per-workspace Send on weekends setting. Weekend delivery remains enabled by default, but customers can pause Saturday and Sunday sends without pausing listing research or estimate generation.
- Added Resend-backed operational alerts for failed monitor, estimate, and outreach jobs, while preserving detailed server logs.
- Removed the obsolete RentCast provider module and configuration; production listing research now uses only customer-funded Apify.
- Fixed live Apify listings being misclassified through demo owner enrichment. Imported agent contacts now remain realtor leads and flow into the estimate approval queue as intended.
- Allowed the queue to build draft estimates for phone-only listing-agent records. A valid email is still required at approval, and autopilot cannot target a contact without one.
- Removed the obsolete optional-approval checkbox from Admin. First-touch estimate approval is a mandatory safety gate and can no longer appear disabled while enforcement remains active.
Mobile QA coverage clarified
- Reframed the signed-app full-surface Maestro sweep as a shallow navigation
smoke test covering the six-tab shell, Schedule controls, the six Create types, and primary More destinations without creating drafts or performing business mutations.
- Made the dedicated read-only flows the authoritative evidence for
conversations, record details, payments, quote builders, roles, organization settings, approved workspaces, and offline behavior.
- Corrected the QA ledger so conditionally skipped Maestro branches are not
counted as verified coverage.
- Expanded the signed owner workforce check across Team, Team Timecards,
Availability & Time Off, and Training & Certifications. The read-only flow verifies each live surface without saving availability, reviewing requests, approving timecards, or changing shifts.
Residential builder navigation stability
- Fixed a mobile crash that could occur when a direct residential step link
was opened while an older builder step remained mounted. Only the visible route now synchronizes shared builder-step state, preventing the two screens from repeatedly overwriting each other.
- Added regression coverage for stacked direct-step navigation and the
photo-assisted measurement entry path.
Realtor Outreach landing-page message rewrite
- Repositioned Realtor Outreach as the AI email agent that finds listings, prices power-washing work from property photos, sends quotes, and follows up until a realtor replies.
- Rewrote the hero, four-email sequence, setup, ownership, pricing, ROI, onboarding, and final call-to-action sections around the contractor outcome rather than product specifications.
- Updated search metadata for power-washing companies and moved the 25-send-per-inbox detail into pricing, where it is framed as a deliverability benefit.
Approved mobile Home reconstruction
- Rebuilt the owner and office Home screen directly from the approved
chat-generated Home PNG instead of the legacy generic workspace-card system.
- Restored the distinctive white-and-blue hierarchy: greeting and workspace,
shared period controls, a blue live summary with the next appointment or job, Agenda, Hot Follow-Ups, pipeline and close rates, salesperson performance, and crew performance.
- Preserved live period-aware data, record navigation, role-specific sales
tasks, pull-to-refresh behavior, and the six-tab mobile navigation shell.
Self-service, multi-tenant Realtor Outreach launch
- Added paid self-service signup through the Clean Estimate Stripe account. A successful checkout creates an isolated customer workspace and sends the owner directly into guided onboarding; a 30-minute setup review is optional.
- Added tenant isolation for listings, contacts, pricing, estimates, provider credentials, sending inboxes, replies, and suppression data so multiple contractors can safely use the same production service.
- Added an authenticated Billing page with subscription state, monthly price, paid-versus-connected inboxes, sending capacity, additional-inbox changes, invoices, payment-method management, and cancellation through Stripe.
- Merged pricing into the landing page, added an interactive ROI calculator, and reframed customer-owned OpenAI, Apify, Gmail, estimate, and export data around Control your own data.
- Rebuilt the product story around automatic visual-AI property inspection, customer pricing rules, editable quote creation, and automated delivery from the contractor's own email address.
- Expanded Realtor Outreach from three touches to a four-email sequence: the text quote breakdown, the signed quote link, an editable-scope reminder, and a final close. Replies continue to suppress all remaining sends.
- Hardened tenant email boundaries so outreach and reply forwarding require the customer's verified inbox and complete business identity, with no fallback to platform email or operator contact details.
- Tied application access to Stripe subscription state, while preserving administrator access to Billing for recovery; billing mutations now require administrator role and same-origin requests.
- Made the post-checkout browser login handoff single-use, bound it to the exact pending signup, and added duplicate-subscription cleanup for repeated paid checkouts.
- Added a prominent direct link to Google App Passwords inside mailbox onboarding, with guidance for the 2-Step Verification and Google Workspace administrator prerequisites.
- Fixed listing-monitor and dashboard failures caused by serverless requests exhausting Supabase's 15-connection session pool. Production application traffic now uses transaction pooling with a one-connection-per-function limit.
- Removed database synchronization and legacy seeding from routine Vercel application builds now that the production migration is complete, preventing deployments from depending on the smaller session-mode connection pool.
- Added an expandable Admin preview of the exact four-email realtor sequence, including sample personalized scope, pricing, timing, quote-link behavior, sender details, and the automatic compliance footer.
- Corrected email three so it explains that scope and pricing are adjustable; only email four closes the sequence and asks for referrals.
- Consolidated Setup, Compliance, Billing, and Admin controls under one Admin header menu.
- Separated the manual listing-monitor action from outreach, improved provider-job logging, and surfaced the exact monitor error in Admin instead of the generic “failed” message.
- Replaced the obsolete deployment-level RentCast listing dependency with a tenant-funded Apify search-to-detail pipeline. The monitor rotates through saved ZIP codes, caps each run at 25 properties, and imports property details, photos, and listing-agent business contact data using the customer's verified Apify key.
- Fixed managed-sending readiness after onboarding so a verified tenant Gmail inbox and configured reply-tracking webhook are sufficient; the portal no longer waits for an unrelated deployment-level forwarding address.
Public Realtor Outreach landing and pricing pages
- Added a responsive public landing page explaining ZIP-based listing
prospecting, editable photo estimates, the four-email quote flow, reply tracking, customer-owned provider accounts, and sending controls.
- Added a transparent pricing page for the $50 monthly base plan and $10 per
additional inbox, including 1-, 4-, and 8-inbox capacity examples.
- Added a direct request-setup path for new prospects and kept customer login
separate for provisioned accounts.
- Changed the bare Realtor Outreach domain to open the public landing page
while keeping dashboard and API routes behind authentication.
Customer-owned OpenAI and Apify onboarding
- Added a required first onboarding step for customer-owned OpenAI and Apify
accounts, including direct signup, billing, and API-key links.
- Added server-side key validation and encrypted credential storage. Secrets
are cleared from browser state after every attempt and never returned by the API or displayed again.
- Removed runtime fallback to deployment-level OpenAI and Apify keys. Property
research, quote tests, and outreach automation remain paused until both customer connections are verified.
- Set BYOK pricing at $50 per month for the first inbox and $10 per month for
each additional inbox; provider usage is billed directly to the customer.
Guided onboarding, multi-inbox sending, and quote delivery
- Added a one-time Setup wizard covering the business profile, target ZIP
codes, estimate defaults, Gmail or Google Workspace connection, private test delivery, and a final controlled-launch review.
- Added encrypted customer mailbox storage and support for multiple verified
sending inboxes. Each inbox has an independent hard limit of 25 emails per day, adding another 25 sends of daily capacity when enabled.
- Added mailbox rotation, transactional capacity reservations, bounded
business-day send batches, per-inbox usage visibility, and reply forwarding back to the mailbox that originated each message.
- Changed the realtor sequence so email one contains the estimate's plain-text
service and price breakdown without a link. Email two introduces the signed link to the complete quote.
Customer-owned Gmail sender for Realtor Outreach
- Changed Realtor Outreach so outbound messages can be delivered directly by
the customer’s Gmail or Google Workspace mailbox. The customer’s address now appears in the From field instead of a Clean Estimate sender address.
- Hardened sender selection so a previously configured Clean Estimate delivery
address can never override the connected customer Gmail in the From field.
- Kept tracked replies and automatic sequence stopping by using the managed
Reply-To route, then forwarding the captured response into the same customer mailbox. Gmail connections remain paused until the mailbox-specific credential and delivery test are complete, and the 25-email daily ceiling remains enforced.
Mobile conversation, Home attention, and Phone polish
- Kept the approved six-tab navigation visible inside an opened mobile
conversation with Inbox selected. Added the centered Today divider and aligned calls, estimate views, and other system activity on one full-width rail while preserving inbound and outbound message alignment.
- Reserved Hot lead for real new-lead and recent or repeated estimate-view
activity. An unanswered thread without one of those signals now says Needs reply instead of being labeled hot.
- Reworked the mobile composer around an external Text/Email selector and a
working Internal note mode that saves team-only timeline entries. The paperclip and camera controls are visible, but correctly explain that media must currently be added from the related job or estimate record rather than claiming conversation upload support.
- Promoted the matching mobile message API to production so Internal note
is accepted as a team-only channel instead of being rejected by the older SMS/email-only validator. The signed Android persistence check now covers saving the note and reopening the conversation.
- Added a tappable schedule-conflict row to the owner and manager Home attention
section and an unread indicator on the Home Inbox bell when a conversation needs a response.
- Polished Phone with blue active controls, one divided summary card, uniform
white activity cards, conditional real call scores, and clearer Call versus Call Back labels without changing call routing or detail behavior.
Time-positioned mobile Schedule and physical-only routing
- Tightened the approved Week calendar so Monday through Friday fit together
on a 412-pixel phone canvas, while the weekend remains available by horizontal scroll. Empty unscheduled lanes now collapse, sales users keep a compact appointment summary, and technicians retain the route preview.
- Changed Day and Week into time-positioned calendars with visible open-hour
gaps, hour guides, an unscheduled follow-up lane, and a current-time line on today. Owner revenue, filters, role scope, and record navigation stay intact.
- Limited route construction to physical jobs and onsite lead or estimate
appointments. Phone calls, text follow-ups, virtual appointments, and other non-travel calendar items never become route stops.
- Made route readiness depend on a usable service address or saved coordinates.
The route card now reports physical and ready counts and uses an honest plain-language preview instead of a simulated map, distance, or travel time.
- Added call, text, and directions shortcuts to sales Day cards. Technician job
cards now show the full service address and a truthful Maps-ready or address-needed travel state.
Managed Realtor Outreach portal with native estimates and real replies
- Launched the dedicated Phase 1 portal at
realtor.cleanestimate.prowith an
isolated customer database, account login, company profile, target ZIP controls, and a hard ceiling of 25 emails per day per connected inbox.
- Added native photo-based estimates that remain independent of the main Clean
Estimate Pro app in Phase 1. Operators can edit customer and property data, service line items, pricing, discounts, notes, disclaimers, and status; share a signed read-only estimate; print it; or export CSV and JSON.
- Added real inbound reply capture, dashboard and lead visibility, read/archive
actions, inbox forwarding, automatic campaign exit on reply, and a follow-up task. New accounts now show a clear paused state and cannot record console messages as sent while the customer inbox is still being connected.
Composable mobile Inbox filters
- Matched the mobile Inbox filter sheet to the approved Filters panel in
clean-estimate-mobile-actionable-inbox-compact-v2-2026-07-31.png. Reply status, assignment, channel, engagement, last-outbound, and priority criteria can now be combined instead of competing for one filter slot.
- Added draft editing with explicit Reset and Apply filters actions,
an optional Save as a view name, and a numeric badge showing the active constraint count. The named configuration remains in the current Inbox screen state and is not presented as an organization-wide synced view.
- Kept every result grounded in production data: exact assignee ID, recorded
channel, persisted document-view timestamps, actual estimate view count, missed-call state, and known outbound classification. Missing metadata does not become a fabricated match, and the operational High-priority filter does not write a new CRM priority.
Correct local-time Home appointments
- Changed Home appointment cohorts to use the scheduled appointment date
instead of the lead or estimate creation timestamp. Today through Year now follow the organization's configured IANA timezone, including DST boundaries.
- Deduplicated a converted lead and linked estimate only when their appointment
date and start time match, while preserving distinct or unlinked appointments.
Truthful, period-aware technician Home
- Made the crew-lead and technician Home dashboard fully follow Today,
Week, Month, Quarter, and Year, including the summary, assigned jobs, section title, scheduled revenue, and empty-state copy.
- Replaced fabricated route-mile estimates with truthful assigned-stop and
address-ready counts. Cancelled jobs are excluded, while completed and invoiced jobs remain visible when scheduled inside the selected period.
- Added explicit assigned-work loading and unavailable states with retry, so a
slow or failed request is never presented as an empty day. My Pay remains visible only when backend organization and membership settings enable performance pay for that worker.
Chat-approved PNG reskin for the four primary mobile workspaces
- Rebuilt Home, Schedule, Inbox, and Phone from the individually
generated and approved PNG references. The production UI no longer treats the legacy field-operations canvas, Figma screens, or the previous live layout as its design source.
- Matched Home to the approved white-and-blue hierarchy, including the compact
role header, shared period selector, blue period summary, and a tappable next appointment or job inside the summary. The later owner, sales, and field content requirements remain connected to real role-scoped data.
- Matched Schedule to the approved full-width Day/Week control, centered date
navigation, owner revenue, truck/team/type filter sheet, color legend, compact time rail, horizontally scrollable week columns, and technician route card without changing record navigation or assignment rules.
- Matched Inbox and Phone to their approved high-density screens: about six
actionable conversation or call rows remain visible, document engagement and call scores stay readable, and Call, Call Back, Text, sorting, filtering, and conversation actions remain connected to production behavior.
- Tightened Inbox to the exact approved compact-card proportions, added the
approved needs-reply and missed-call row treatments, removed the old header ornament, and moved Sort and Filters into dimmed bottom sheets that preserve the visible conversation workspace.
Visible mobile calendar, permission-driven records, and quieter Home
- Kept the Schedule calendar picker visible beside the Day/Week workflow and
added a seven-day Week strip with event counts. Owners now see scheduled revenue on each day as well as in the period total; truck, technician/sales, type, color, and route behavior remains intact.
- Removed the legacy Home dashboard timeout card when the authenticated mobile
Home feed is still usable. An actually rejected session continues to show the explicit sign-in-required state.
- Made More record visibility follow effective backend permissions for every
role instead of hard-coding CRM and finance records off for crew and technician accounts. Owners can now reach the global Record Numbering editor from More > Pricing > Record Numbering.
- Kept the full lead number visible on compact CRM cards, suppressed raw
assignee UUIDs in Pipeline, and removed annual Suds Club totals from the two remaining legacy mobile quote-review paths so those views lead with the monthly membership charge.
Approved compact mobile Estimates workspace
- Matched the native Estimates list to the approved status-header-band design:
a centered title and open count, compact blue status filters, full service address, skinny rows, estimate type and total in the band, assigned-rep photo or initials, latest customer activity, and the contextual next action.
- Added a real Recently viewed sort backed by the persisted estimate view
timestamp, while retaining recently updated, highest total, and client-name choices. Accepted estimates are labeled Approved in the mobile filter.
- Fixed residential draft identity handoff so autosave returns both the saved
draft ID and its canonical estimate number. Review, Save as Draft confirmation, resume, and the Estimates list now show the same identifier.
Mobile CRM, in-person close, and Suds Club parity
- Tightened Clients, Leads, and Pipeline to preserve the approved compact-card
hierarchy while keeping an explicit lead number and the complete service address, including city/state. CRM retains Recently active, Last contacted, and Name A-Z sorts.
- Removed the repeated address from the estimate identity card because the
satellite/map property header already owns it. Send remains a distinct delivery action, while in-person review/sign and Present & close now say exactly that.
- Clarified that map measurement and AI Photo Assist apply to one selected
service item. The measurement header now shows the full street, city, state, and ZIP without changing any other item or price.
- Added a compact Recurring Plans callout for the four-visit Suds Club,
customer-facing monthly charge, and upgrade opportunity. Invoice, collection, expanded Other payment methods, configured tips, and More were reverified against their approved designs without needing behavior changes.
Quiet background sync without a global error banner
- Removed the persistent sync/data warning from the top of every signed-in
mobile screen. Offline work still syncs automatically through a headless background controller, while queue health, failed-item details, and manual retry controls remain available under More > Offline & Storage.
- Made failed-item acknowledgement non-destructive, so hiding a notice never
deletes unsynced work. Sync now waits for verified network reachability, clears stale error state after genuine recovery, catches background retry failures, and shows pipeline plus miscellaneous queue counts alongside proposals, deliveries, and photos.
Global record numbering and simpler QA identifiers
- Added Settings > Record Numbering so authorized workspace admins can set
the prefix, date segment, reset cadence, sequence width, and minimum next sequence independently for leads, estimates, jobs, and invoices. A live preview shows the resulting number before saving.
- Moved allocation to one atomic organization-wide database boundary, so
records created from web, mobile, APIs, imports, or automations use the same format without duplicate numbers during simultaneous creation.
- Reset the original Rolling Suds production-QA workspace to simple
oldest-first identifiers: L-1 through L-76, EST-1 through EST-303, JOB-1 through JOB-54, and INV-1 through INV-10. Internal UUIDs and record relationships were preserved, and each type now continues with its next sequential number.
- Treated sequence width as a minimum instead of a limit, so a one-digit
format continues from 9 to 10 without truncation. Invalid date/reset combinations are rejected before they can repeat a visible number.
Durable mobile sync warnings and cleaner core rows
- Made the sync-warning close action persist for the exact failed-item set
across tab changes and app restarts. A new or changed sync issue makes the warning visible again, while Home no longer repeats the global warning as a second card.
- Reorganized Home agenda rows so the record type, customer or job name, time,
service, and address remain readable without collisions on long entries.
- Removed repeated city, state, and ZIP text from Schedule cards and route
destinations when a legacy street field already contains the full address, including older city fields that store the city and state together.
Four-screen mobile approval refresh
- Published an Android OTA approval candidate for Home, Schedule,
Inbox, and Phone using the individually approved mobile screen designs. These screens now use one clean sans type system, tighter cards, reduced vertical padding, dark status-bar content on light screens, and no production bug-report overlay.
- Reorganized role dashboards so owners and managers can change Today, Week,
Month, Quarter, and Year once and review appointments, jobs, revenue, pipeline, close rates, salesperson performance, crew performance, and hot follow-ups on that period. Sales users see agenda and hot follow-ups first; field users see assigned jobs and enabled performance pay.
- Compacted Schedule into the approved field-service layout. The calendar
keeps role scope, day/week and date controls, collapsed truck/person/type filters, owner revenue, assigned routes, and purple lead, blue job, and yellow/orange task states without pushing the first event below the fold.
- Tightened Inbox cards and kept the approved latest-inbound and document-view
filters visible. Conversations that need a reply now surface Call Back and Text Back, and activity notifications align on one consistent rail.
- Tightened Phone activity cards so more calls remain visible and added the
call-score column and score badges while preserving permission-aware Quick dial, callback, transcript, and coaching behavior.
Reproducible protected Android builds
- Fixed the production EAS configuration bootstrap so a release can be
submitted before protected remote environment values are downloaded. The actual EAS builder still stops before native compilation unless the Android Maps key and validated Firebase configuration are mounted, and local release builds continue to verify the generated Maps manifest metadata.
Clearer field tracking and more reliable customer activity
- Added an Android canvassing disclosure before the native location prompt. It
explains active-shift-only background collection, who can review route data, the organization's retention period, and how flagged evidence is used.
- Restored reliable job proof uploads by provisioning a private
job-photos
bucket for standard jobs and individual truck runs, with active-organization access, a 10 MB limit, JPEG/PNG/WebP validation, and short-lived signed read URLs. Holiday mockups now use a scoped access URL that verifies the exact saved path before redirecting to a fresh signed URL.
- Bounded office-side holiday mockup generation before any AI image work. The
action now requires a signed-in teammate who can manage Holiday Lights or create or manage estimates, caps the request at 3.5 MB and the validated normalized PNG at 2 MB, and applies a trusted workspace-and-user burst limit that cannot be reset by changing networks. Mockups now honor the workspace AI enable switch and hard credit policy, atomically reserve credits before OpenAI work, use the current GPT Image 2 editor at explicit high quality, record image count, tokens, and estimated cost on completion, and safely replay or reconcile paid results without double generation.
- Hardened holiday mockup generation against cross-site requests. The paid
browser action now requires a verified CE Pro origin and JSON content type, rejecting cross-site or form-style submissions before rate limiting, credit reservation, or image-provider work.
- Made holiday mockup recovery durable across reloads and tab crashes with
workspace-and-teammate-scoped request identities. Ambiguous or empty paid provider results now show a deliberate Start New Generation action and a second-charge warning instead of looping a terminal retry. Completed images are staged before a deterministic private upload, so storage retries never regenerate the paid image or save a new inline data URL. Known staged usage and its credit reservation now remain pending across every AI request kind until the usage ledger commits, even after repeated reconciliation failures.
- Fixed proposal-viewed mobile pushes across residential estimates, direct
proposals, maintenance offers, holiday-lights quotes, fleet proposals, and commercial building proposals. Explicit source resolution separates current multi-option and legacy proposals; recipients must be active same-org members with live sign-in accounts, owner fallback remains available, and pushes now honor Proposal Viewed opt-out plus organization-timezone quiet hours. The post-response delivery call is now time-bounded and awaited so serverless cleanup cannot drop it, while notification outages remain non-blocking for the customer page. Internal delivery authorization now relies on Supabase's verified platform JWT and requires its service_role claim, preserving delivery across service-key rotation while rejecting anonymous, malformed, and ordinary signed-in user tokens.
- Prevented Next.js router and middleware prefetches from counting as customer
views. Commercial proposal pushes are now sent only when a same-workspace mobile estimate is verified as linked to the source proposal, so a notification cannot open an unrelated or nonexistent estimate.
- Fixed mobile Organization settings so owners and managers can load and
save the complete company address again. The mobile Street field now maps to the canonical organization address used by the web dashboard.
- Restored mobile organization-logo uploads by provisioning the missing
org-uploads bucket for the existing public logo URL contract. Uploads are limited to PNG, JPEG, or WebP files up to 5 MB and immutable logos/{workspace-id}/{uuid}.{extension} paths. Only active same-workspace owners or managers can insert, replace, or delete a logo; restrictive policies keep historical broad Storage rules from reopening cross-workspace writes. Public reads remain available because logos appear on unsigned proposals, email, and customer portal pages. Mobile now reads the selected logo through the native Expo file API and uploads verified bytes directly, avoiding the React Native blob conversion that caused device uploads to fail.
- Hardened organization-logo replacement with real PNG, JPEG, and WebP byte
validation and immutable workspace-scoped versioned paths. Mobile now saves the new public URL only after the server verifies its storage object, attempts to remove an unreferenced upload when the settings update fails, and retains superseded versions so historical emails and customer content keep working. It avoids stale cached branding, confirms when the replacement is live, and reports when the local organization profile could not refresh afterward.
- Verified the replacement flow on signed Android Build 63 with the native
picker and cropper. The new public PNG matched the selected 1080x1080 source byte-for-byte, the prior object remained available, and the saved logo survived a force-stop/cold reopen.
Atomic AI estimate-review token credits
- Aligned mobile estimate review with the shared AI token-credit pool. One
credit represents up to 1,000 combined provider input and output tokens for the month, rounded up across the workspace; purchased pack credits extend that same pool.
- Before contacting the provider, CE Pro conservatively reserves credits from
the exact prompt size plus the maximum possible response. Reservations are serialized per workspace and included in available-balance reporting, so simultaneous requests cannot collectively overspend a blocking limit.
- Added request-level idempotency and completed-response replay. Retrying the
same review request does not call the provider again or consume another credit, while reuse of that request ID for a different estimate is rejected.
- Staged successful provider results before final usage logging. If final
metering is temporarily unavailable, the completed request remains held for reconciliation. Successful finalization replaces the conservative hold with actual provider tokens without double charging.
- Honored the workspace AI limit policy: Block rejects work that would
exceed the pool, while Warn and Overage continue with tracked usage and report zero remaining included credits after the limit is reached.
- Extended the same atomic reservation, metering, and replay lifecycle to the
web estimate-review panel, so office reviews can no longer bypass the shared token-credit ledger or race past a blocking balance.
- Kept paid mobile results recoverable across screen changes and app restarts.
A zero displayed balance no longer disables a retry when the original review owns the pending hold, and ambiguous retries keep their durable request ID.
- Stabilized legacy retry identity around sorted canonical estimate fields
instead of mutable workspace history, and added provider message-overhead headroom to the conservative reservation ceiling.
- Made web review retries durable across reloads and tab crashes, preserved
paid replay recovery when provider configuration changes, and kept legacy no-ID clients on local checks when no provider is configured.
- Protected the cookie-authenticated web review action with same-origin
enforcement and a trusted workspace-and-user request limit before any paid provider work.
Offline cold starts, live mobile workspaces, and guarded releases
- Restored the last verified, user-scoped organization and membership when an
installed mobile session cold-starts without connectivity. Explicit sign-out or an account change clears the saved workspace, explicit permission denials never fall back to it, and the offline authorization expires after 24 hours.
- Connected every approved mobile workspace destination to live,
organization- and role-scoped data in read-only mode. Empty and failed requests remain explicit, and unfinished write operations do not expose simulated controls or records.
- Added Payment Collection to More as a stable route into the live
Payments & Transactions workspace while preserving the existing permission and production-safety rules for recording a payment.
- Moved the EAS workflow into the repository-level GitHub Actions directory.
Pushes now validate mobile changes only; native builds, non-production OTA updates, and store submission each require explicit manual release inputs.
- Made isolated EAS validation resolve the mobile Babel runtime explicitly,
cleared error-level repository lint blockers, and kept the Time Clock's elapsed-time refresh on scheduled state updates instead of impure render-time reads.
Reliable invoice status and payment collection
- Made mobile invoice Unsent and Overdue filters derive from send time,
due date, balance, and terminal payment state. Inconsistent imported rows now remain actionable and appear in the correct columns; zero-total unpaid drafts stay sendable instead of being mislabeled paid. Mobile also supplies its local calendar date so filter membership and row badges agree at midnight.
- Simplified the collection row to Card, ACH, and Other. Other now
expands to Cash, Check, Zelle, financing, bank transfer, and any additional workspace-enabled alternatives, while retaining three configured tip choices plus zero tip.
- Fixed the visible payment-screen back control to dismiss to the linked
invoice. It now works whether collection was entered from invoice detail or directly from the invoice list.
- Fixed Android header icon hit targets so the visible Back arrow and other
compact header controls receive the tap instead of their SVG artwork.
- Moved invoice and payment headers below the Android status bar and camera
cutout so system chrome no longer intercepts those header taps.
Exact mobile Inbox activity sorting
- Made Latest inbound and Our last message use independent customer and
team-message timestamps, even when a newer estimate view, invoice view, or system event is the card's current preview.
- Added Latest estimate/invoice view as one combined document-engagement
sort and kept the precise latest view time visible on compact customer cards.
- Locked system notifications in an opened conversation to the same full-width
timeline rail. The existing compact Phone cards and per-call score remain unchanged after verification against the approved design.
Recoverable residential pricing gate
- Replaced the dead-end Step 2 pricing lock with an explicit recovery panel.
Property details remain editable and auto-saved while verified organization pricing is unavailable; reps can retry the real price-book load or keep the draft and exit to Proposals.
- Services and totals remain locked until a current organization-scoped bundle
is verified. The app does not fall back to sample, expired, or cross-workspace rates, and a direct services-step route cannot expose a zero-dollar quote.
- Added Measure with map or photo to the numbered residential Property and
Services steps. The action enables only after a service is selected, then opens the existing satellite measurement and representative-verified AI Photo Assist without losing the in-progress estimate.
Reliable in-app account switching
- Fixed a mobile logout race that could let a second account begin signing in
while the previous account's Supabase logout and scoped cleanup were still running. Password sign-in now waits for that active logout to settle, so technicians, owners, managers, and sales users can switch accounts in the same app process without a schema error or a force-stop.
- Made Android Back follow tab history. Opening Jobs, Invoices, Estimates, or
another record workspace from More now returns to More instead of unexpectedly jumping to Home or leaving the app.
- Stopped the mobile tab shell from briefly falling back to a sales workspace
while an authenticated membership is loading or being cleared during sign-out. The transition now stays neutral until the real role is resolved.
Readable mobile workspace filters
- Fixed selected period and filter labels disappearing against a white chip in
the shared Analytics, Payments, and Recurring mobile workspace. Selected chips now use a pale-blue surface, dark-blue text, a blue border, and the existing screen-reader selected state.
Connected mobile Workforce navigation
- Added compact, role-aware navigation to every native Workforce screen.
Owners and managers can now move from Team to Timecards, Availability, and Training without hidden deep links; field users retain Time Clock and see My Pay only when their role and Crew Performance Pay configuration allow it. Timecard rows continue to open the individual Review Timesheet workflow.
Single mobile estimate detail header
- Removed the duplicate native navigator bar from estimate detail. The screen
now keeps its approved in-screen Estimate title and back action without a second Estimate Details header stacked above it.
Live mobile Workforce, Time, and Crew Pay
- Added category-specific native screens for the owner team directory, team
timecards, technician Time Clock, timesheet review, availability and time off, training and certifications, and technician My Pay.
- Added bearer-authenticated, organization-scoped Workforce APIs with
effective team permissions. Clock timestamps and transitions are server-authoritative, concurrency-locked, and idempotent; timesheet approval is blocked while a shift remains open.
- Reused the production Crew Pay policy and completed-job calculator for
employee earnings. Mobile labels the result as calculated pay and never claims that payroll has been settled.
- Added honest empty and error states throughout. Missing workforce,
certification, time-off, or pay data never produces preview records.
Live, production-safe mobile Payments workspace
- Connected all seven approved mobile Payments & Transactions screens to live,
organization-scoped ledger and open-invoice data for owners and managers with View Billing permission.
- Added bounded transaction periods and status filters plus honest views for
refund eligibility, failed payments, financing metadata, and disputes. Empty provider states never create sample applications, disputes, or money records.
- Kept refund, void, split-payment, credit-memo, retry, financing, and dispute
writes disabled until durable provider-safe operations are available, and tightened manual mobile collection to require Manage Billing and access to the linked customer record.
Live, permission-safe mobile Analytics
- Connected the seven approved mobile Analytics screens to live,
organization-scoped reporting for owners and managers with View Analytics permission.
- Added shared Today, Week, Month, Quarter, and Year periods
plus report-specific filters for lead source, salesperson, crew, service, and Suds Club analysis.
- Reused the production lead-source ROI, sales-cycle, revenue, operations, and
schedule calculations and added read-only Suds Club growth and four-visit utilization reporting. Empty and failed periods stay explicit and never display preview people, businesses, or revenue.
Multi-truck mobile schedule coverage
- Updated the tenant-scoped schedule API to attach every truck-run assignment
to one job event. Multi-truck work now stays a single calendar item, so job counts and daily revenue are not duplicated.
- Expanded field-role schedule scoping to match any assigned run crew or truck,
including secondary trucks, while preserving the original assignment fields for legacy single-truck jobs.
- Made owner and manager truck and technician/crew filters match every run on a
job. Compact event cards summarize four assigned trucks as Truck 1 +3 while the filter catalog retains all four truck and crew names.
2026-08-01
Representative-verified AI photo quoting
- Added AI photo-assisted measurement to the native residential Measure
screen for one selected service item at a time, including decks, patios, roofs, driveways, fences, gutters, windows, and other price-book services.
- Added a bearer-authenticated, organization-scoped mobile vision endpoint
with AI plus estimate view/create permissions, strict image and response validation, durable request idempotency, credit reservation, and per-user rate limits. Images are resized on device and are not accepted by URL.
- Showed the AI measurement basis, low-to-high range, confidence, visible
observations, and limitations without calculating or committing a price. The representative must choose the suggestion, verify or edit the quantity, and confirm it before Apply to estimate enables the existing price-book calculation.
- Kept offline, unconfigured-provider, credit-limit, unsafe-image, and provider
failure states honest: manual measurement remains available and no fabricated fallback range is substituted.
Consistent Fusion mobile settings and client loading
- Reworked Profile, Pricing Defaults, Tax Rates, and About around
the approved Fusion mobile canvas, typography, compact bordered panels, and shared loading/error states without changing their data or actions.
- Replaced the client-detail page's legacy gray spinner with the standard
Fusion loading card so the CRM workspace no longer changes visual systems while a live customer record is being fetched.
Bearer-authenticated mobile AI estimate review
- Moved mobile AI-review credits and estimate review off the browser-only
admin route and onto a bearer-authenticated, organization-scoped mobile API.
- Required both AI and estimate-view permissions, validated canonical
persisted estimate IDs, and kept every review lookup inside the organization selected by the signed-in mobile session.
- Wired the saved estimate ID into mobile pricing and pre-send review. Offline,
unsynced, unconfigured-provider, and provider-failure results are now labeled Local checks and do not masquerade as an AI-generated review.
- Added a static mobile-client endpoint inventory test. Every direct or dynamic
mobileApi* operation must now be registered in the bearer-secured mobile OpenAPI contract or appear in the small, reasoned legacy-route allowlist.
Stable mobile routes and current full-app QA
- Removed the unreachable standalone mobile Map tab. The approved six-tab
shell remains Home, Schedule, Create, Inbox, Phone, and More; daily job routing stays inside Schedule.
- Standardized residential estimate navigation on one canonical mobile route
family. Older nested links now redirect into the same estimator while preserving the estimate draft, edit mode, step, and customer prefill.
- Expanded the signed-app regression sweep to cover all six tabs, Schedule
day/week controls, non-mutating job controls, More destinations, Canvass, build identity, and the residential Estimate, Suds Club, and Close Kit flow.
Permission-safe mobile Workforce, Create, and Search
- Split the Workforce catalog into management and employee access. Crew leads
and technicians can reach their own Time Clock, availability, training, and eligible My Pay destinations without inheriting the owner Team directory, team timecards, or timesheet approval screen.
- Replaced hardcoded office-role checks in the center Create screen and global
Quick Add with a server-resolved capability contract based on the active workspace's effective permission overrides. Disabled and failed access checks now fail closed, and the matching Client, Lead, and Estimate APIs enforce the same permission requirements.
- Made global Search permission-aware. Default crew and technician accounts can
search only their assigned jobs, roles with no searchable permission receive a clear unavailable state, and permission-check failures are distinct from an empty result set.
- Removed the Messages filter from global Search because conversation results
are not implemented there yet; message search remains available in Inbox.
Live, production-safe mobile Suds Club workspace
- Connected More > Recurring Plans & Suds Club to a bearer-authenticated,
organization-scoped mobile API backed by maintenance plans and their actual occurrences. The app no longer tries to send a mobile token to an admin browser-cookie route.
- Added live Plans, Visits, and Exceptions segments with All,
Active, and Needs attention filters. Plan cards show the customer, completed-visit count, current status, term, and monthly charge when the plan bills monthly; visit cards show their real schedule, job link, amount, and operating status.
- Kept the first mobile release read-only. Scheduling, pausing, billing, and
other plan changes stay in the web workspace until their permission-gated mobile write APIs ship.
- Changed unfinished approved-workspace catalog pages to a clear **not
available in mobile yet** state. They no longer display preview metrics, simulated switches, or action buttons that could be mistaken for live production controls.
Approved mobile Estimates list and detail
- Updated the live mobile Estimates route—not a parallel preview—with compact
status-header cards and complete, wrapping service addresses.
- Reworked estimate detail around the approved property-first flow: a
satellite/map header, one customer identity, collapsed estimate metadata, an open line-item breakdown, and scrolled sections for deposit, payments, signatures, attachments, and notes.
- Kept Send estimate distinct from Open Close Kit. Send delivers the
customer link, while Close Kit guides an in-person review, signature, scheduling, and payment.
- Preserved per-item photo display in the line-item breakdown and paired each
residential measurement photo with the selected service-specific map/photo measurement entry.
Clear mobile navigation around bug reporting
- Kept the floating mobile bug-report button above the persistent tab bar on
job and other detail routes, so it no longer intercepts taps intended for More or another bottom-navigation destination.
- Made the mobile payment collector's back control return directly to its
invoice detail instead of becoming a no-op when route history is unavailable.
- Removed duplicate navigator headers from mobile Job, Invoice, Client, and
client-account details so each screen has one clear title and back control.
Permission-aware mobile Phone tab
- Kept Phone in the same fifth tab position for owners, managers, sales
representatives, crew leads, and technicians while making its contents obey the workspace's effective phone permission overrides.
- Accounts without View Phone Activity now see a clear administrator-controlled
access-off state. The app does not request call history or deep-linked call detail for those accounts.
- Accounts with phone viewing but without Place Phone Calls can still review
permitted activity and call intelligence, while Quick dial and every callback action are omitted.
- Gated secure callback, in-app calling, and Android background voice registration
before the API boundary, so a disabled account never requests a call or voice token endpoint. Crew-lead and technician overrides now work on the protected call activity and detail APIs as well.
Independent multi-truck job execution
- Jobs can now be assigned to one, four, or more trucks from the job dispatch
assignment. Each selected truck receives an independent field run tied to that truck and crew.
- Each run stores its own start, completion, progress, proof, notes, and audit
history. One crew starting work no longer starts every crew on the job.
- Overall job status and progress aggregate across all truck runs. Completion
and post-job automation occur once, after the final assigned truck finishes.
- Added tenant-safe execution checks and retry-idempotent transition requests,
while preserving the existing primary-truck fields for single-truck jobs and older integrations.
- Hardened the production backfill for older completed jobs that have no stored
completion timestamp, and replaced the legacy cross-table appointment trigger on jobs with a row-type-safe job trigger so normal job updates cannot look for lead-only appointment fields.
- Moved sales notes into a prominent Read before start card above mobile
execution controls.
- Corrected that card to read the actual sales handoff field instead of only
the separate crew-notes field. When both exist, the app now shows both with clear labels before any truck can be started.
Clear Suds Club upgrade on every residential quote
- Added a clear Suds Club upgrade offer to every residential quote across the
mobile Close Kit, hosted estimate, customer portal, proposal PDF, and delivery email, including quotes where the membership has not been selected yet.
- Standardized customer-facing membership language on **four scheduled visits
per year** and a single monthly charge. Annual totals, signup amounts, final payments, pricing floors, and commissions remain hidden from customers.
- Preserved annual and payment-schedule values internally for configuration,
calculation, and accounting without changing the approved package definitions.
Safer mobile SMS and email replies
- Email-only conversations now open with Email selected and can be replied
to without requiring a phone number. SMS remains unavailable until the customer has a valid phone number.
- The mobile composer sends only the recipient for the selected channel. The
API verifies that recipient against the organization-scoped customer thread and rejects unsupported channels, malformed recipients, mismatches, and estimates from another workspace.
- Outbound email now records provider message IDs in both communication and
unified-message history without adding a duplicate optimistic timeline row.
Configurable mobile payment collection
- Added Settings > Payment Collection for owners and managers to configure
exactly three customer-facing tip choices as percentages or flat amounts. No tip remains available as the fourth, zero-value choice.
- Added workspace-level alternate payment method settings. Mobile keeps Card,
ACH, and Other in the standard view. Other expands to Cash, Check, and configured choices such as Zelle, financing, bank transfer, Venmo, Cash App, or PayPal.
- Wired the saved configuration into invoice and job collection screens. The
payment APIs calculate against the authoritative taxable amount and reject stale, disabled, or altered tip and alternate-method submissions.
- Added safe rollout defaults of 10%, 15%, and 20% plus the existing common
alternate methods for workspaces that have not saved their settings yet.
Correct manager scope and private-pay visibility
- Home and Schedule now give managers the same organization-wide operational
view as owners, including work assigned to other representatives and crews, while sales and field users remain assignment-scoped.
- Added manager all-work calendar filters and daily revenue without changing
the shared six-tab navigation.
- My Pay now appears only for crew leads and technicians when the
organization enables crew performance pay and the active membership uses commission pay. Hourly and disabled configurations also fail closed on a direct My Pay link.
- Manager pipeline commission totals now require an authorized active Red Line
configuration; unresolved or disabled configurations hide the private total and explanatory note.
Reliable mobile Home during backend rollouts
- Fixed an Android Home crash that could occur when a newly deployed dashboard
endpoint returned a successful but incomplete response.
- Home now normalizes the live dashboard response at the API boundary, keeps
the authenticated account and selected workspace identity, preserves valid activity rows, and uses empty collections only for fields the response omitted.
- Added nested presentation guards and regression coverage for empty responses,
missing users, missing organizations, and malformed activity collections.
- Hardened the shared mobile API transport so only valid JSON responses with a
JSON media type count as successful API data. HTML protection pages and malformed JSON returned with HTTP 200 now become retryable connection errors without retaining their response body; legitimate HTTP 204 responses remain supported without body parsing.
Safer Android phone startup
- Advanced the native mobile runtime to 1.1.4 so the Expo SDK 55 React
Native patch, Firebase validation, and the current Fusion regression fixes ship in a newly signed binary instead of being applied across the older 1.1.3 native boundary by OTA.
- Aligned the signed Android runtime with Expo SDK 55's supported React Native
0.83.10 patch and refreshed the native dependency lockfile. Production builds now reject a Firebase configuration that does not contain the com.cleanestimate.pro Android client instead of producing an APK with broken incoming-call registration.
- Fixed an Android startup crash that could occur when a signed preview was
built without initialized Firebase messaging and a voice-enabled Twilio workspace tried to register for incoming calls.
- The app now checks Firebase Cloud Messaging through Expo's guarded token path
before invoking Twilio's native registration. An incompletely configured APK stays open, marks native calling unavailable, and keeps the secure company-line callback available.
- A valid
google-services.jsonis still required in the named EAS build
environment for push notifications and native incoming calls. Adding that native configuration requires a new APK, not only an over-the-air update.
Fusion mobile workspace overhaul
- Standardized mobile navigation on six destinations: Home, Schedule,
Create, Inbox, Phone, and More. Role and backend permissions now change the information inside the workspace without rearranging the primary tabs.
- Rebuilt Home around role-specific priorities. Owners and managers can compare
appointments, jobs, close rates, pipeline, revenue, salesperson performance, crew performance, and hot follow-ups across Today, Week, Month, Quarter, and Year. Sales reps see today's agenda before hot follow-ups, tasks, pipeline, and personal performance. Technicians see today's jobs and their own enabled performance pay.
- Reworked Schedule into a home-service calendar with purple leads, blue jobs,
yellow normal-priority follow-ups, and orange high-priority follow-ups. Added truck, technician/salesperson, record-type, day/week, and month-picker filters. Owners can see all work and daily revenue; other roles start on assigned work.
- Added native Task/Event creation from the center Create tab. Authorized
office users can choose the follow-up type and priority, assign a teammate, associate a customer, set the due date and time, and add notes without leaving the app.
- Tightened Client, Lead, Estimate, Job, Invoice, Task, Inbox, and Phone cards
so more records fit on a phone without removing record numbers, addresses, dates, assignments, totals, engagement, next actions, or call scores.
- Split Clients and Leads into dedicated More destinations, added name/newest/
last-contacted client sorting, and kept the approved lead number, full street address, and city/state visible on the slimmer Pipeline cards.
- Added true Latest inbound and Our last message Inbox sorting while
preserving the last estimate or invoice view time on compact conversation cards.
- Expanded More into role-permitted CRM, recurring plans and Suds Club,
operations, inventory and purchasing, expenses, analytics, team, security, administration, and account workspaces.
Native mobile Generic Quote
- Replaced the Generic Quote browser handoff with a native customer-first flow.
Reps confirm customer information and the full service address, then add live price-book or custom service, product, and equipment items before review.
- Added quantity, unit price, descriptions, taxable controls, tax totals,
customer message, offline-first save, and native resume from Home or Proposals.
- Kept Asphalt Maintenance on its existing specialty web builder.
- Added the dedicated authenticated Generic Quote sync contract. Saved mobile
quotes now queue from the device, sync atomically into linked proposal and estimate records, recheck price-book items and totals on the server, and preserve revision checks for safe edits. Staff can review and send the customer-facing quote from the shared estimate after it syncs.
Authenticated customer account
- Added a mobile-first, passwordless customer account with Home,
Appointments, Messages, Payments, and Account navigation.
- Added property-scoped service history, documents and signatures, invoice and
receipt history, editable customer contact details, and notification preferences.
- Kept every account query inside the resolved workspace and property. When an
email address matches more than one provider, the account withholds details instead of combining customer records across businesses.
Customer appointment change requests
- Added a signed-in customer workflow for requesting a future appointment date
or cancellation without changing the scheduled job immediately.
- Added a permission-protected review queue under Admin > Tasks. Staff can
approve a request after confirmation or decline it with a required reason.
- Approved requests update the job, route placement, and audit history
together. If the job changed after the customer submitted the request, CE Pro stops approval for staff review instead of overwriting the newer schedule. Repeated decisions are safe and do not apply twice.
Standalone Christmas Lights designer pilot
- Set the pilot price book to $9 per foot for C9 lighting, $50 per mini-light
strand, and wreath pricing of $50, $225, $485, and $900 for the 24-, 36-, 48-, and 60-inch sizes.
- Added an isolated Christmas-light quote smoke test that uses a random Apify
listing and sends package pricing only to the operator's test inbox.
- Added separate Good, Better, and Best lighting mockups to emailed test quotes
so each price is paired with its corresponding design.
- Corrected Apify's Zillow gallery URL expansion so the designer can use all
valid photos returned for a listing instead of only the hero image.
- Added a hard automatic-design cutoff at 4,000 square feet. Larger properties
are routed to manual review before vision analysis or customer delivery.
- Changed pilot property selection to prefer detached homes near 2,000 square
feet. A low-cost vision pass now ranks eligible properties and their complete Apify galleries, rejecting aerial, interior, obstructed, or overly complex views before the precision roofline trace runs.
- Added a $1,000 seasonal service minimum. The quote still measures C9 work at
the configured per-foot rate internally, but customer package pricing never drops below the contractor's minimum.
- Reworked generated designs around exact roof-edge traces and bounded decor
zones. Trees, bushes, columns, wreaths, and pathway lights now fit the visible objects, and walkway bulbs follow both edges instead of a generic center line.
- Normalized main-ridge selections to the full installable main-roof span. Short
center-only fragments are rejected or expanded, while real obstructions use separate installable segments on each side.
- Split the designer into Edit and Finished modes. Edit alone shows
tracing, measurements, handles, and guide bulbs; Finished shows the saved photorealistic Good, Better, and Best package images without design guides.
- The finish pipeline creates one winter background, runs a strict masked
gpt-image-2 finish for each package, and clips edits on the client to the package's allowed scope. It stores final PNGs in R2, using a data fallback only in local or no-storage environments.
- Customer proposals now display the selected package's saved Finished image,
and the Hillcrest property is the default test demo.
- Improved proposal and designer lighting fidelity. C9 bulbs now use small
white-hot cores with subtle bloom and spill, while mini lights use dense, irregular placement based on saved strand counts. Every render remains locked to the exact deterministic scope of its Good, Better, or Best package.
- Added a send-time guard that stops the isolated email test when the Good tier
cannot satisfy its configured package band after the service minimum.
- Fixed exact-address test quotes so a missing Apify result stops for manual
review instead of falling back to a random listing. Reviewed image overrides must match the requested street and ZIP before a proposal can be emailed.
- Added a separate test app for turning a property address or uploaded photo into
a measured Christmas light design. Google Maps and Apify lookups are limited to explicitly approved test mode, with upload and manual-drawing fallbacks.
- Rebuilt roof-line editing around primary and secondary segments. Each section
has its own measurement and can be verified, changed, moved, or deleted before the design advances.
- Added price-book-driven Good, Better, and Best packages, plus wreath, bush,
tree, column, garland, pathway, spacing, and contractor-approved color rules.
- Added contractor settings for price-book rates, approved colors, C9 spacing,
deposit percentage, business copy, and lease or sale defaults.
- Added saved winter-night concepts and a customer proposal flow for package
selection, color-change requests, signature, and an adjustable deposit.
- Kept the pilot separate from the current production Holiday Lights module. The
Clean Estimate Pro data, proposal, and payment bridge remains a future rollout requirement before the legacy holiday-light workflow can be retired.
2026-07-31
Baltimore Workiz connection
- Added a Lancaster/Baltimore branch selector before importing a Workiz lead or job into the commercial estimator.
- Saves the selected branch with the quote and carries it into the maintenance proposal builder.
- Routes maintenance-plan pushback through the matching branch API connection.
- Keeps Lancaster as the default for older quotes that do not have a saved branch.
Vision-assisted realtor quotes
- Connected the Rolling Suds realtor outreach workflow to CleanEstimatePro draft estimates.
- Added opt-in Apify Zillow photo enrichment for listings whose RentCast record has no gallery. Results are exact-address matched, limited to one listing and eight photos by default, cached to control spend, and never used to import agent contact data.
- Listing-photo analysis can identify exterior condition, surface measurements, and material types for house washing, roofs, driveways, gutters, windows, decks, patios, and fences.
- Added an inspection panel on each outreach lead so the photo analysis and exact customer quote can be reviewed together.
- Live
POST /api/v1/estimatesresponses now include a CleanEstimatePro-generated signedview_url, so integrations can deliver the quote without copying the platform's customer-token signing secret. - Estimate creation uses a stable idempotency key and lets CleanEstimatePro reuse an existing client by email, preventing duplicate quotes and duplicate realtor records during retries.
- Fixed the app login middleware so the scheduled quote worker can reach its own
CRON_SECRETbearer check instead of being redirected to the login page. - Added a one-lead production smoke mode that exercises Apify, vision analysis, and quote creation while explicitly skipping the listing monitor and outreach sender.
- Added an isolated random-property email test that picks from a capped set of live Lancaster for-sale results and sends the photo assessment and signed draft quote to a fixed operator inbox without contacting the listing agent or changing outreach state.
- Switched listing-photo analysis to the lower-cost
gpt-5.6-lunavision model with low reasoning and high-detail image input. The isolated email test can accept a matching operator-reviewed Apify property assessment, while scheduled outreach cannot use that override. - Fixed the isolated quote-email test so a vision provider error or empty assessment fails the run instead of reporting success without creating or emailing a quote.
- Fixed Rolling Suds estimate creation to use CleanEstimatePro's registered
apisource value, preventing the live estimates constraint from rejecting photo-quote requests while retaining the creating API key for traceability. - Versioned the Rolling Suds idempotency-key namespace after the request-source correction, allowing the corrected payload to proceed without weakening CleanEstimatePro's protection against reusing a key with a different request body.
- Fixed listing-photo analysis so visible serviceable patios, decks, hardscapes, and fences are inventoried even when they look relatively clean. Those surfaces now reach the draft quote instead of being dropped solely because obvious staining is absent.
- Rolling Suds quote idempotency now fingerprints the canonical request body: an identical retry reuses its estimate, while a genuine scope revision such as adding a patio creates a new draft without an idempotency conflict.
- Rewrote the realtor outreach into a property-specific three-touch sequence: deliver a courtesy photo quote on day 0, invite questions by call or text on day 4, and close the note on day 8.
- Removed the permission gate, generic follow-up filler, referral pressure, fake urgency, and roof or insurance pressure from the first email. The opening now explains how clean siding and windows strengthen the buyer's first impression when they arrive at the home.
- Added priority-scheduling copy to the first and second realtor touches so agents know an active listing can move to the front of the schedule once the seller is ready.
- Added one referral request to the final realtor touch so the quote emails stay focused while the closing note gives agents a simple way to share Rolling Suds with another agent or seller.
- Added quote-required sending. First-property and new-listing emails run the photo analysis and CleanEstimatePro quote first, then skip delivery when the photos cannot support a reliable quote.
- Kept new-listing quotes behind the same four-day contact cooldown and documented that opt-outs, bounces, and complaints stop delivery while mailbox replies still require an operator pause.
Freeform building detection area
- Added a freeform boundary before automatic commercial building detection.
- Detection now returns only mapped buildings whose centers fall inside the drawn shape, which keeps nearby blocks out of dense-property searches.
- Added finish, undo-point, clear, redraw, and draggable-corner controls, and clears old results whenever the boundary changes.
- Requires a completed search area before detection so the visible map is no longer treated as the search boundary.
Multi-building commercial estimates and LiDAR height
- Added a building roster for apartment and HOA properties with 5–20 separate buildings.
- Added map highlighting, select-all, batch story and floor-height changes, and setup copying for similar buildings.
- Added USGS 3DEP LiDAR estimates for eave height and roof peak, with the source dataset and confidence shown for each building.
- Kept every result out of pricing until a rep accepts it. The batch apply action uses only medium- and high-confidence estimates.
- Saved the LiDAR review with each building so it reopens with the quote and map outlines.
Commercial quote to maintenance plan
- Connected the Rolling Suds commercial estimator to the maintenance proposal builder by saved quote number.
- Carries the final customer price, scope, optional work, quote number, and linked Workiz record into the program builder.
- Added an explicit action to push the selected maintenance plan back to the same Workiz lead or job description without changing its status, schedule, or customer fields.
- Kept the two existing permanent app URLs and the PDF/manual proposal workflow.
- Workiz import and push-back use the v2 bearer-key search, record, and update endpoints.
Residential measurement opens at the property
- Fixed mobile Measure so estimates started from a lead with an address but
no saved coordinates center the satellite map on that property instead of opening at the default map location.
- Replaced the Android native address lookup with the authenticated CE Pro map
service, which does not require phone location permission to locate the customer property.
- Fixed map taps being discarded when an estimate arrived without saved
coordinates. Measurement points can now be placed as soon as the service address resolves, and the screen shows a clear loading or address error instead of failing silently.
Quieter mobile background sync
- Removed routine “Syncing saved proposals” and completion banners while
mobile changes upload normally in the background.
- Kept offline and action-required sync notices visible so saved work and
failures are still clear.
Clearer mobile lead details
- Grouped lead actions, customer information, estimates and payments, recent messages, and notes under consistent section headings.
- Stacked appointment and deposit summaries so long dates, time windows, and amounts remain readable on narrow phones.
2026-07-30
Bug reporting from every mobile screen
- Added a floating bug-report button to every signed-in mobile staff screen.
The form automatically attaches the current screen, device, build, OTA update, recent navigation, app errors, and failed requests.
- Mobile reports use the same bug inbox, intake scoring, screenshot storage,
GitHub issue sync, webhook, and manager alerts as the web app.
- Reports save on the phone when offline or during a temporary server outage
and send after the same user reconnects to the same workspace.
- Trusted owner workspaces can start a hosted Codex repair workflow immediately
from accepted native-app reports. The workflow investigates, tests, and opens a reviewable PR without relying on an office computer or self-hosted runner.
- Added per-bug locking, a 10-minute recovery sweep, visible started, PR,
needs-review, and failed states, and manual retry for failures. Other reporters still require approval before report text can trigger a code-writing agent.
- Kept the offline queue native-only so mobile web rendering and OTA exports
do not try to initialize phone storage.
- Upgraded approved owner reports from PR-only handoff to guarded automatic
release. Repairs now update the docs, pass the complete application, mobile, database, and documentation gates, merge into main, and trigger the Vercel production deployment. Mobile changes also publish an iOS and Android Expo production OTA.
- Updated the runner's GitHub scripting action to the current Node 24 release
so repair runs no longer carry the retired Node 20 warning.
- Fixed native bug reports returning Unauthorized. The creation endpoint
now recognizes the mobile bearer session and selected workspace while preserving browser-cookie authentication for the web bug inbox.
- Enabled the repository permission required for the repair runner to open its
pull request, and isolated the documentation checkout from application lint and typecheck so verified repairs can continue through merge and deployment.
- Fixed Vercel blocking automated production releases by attributing repair
commits to the connected repository owner instead of an unmapped bot.
Sync warning now clears
- Fixed the mobile Needs attention header so an old sync error no longer
keeps it visible after all queued work has cleared.
- Prevented the same failed item from being counted twice when it appears in
both the saved queue and the active sync engine.
- Added a close button that hides the current warning without deleting saved
work. A new or changed sync issue shows the warning again.
Simpler mobile proposal details
- Replaced the proposal action grid with one state-aware Next step button,
a short action list, and a collapsed More options section.
- Condensed proposal progress, delivery, payment, and customer details so the
page is easier to scan on a phone.
- Kept calling, texting, follow-up, resend, editing, scheduling, duplication,
customer approval, signature, and payment actions available.
Clearer language across web and mobile
- Rewrote labels, helper text, empty states, errors, estimate screens, and
customer approval screens in plain language.
- Removed sales pressure, unsupported social proof, vague filler, and internal
implementation terms from customer-facing text.
- Renamed Close Kit actions to match the task: Estimate options, **Back to
representative, Choose a date, Pay deposit, and Appointment confirmed**.
- Kept workflows, pricing, data handling, and API behavior unchanged.
Mobile layouts now stay inside usable phone space
- Hardened shared mobile screens, cards, estimate builders, modals, and bottom
sheets so their frames remain within the device's usable width and height.
- Added compact layouts for narrow phones: crowded field groups and builder
totals/actions stack instead of overflowing, while headers use less vertical space without clipping their titles or controls.
- Kept pinned navigation, save, approval, measurement, Suds Club, and Close Kit
actions above Android system navigation and the iPhone home indicator.
- Limited custom selection and approval sheets to the safe screen height so
both their close controls and final actions remain reachable.
Consistent estimate builders across web and mobile
- Corrected estimate routing so Holiday Lights, Generic Quote, and Asphalt
Maintenance records open their own editor from the Estimates table and from client detail instead of falling into the residential editor.
- Duplicate now creates Generic and Asphalt drafts inside the matching
builder while leaving the original estimate unchanged.
- Mobile Edit estimate now reopens Generic and Asphalt estimates in their
live web builder. New mobile handoffs preserve the selected customer, property address, lead source, and known property size.
- The mobile estimate picker labels the Fusion residential builder as a
single-screen flow when Fusion is enabled.
- Commercial, Fleet, and Holiday Lights drafts now finish loading before their
builder is displayed or autosaved, preventing another draft's data from flashing or being written into the requested record.
One mobile residential estimate editor
- Unified residential estimate creation, draft resuming, and estimate editing
on the Fusion single-screen estimator whenever Fusion is enabled.
- Removed the route that sent Edit estimate and resumed drafts into the
older five-step interface after they were created in Fusion.
- Existing estimates are loaded before the editor is shown so another draft's
customer information cannot flash while the requested estimate opens.
Stable mobile estimator input and Close Kit controls
- Kept residential measurement and pricing fields mounted while totals
recalculate, preventing Android from switching keyboards or moving focus to another field while a representative enters numbers.
- Raised the customer-facing Review & sign action above the phone's system
navigation area so it remains visible and tappable on Android devices.
Integrated mobile service pricing and rep commission plans
- Combined mobile residential service selection, measurements, sold pricing,
and Red Line floor details into one vertical card per selected service.
- Added per-window counts and cleaning options, walkway pricing by linear foot
or square foot, approved custom services, ad-hoc manager line items, and editable custom quantities and rates.
- Added salesperson commission plans to Team administration. Each rep can use
percentage or flat-rate commission with separate company-generated and self-generated amounts for residential and commercial work.
- Residential web and mobile estimates now select the assigned rep's plan from
the lead-source category, save that commission on the estimate, and use the saved amount on the Commissions dashboard.
Mobile property autofill and field estimate layout
- Connected the Fusion residential mobile estimator to the same authenticated
property lookup used by the web experience. Available square footage, stories, lot size, and year-built data now fill automatically, with derived roof area and gutter length applied to property-priced services.
- Carried known lead square footage into new mobile estimates and kept every
property value editable when lookup data is unavailable.
- Replaced the horizontal service strip with a vertical service list and clear
+ and - controls.
- Removed the duplicate route header and kept pinned Save and Close Kit
actions above iPhone and Android system navigation areas.
Visible customer appointment confirmations
- Added a prominent green check and CONFIRMED label to the Leads list,
Jobs list, individual lead, and individual job screens after a customer confirms the current appointment.
- Extended the same confirmation state into the native mobile Clients lead
rows, lead detail, Jobs list, Dispatch inbox, and job detail so office, sales, and field users see one consistent status.
- The indicator uses the recorded link or SMS confirmation state and
disappears automatically when an appointment is rescheduled from web or mobile.
Complete automation list
- Fixed the Automations page so it loads every non-archived workflow instead
of silently stopping after the first 25.
- The All, Active, Paused, and Drafts tab totals now describe the
complete workflow list shown on the page.
Rescaled automation run charts
- Resized the workflow Runs Per Day chart to a compact, responsive
dashboard height.
- Prevented chart grid lines and date labels from stretching when the overview
is viewed on a wide screen.
Faster automation discovery
- Added automation search and an automation-type filter.
- Added sortable name, category, trigger, status, runs, success-rate, and
last-run columns.
- Added 10, 25, and 100 row options with previous/next pagination and visible
result counts.
Booked appointment confirmations and reminder cadence
- Updated the lead-booked and job-booked automations to send SMS and email
within about one minute of scheduling, then one week, three days, 24 hours, and two hours before the appointment.
- Messages now show the full arrival window and explain that the sales rep or
crew may arrive anytime inside it.
- Added a secure Confirm appointment button and support for replying
CONFIRM by SMS. Rescheduling clears the previous confirmation.
- Made all five timing stages visible in both booked workflows: immediate,
one week, three days, 24 hours, and day-of. Each stage shows its own SMS and email actions in the workflow editor.
Clearer automation editing
- Renamed the canvas Wizard action to Guided Editor and clarified that
it is the step-by-step Trigger, Conditions, Actions, and Review editor.
- Rebuilt Auto Arrange so ordinary steps form a straight vertical flow and
TRUE/FALSE condition paths form a stable, ordered tree.
- Replaced stepped visual-builder connectors with direct straight lines, so
aligned cards and condition branches no longer show small jagged bends.
- Replaced raw reminder minute totals with number-and-unit controls for
minutes, hours, and days. Condition cards now display values such as 7 days before appointment instead of 10080.
- Added labeled Insert live data field pickers to canvas and guided SMS,
email subject, email body, note, call, and voicemail fields. Appointment date, arrival window, and confirmation-link fields are included.
2026-07-29
Native mobile time selection and reliable scheduled lead creation
- Replaced the mobile time grid with the phone's standard time control.
Android now opens the system clock dialog and iPhone uses the Apple time spinner wherever the shared mobile time field is used. See Create and Schedule Leads on Mobile.
- Fixed Quick Add scheduled leads failing with Unable to create lead.
Legacy estimate appointments are now saved as supported site visits through both the authenticated mobile API and its connection fallback.
- Added traceable server diagnostics for lead-creation failures without
recording customer form data in production logs.
Lower Suds Club monthly pricing
- Reduced every Rolling Suds of Lancaster Suds Club base tier by $10 per month:
$189, $209, $229, $249, $269, and $289.
- Updated the public price tables, first-service amounts, annual totals,
estimating defaults, and live Stripe checkout links for new memberships.
- Existing Stripe subscriptions retain their contracted pricing.
Suds Club service update
- Removed moisture-meter maintenance from the Suds Club pricing catalog and
the Rolling Suds of Lancaster public membership pages so it cannot be added to new plans or advertised as an included service.
Slimmer quote-form text consent
- Shortened the optional Rolling Suds text-message checkbox to a compact
statement covering quote updates, service updates, and occasional offers.
- Kept the full messaging disclosures on the linked Privacy Policy and Terms
of Service pages and versioned the revised consent language for lead records.
Mobile quote form and address autocomplete
- Fixed the Lancaster hero form so typed values display in dark text instead of
appearing invisible against white input backgrounds.
- Added Google-powered address autocomplete to both homepage quote forms and
the public SMS-consent form while preserving manual address entry.
Central Pennsylvania service area
- Expanded the Lancaster homepage positioning and service-area structured data
to reflect coverage across Central Pennsylvania, including Reading, Harrisburg, York, Carlisle, Lebanon, Mechanicsburg, and Camp Hill.
- Aligned the commercial, residential, specialty-service, Suds Club, quote,
about, and local landing pages with the broader Central Pennsylvania service area while preserving city-specific copy on local search pages.
Faster Lancaster homepage lead capture and sharing
- Added the complete quote form to the homepage hero while keeping the existing
lower-page form.
- Added a Get Quote button to the mobile and desktop header that jumps to
the hero quote form.
- Added a branded favicon across the Lancaster site's public pages.
- Added a dedicated 1200×630 social-sharing image and complete Open Graph and
X/Twitter card metadata for the Lancaster homepage.
Clearer Lancaster quote-form requirements
- Added an asterisk beside every required quote-form field.
- Made phone number required and email optional on both public quote forms.
- Aligned server-side lead validation with the visible required fields: full
name, phone number, and property address.
Simpler Lancaster website quote consent
- Replaced the three quote-form checkboxes with one small, optional SMS-consent
checkbox that covers informational, transactional, and recurring marketing texts.
- Kept text-message consent unchecked and optional. Customers can submit a
quote without selecting it, while the Terms of Service and Privacy Policy acknowledgment now appears below the submit button.
- Updated the lead notification to record the combined consent choice and its
disclosure version, and aligned the public SMS, privacy, and terms pages with the simplified form.
2026-07-28
Faster web, admin, customer, and mobile page loads
- Made navigation feel quicker across the entire website, including public,
admin, and customer pages. Large menus now wait for pointer, keyboard, or touch intent before preloading a destination instead of fetching every linked page during the current page load.
- Deferred optional tracking until after the page is interactive and added
consistent loading states so route changes respond immediately while their required data finishes loading.
- Split optional Sales Workspace and customer-estimate panels out of their
initial JavaScript bundles. Sales quote configuration now streams behind the ready workspace shell and reads existing lead-source defaults without triggering tenant-bootstrap writes. Public estimate redirects happen before residential offer hydration or other nonessential reads; remaining organization-scoped reads and offer hydration run in parallel, canonical acceptance reuses the authorized estimate and organization result instead of repeating those reads, and independent holiday-light design images render concurrently.
- Removed duplicate admin-shell work. Desktop and mobile navigation plus
Command Center now share tenant-scoped live alert and unread-message state instead of opening overlapping reads, polls, and realtime subscriptions.
- Shortened the Command Center critical path by loading data for the signed-in
role. Sales and crew users no longer wait for office-only dashboard metrics before their workspace appears.
- Made mobile startup and tab changes smoother by connecting data refreshes to
app focus, screen focus, and network state. Background or inactive screens pause periodic API work, while non-critical voice and notification setup waits until the initial app interaction is ready.
- Reduced bursts against the mobile API by sharing identical tenant-context
authorization lookups only while they are concurrently in flight. The key includes the exact bearer token and selected organization, failures are not retained, and no tenant can reuse another tenant's authorization result.
- Hardened mobile workspace selection for the upcoming SaaS rollout. A
malformed organization header or an explicit organization without an active membership now fails closed instead of silently selecting a different tenant; only stale server-owned account metadata can recover to another membership the same user already owns.
- Kept the performance work ready for Clean Estimate's SaaS rollout: page data,
live feeds, API deduplication, and server caches remain isolated by selected organization rather than using a global cross-tenant cache.
Bulk annual residential re-quotes
- Added a Re-quote bulk action to the Estimate workbench for standard
residential customers. Teams can select up to 100 prior estimates, choose a target year, and apply a percentage increase such as 3%.
- Each renewal receives a new estimate number and keeps the customer,
property, service scope, proposal options, maintenance choices, assignment, and CRM links while leaving the original estimate unchanged.
- Added draft-first and optional immediate-send modes. Immediate delivery uses
the existing estimate email lifecycle; quotes with missing email addresses or delivery errors remain drafts and are identified in the batch result.
- Customer-facing prices receive the selected increase while internal redline
and commission values remain unchanged.
- Added source-estimate, target-year, and markup tracking with database-level
duplicate protection, so retrying the same renewal batch safely skips a residential renewal that already exists for that year.
Bulk holiday-light seasonal re-quotes
- Added a bulk Re-Quote for New Season workflow to the Holiday Lights
estimate list. Teams can select up to 100 prior-season quotes, choose the target season, and apply a percentage increase such as 3% without rebuilding each customer quote.
- Renewal quotes now preserve the customer, property, measurements, lighting
design, saved media, and contract selection while leaving the historical quote and internal material and labor costs unchanged.
- Added draft-first and explicit Create & Send Renewals modes. Missing-email
records remain drafts, delivery warnings are reported per batch, and the completion summary separates drafts, sent quotes, skipped duplicates, and failures.
- Added source-quote, season, and markup tracking with database-level duplicate
protection so retrying a batch cannot create two renewals from the same quote for the same season.
Canvassing handoff reliability hotfix
- Removed the redundant nested native navigator that could crash Android when
I'm at the door, a lead's New estimate, or another direct estimate handoff opened a builder. Residential, commercial, fleet, and holiday-light estimates now render inside the existing proposal navigator.
- Hardened Expo Router's native stack adapter for production navigation states
that omit the optional preloaded-route list. Direct estimate entry now treats that missing list as empty instead of crashing before the first step.
- Flattened every estimate builder into one stable Estimates navigator,
removing the redundant builder-only stacks while keeping Estimate routes grouped behind the normal tab instead of exposing internal routes as tabs.
- Hardened React Navigation tab and stack rehydration when an OTA changes the
nested estimate route manifest. A stale partial state now rebuilds missing route and preloaded-route lists from the current manifest instead of crashing on .find or .filter.
- Added a stable top-level residential estimate entry route for lead,
pre-knock, canvass, manager-map, and estimate-type actions. These workflows now continue into the builder after OTA state recovery instead of safely falling back to the Estimates list.
- Added live US phone formatting to canvass customer details. Ten-digit entry
is shown progressively as 555-555-5555 while the representative types.
- Fixed mobile workspace scoping for organizations whose valid Postgres UUID
does not contain RFC version bits. Canvassing, leads, estimates, and other mobile API calls now stay pinned to the workspace selected in the app.
- Prevented door saves from waiting indefinitely for a fresh GPS fix. The app
uses a short bounded location attempt, falls back to a recent device location, and saves without coordinates for manager review when neither is available.
- Made Save + quote and Save + lead navigate as soon as the server
confirms the door and CRM records. Shift cleanup and map refreshes continue in the background instead of holding the next screen open.
- Changed I'm at the door on the pre-knock brief to open the lead-linked
native residential estimate builder directly.
Faster native canvassing dispositions
- Replaced the long-press-first missing-house workflow with a single tap on an
empty territory map point. A connected house opens its disposition control as soon as the address resolves.
- Added a compact, horizontally swipeable quick-disposition control. Reps can
record no-contact outcomes such as No Answer, Not Home, and Vacant with one tap, then move immediately to the next eligible door.
- Added Conversation / details for outcomes that need contact information,
notes, services, follow-up, or appointment details.
- Replaced typed, comma-separated Services discussed entry with a fast
multi-select picker for common work such as House Wash, Window Cleaning, Driveway, Patio, Deck, and Gutter Cleaning. Selected services carry directly into the native estimate handoff.
- Added selectable Quote Sent and Sold canvass outcomes. Both require a
confirmed customer, create or match the Door Knocking client and lead, and preserve the signed-in rep assignment.
- Replaced blank-looking placeholder-only customer inputs with permanently
visible Customer name, Phone, and Email labels. Save + lead now opens the created or matched lead in the native app, while Save + quote opens the estimate builder.
- Made satellite imagery the default representative canvass map and added a
layers control that switches instantly between satellite and standard maps.
- Applied the same satellite default and visible Map / Satellite switch to
the owner and manager team map. Manager-entered Save + lead outcomes now open the saved native lead, and Save + quote opens the in-app residential builder with its client, lead, address, and canvassing attribution attached.
- Fixed existing-lead scheduling when the lead had no prior appointment type.
The editor now uses the supported Site visit default instead of sending an invalid hidden value that caused Unable to update lead.
- Preserved historical lead sources in the mobile editor even when that source
is no longer offered for new leads. Scheduling or editing other fields no longer fails merely because the unchanged source or assignee is inactive.
- Added Save + quote for qualified doors. The visit creates or matches the
client and lead with Door Knocking as the source, assigns the signed-in canvasser as the sales rep, and opens the native residential estimate builder with the saved canvassing attribution.
- Raised the compact and expanded disposition action areas above Android and
iOS system navigation insets so Save + next and Save + quote remain fully tappable.
Native canvass campaign management
- Added a native Campaigns workspace for owners and managers directly
inside the mobile Canvass stack. Campaigns can now be created as active work or drafts and edited without opening the admin website.
- Added native editing for campaign name, offer, description, start date, and
end date. Both dates use the shared in-app calendar, and invalid end-before- start windows are blocked on the device and server.
- Added manager-safe campaign lifecycle controls for activate, pause, resume,
complete, reopen, archive, and restore-to-draft. Existing territory completion and active-shift safeguards remain enforced by the server.
- Added campaign progress cards with territory counts and visited-door totals.
The manager team map now provides Campaigns access in its header and an active-campaign selector, so a newly created campaign can receive its first drawn territory immediately from mobile.
- Rebuilt mobile territory drawing as one manager workflow: select the active
campaign, tap points on the map, undo or clear points, name the territory, select one or more reps, and save the boundary plus assignments together. Newly drawn and previously unassigned territories remain visible to managers, and an initial assignment failure cleans up the new boundary instead of leaving hidden territory data behind.
- Hardened territory map input so an Android map press without geographic
coordinates is safely ignored instead of crashing the Canvass screen.
- Fixed the manager map layout so the territory name, representative choices,
and Save button scroll fully into view beneath the fixed map.
- Added operational house management to the mobile manager territory map.
Managers can now see color-coded houses and recent knock activity, review attempt history, record or update a disposition, and tap an empty point inside the boundary to add and immediately work a missing house. Territory cards show total, knocked, unvisited, and do-not-knock counts.
- Fixed manager-entered knock outcomes so the app automatically opens and
closes the validated canvassing session required by the visit API. Managers can now disposition a territory house without first starting a rep shift.
- Added native territory boundary editing. Managers can select an existing
territory, trace a natural replacement outline with one continuous finger drag, undo or clear points, and save the new shape without losing assigned reps, houses, attempts, or dispositions.
2026-07-27
In-app calendar and time selection across mobile workflows
- Replaced manual date and 24-hour time typing with a reusable in-app calendar
and readable time selector across lead creation and editing, proposal next actions, canvassing follow-ups and appointments, active-job rescheduling, and invoice due-date editing.
- Date fields now show a familiar localized date, open at the selected month,
support previous/next month navigation, mark today and the selected day, and save the same canonical YYYY-MM-DD value expected by the API.
- Time fields now show 12-hour, easy-to-scan labels and selectable 15-minute
increments while continuing to save canonical 24-hour values. This removes formatting guesswork without changing existing schedule data.
Usable native estimate editing and sending
- Repaired the shared native estimate-builder footer after Android could expose
an invisible but tappable navigation control. Next, Back, and Review Estimate now render as explicit, high-contrast actions beside a compact per-visit summary. The footer no longer stacks pricing and duplicate safe-area spacing into a panel that consumes roughly a quarter of the estimate screen.
- Repaired the shared Android button surface used by estimate Review. **Send
Estimate, Present on device, and Save as Draft** now render their full button backgrounds and borders instead of leaving only an invisible tap target or unframed label.
- Changed Edit estimate to open the existing native builder at its first
editable step instead of dropping the user on the final review screen. Residential, commercial, fleet, and holiday-lights editors now retain the draft ID and edit mode while moving between steps.
- Added an explicit Edit estimate details action on residential Review and
restored the original estimate number while editing an existing record.
- Removed the redundant Delivery confirmation checkbox from residential
customer entry and Review. Customers have already authorized contact before entering the workspace, so a valid selected email or SMS destination can now send without a second confirmation. The delivery service uses the same rule and no longer blocks an otherwise valid send because an older estimate lacks the retired confirmation field.
- Fixed delayed draft hydration on Review so saved email and SMS destinations
become selected when the existing estimate finishes loading. Send Estimate now becomes available without forcing the user to re-enter contact details.
- Accepted Supabase's offset-formatted, microsecond-precision revision
timestamps during residential updates. Existing estimates can now pass optimistic locking and send instead of stopping with an Invalid ISO datetime review error.
- Restored the current idempotent delivery service in production so a provider-
accepted send records its delivery attempt and atomically marks the linked proposal and estimate Sent.
- Allowed canonical PostgreSQL UUIDs used by long-lived seeded workspaces in
the delivery service and repaired the production Twilio credentials. SMS estimate links now reach Twilio's correct Messages endpoint.
- Fixed the mobile result check that could hide a failed current delivery after
the estimate's synced timestamp advanced. The app now shows the delivery service's specific error for review and displays Sent only after the provider accepts the current send. The carrier's own final verdict stays where it is recorded — the provider's messaging logs — instead of being claimed prematurely here.
- Moved estimate SMS delivery to the Clean Estimate company line already
registered for U.S. application messaging. This prevents Twilio 30034 rejections from the retired unregistered sender.
Native estimate editing from mobile detail
- Replaced the mobile estimate detail page's admin-website handoff with the
existing native residential, commercial, fleet, or holiday-lights builder. Edit estimate now restores the synced builder data on the device and opens the correct builder without leaving Clean Estimate.
- Preserved the proposal and estimate server revisions in the restored draft
so saving edits updates the same estimate with conflict protection instead of creating a duplicate record.
- Corrected canonical estimate-ID resolution for records opened by their
linked mobile proposal ID. Payment totals, customer presentation, delivery history, and editor data now resolve against the same estimate.
Complete mobile lead intake and connected record navigation
- Rebuilt mobile lead intake around the required customer relationship. Typing
a customer name now searches existing CRM customers, selecting a result carries its contact and property data into the lead, and Create new customer saves the customer and lead together when there is no match.
- Replaced free-text lead source entry with the workspace source list and an
inline Add new source action. Lead creation also supports assigned or unassigned ownership plus scheduled or unscheduled intake with date, time, and appointment notes.
- Reduced address-search delay, cached repeated searches, compacted results,
and removed redundant country text while preserving automatic street, city, state, and ZIP filling.
- Added native lead editing, assignment, scheduling, rescheduling, property
changes, notes, customer-profile navigation, and estimate creation from the saved lead page.
- Connected customer, lead, estimate, job, and invoice detail pages. Customer
profiles now show lead, proposal, job, and invoice history; downstream records link back to the customer; and estimate Back returns to the record that opened it instead of always replacing navigation with the proposal list.
- Added mobile job rescheduling with server-side authorization and completed
job protection. Job detail now links directly to its customer, estimate, and invoice.
- Added an invoice header with reliable Back navigation and native editing for
due date and notes. Estimate detail now provides customer-profile and full estimate-editor actions.
Customer-threaded mobile Inbox, faster creation, and recorded in-app calls
- Grouped calls, voicemail, texts, emails, and estimate views into one mobile
Inbox card per matched customer. The card shows the latest event and total activity count; approvals remain separate System work.
- Made the customer name on an Inbox card open the linked CRM profile while the
card itself opens the complete communication thread.
- Kept legacy estimate-linked profile resolution to one indexed estimate lookup
instead of repeated customer phone/email wildcard queries, preventing the grouped mobile Inbox from timing out while resolving CRM links.
- Tightened customer cards into an approximately 80-pixel, three-line layout so
about seven conversations fit on a standard Android Inbox while borders, full-width message/activity previews, activity counts, and compact Call/Text actions keep each customer useful and distinct.
- Replaced the Inbox call modal with a safe-area-aware, scrollable bottom sheet
so company-line and device-dialer actions stay above Android navigation. Tapping Call on an Inbox customer now opens that confirmation sheet instead of starting an outbound call immediately.
- Compacted the Phone tab Quick dial area and repaired the Android estimate
picker so card surfaces, accent rails, padding, descriptions, and actions no longer collapse or clip at the left edge.
- Stabilized shared primary-button surfaces on Android so the blue company-call
confirmation and other colored estimate or close-flow actions remain visible instead of rendering as white text on a blank background.
- Added a floating Quick Add launcher on primary office pages for leads,
customers, estimates, accepted-estimate jobs, and job-based invoices.
- Stabilized the native customer-thread entrance so Android keeps the filter
strip at its finished height while history loads instead of briefly stretching the strip and then jumping into place. Threads now render from the newest item immediately instead of visibly scrolling through older history after opening.
- Made the customer name inside an opened mobile Inbox conversation link to
the matched client profile, formatted US numbers as XXX-XXX-XXXX across Inbox and Phone surfaces, and lifted the call-sheet actions farther above Android system navigation.
- Unified Quick Add around one foreground blue floating + in its original
position above the tab bar on every primary office page, including both Home route variants and the native tab-group root used by Android. Every launcher now opens the same five actions for a lead, customer, estimate, job, or invoice.
- Routed future Android in-app inbound and outbound calls through Twilio
dual-channel recording when workspace recording is enabled. The recording disclosure and completed-recording callback now feed the existing automatic transcript, coaching, next-step, and follow-up-task pipeline. Calls made before this fix without a recording cannot be transcribed retroactively.
Mobile call transcripts, coaching, and follow-up work
- Made every mobile Phone activity card open a complete Call intelligence
detail page without interfering with its separate Call back and Text actions.
- Added speaker-separated call and voicemail transcripts, overall and
category AI scores, sentiment, outcome, evidence-based strengths, coaching opportunities, manager calibration and notes, recommended next steps, and follow-up message drafts.
- Added both follow-up work created specifically from the call and pending CRM
tasks related through the same client or lead, including due dates, priorities, and assignees.
- Enforced the existing phone visibility boundary on the new detail API:
owners and authorized managers can review team calls, while sales representatives can open only their own calls and assigned related work. Provider recording URLs and raw private call metadata remain server-side.
- Added the call-detail operation to the mobile API contract and expanded the
release QA inventory to 69 surfaces with transcript, coaching, empty-state, task-association, direct-route, and privacy checks.
- Corrected issues found during installed-app OTA QA so metadata-backed calls
keep the same customer name on detail and an empty missed call says Call transcript instead of being mislabeled as voicemail.
Mobile Phone tab and clearer Inbox activity
- Added a permanent Phone tab for owners, managers, and sales
representatives. It includes company-number readiness, Quick dial, calls today, missed-call, voicemail, and talk-time totals plus filters for all, inbound, outbound, missed, and voicemail activity.
- Connected the mobile Phone tab to the durable, organization-scoped
phone_calls history used by the web Phone workspace. Owners and authorized managers can review team activity while sales representatives remain limited to their own calls.
- Added one-tap Call back through the company line and Text handoff from
every recent-call card. Incoming Android calls continue to appear from any mobile screen; the Phone tab does not need to be open.
- Redesigned the mobile Inbox list so every message, approval, and estimate
view is a separate high-contrast card with a colored edge, stronger border and shadow, type label, timestamp, alert state, and divided quick-action area. This prevents adjacent customer activity from visually running together.
- Corrected Android card and action rendering found during production-device
QA so Inbox activity rows, Start company call, Text, Call back, and Reply by text keep their intended backgrounds, borders, colored edges, and tap feedback.
- Added the phone-activity read operation to the published mobile API contract
and expanded the mobile QA inventory to cover the Phone workspace.
Android company phone in 1.1.2
- Added a native Android softphone to customer, lead, proposal, account,
pre-knock, Inbox, and client-list call actions. Authorized users can call directly inside Clean Estimate while customers see the workspace Twilio number, with connecting/ringing/connected state plus mute and end controls.
- Added inbound company-call delivery for active Android office users whose
role includes Make calls. Incoming calls show the QA/customer number with Answer and Decline controls, support background and terminated-app delivery through FCM, and stop ringing other devices after the first user answers.
- Preserved the production safety net: unanswered app calls fall through after
18 seconds to the saved office forwarding number and then workspace voicemail. A mobile recipient lookup failure also uses the existing fallback instead of taking the company phone line down.
- Fixed inbound recipient selection for workspaces with more than ten
call-capable memberships. CE Pro now records mobile voice presence when an Android user registers and prioritizes the most recently registered authorized devices, instead of letting older inactive test or staff memberships consume Twilio's ten-recipient ring limit.
- Added authenticated, organization-scoped Twilio access tokens and outbound
instructions, effective-role authorization, record-scoped call logging, registration refresh before token expiry, webhook signature validation, and retry-safe inbound completion events.
- Kept the secure two-step callback for mobile web, normal web, and Android
devices where native registration is unavailable. The personal device dialer remains a clearly labeled fallback.
- Bumped the native app to 1.1.2. This release requires a newly signed APK
because Twilio Voice and Firebase call notifications add native Android code; it cannot be installed into a 1.1.1 binary by OTA and is not available in Expo Go.
- Fixed the Android release check so EAS can inspect and submit the project
through an ignored local Firebase-file path before the EAS-hosted secret is mounted, while the remote production builder still stops if that required file is missing.
- Added a repository-level, source-only EAS archive policy so the web project,
local dependencies, generated Android output, tests, and credentials are excluded from signed mobile builds while the shared pricing/payment package remains available to Metro.
- Expanded the mobile QA ledger to all 67 current surfaces and added direct
outbound, inbound, background/killed delivery, role isolation, first-answer wins, registration refresh, forwarding, voicemail, notification, and microphone test coverage.
Mobile business records and invoices
- Replaced the ambiguous office-user Me tab with More and added a
permanent Business section for Clients & Leads, Estimates, Jobs, and Invoices.
- Added native, organization-scoped invoice search, status filters, balances,
totals, line items, notes, and links back to the related client, job, and estimate. Eligible office users can now create a draft invoice directly from a job while preserving its customer, line items, discount, tax, total, and existing payment balance.
- Updated the office Jobs workspace to show the complete job list across all
statuses instead of only the next few active field runs.
- Kept these business destinations visible for owners, managers, and sales
representatives independently of optional scheduling and payment feature flags. Crew roles retain their restricted field-only record access.
- Made desktop invoice dates and overdue-day calculations deterministic between
the server and browser, eliminating the invoice-list hydration warning found during the post-release production pass.
- Added the mobile invoice list, detail, and create operations to the published
mobile OpenAPI contract so the shipped API and its integration reference stay in sync.
2026-07-26
Production QA and field-access hardening
- Scoped crew-lead and technician browser job lists, direct job URLs, and
global search results to work assigned to that user or their active crew. Field job detail is now a read-only operational view that omits pricing, payments, compensation, internal office notes, and management controls.
- Removed organization-wide Clients, Leads, recurring Job Templates, and
Invoices from the default crew-lead and technician browser workspace so unrelated customer and financial records do not bypass the field-safe job view. Recurring-template list, detail, and option APIs now enforce the same job-management boundary.
- Fixed mobile notification preferences to load and save through the
authenticated mobile API instead of relying on a profile column that is not present in every production workspace.
- Fixed accepted-estimate job handoff so an estimate without a real appointment
creates an Unscheduled job instead of silently assigning today. Service quantities, units, details, and source metadata now carry into the job.
- Bumped the native app version to 1.1.1, corrected the production legal
links, and restricted organization, pricing, and tax settings to owners and managers.
- Fixed native post-login routing so crew leads and technicians open directly
on assigned Jobs instead of briefly loading the office dashboard and showing a permission error.
- Fixed the installed Android Map tab crash by packaging the protected
Google Maps credential into native release builds. Release packaging now stops with a clear configuration error if that credential is unavailable.
Option A/B/C proposals across every estimate type
- Added one shared proposal-option builder for residential, generic, asphalt,
fleet, commercial building, holiday lights, and source-linked maintenance proposals.
- Teams can add, duplicate, delete, rename, recommend, and reorder up to 12
options. Every option supports its own description, line items, quantities, units, prices, discount, tax, deposit, and server-calculated total.
- Updated customer web proposals, emails, and PDFs to show all active choices
in order and require exactly one selection before signature.
- Replaced the legacy Custom/Premium-only acceptance contract with stable
arbitrary option keys and canonical child estimates. Jobs, invoices, payments, and acceptance automations now inherit the selected option rather than combining every alternative.
- Added revision history, stale-review protection, accepted/signed locking, and
immutable acceptance snapshots so sent proposal changes remain auditable.
- Added proposal-option launch actions to each estimate review workflow and
automatic linked-estimate preparation for fleet, commercial building, and holiday lights proposals.
Twilio company-line calling
- Added live, call-type-specific scripts for representatives. The Phone dialer
now lets a rep choose the conversation type before calling, and a persistent guide opens with personalized talking points, step navigation, completion tracking, progress, and a minimize mode. Active inbound calls surface the default inbound guide automatically. Owners and managers can create, edit, enable, and direction-scope scripts and select inbound/outbound defaults in Phone Intelligence settings.
- Expanded Phone into a complete call-intelligence and coaching system. Owners
can build weighted custom scorecards, set passing and low-score thresholds, configure automatic follow-up tasks and message drafts, and customize the inbound recording disclosure. A retry-safe queue now processes reviews and records failed attempts instead of relying on the original webhook request.
- Added a 90-day Coaching dashboard with rep averages, category performance,
score movement, recent trends, and a manager review queue. Every reviewed call now has a detail page with secure recording playback, customer and rep context, speaker-labeled transcript, evidence-based scoring, follow-up draft, private manager notes, Reviewed/Coached/Acknowledged workflow, JSON export, manager score calibration that feeds team trends while preserving the original AI score, and controlled recording deletion.
- Added dedicated phone permissions for activity, calling, coaching, recordings,
and settings. Sales reps are scoped to assigned calls by default, while managers and owners can review the team. Privacy controls now redact common sensitive data before AI review and apply separate recording/transcript retention periods with a daily cleanup job.
- Added automatic AI call review for recorded customer conversations. Clean
Estimate now creates a speaker-aware transcript, scores rapport, discovery, clarity, objection handling, next steps, and professionalism, and gives reps evidence-based strengths, coaching opportunities, customer sentiment, call outcome, and recommended follow-up. The Phone workspace also shows the average score, reviewed-call count, current coaching focus, manual retry, and expandable per-call reviews.
- Added a dedicated Phone workspace to the admin sidebar. Teams can dial
customers from the shared company number, review customer-facing inbound and outbound calls without duplicate forwarding legs, filter missed calls and voicemail, search by name or number, open the linked Messages conversation, redial, read voicemail transcripts, and play provider recordings through an authenticated Clean Estimate audio endpoint.
- Connected the desktop Messages phone button to the real provider-backed
company-line flow instead of opening a local tel: link. Clean Estimate now rings the team member first, bridges the customer only after answer, presents the shared Twilio number to the customer, and logs the outbound call.
- Company-line calls now fall back to the workspace's configured forwarding
number when the current user has no profile phone, fixing the blocker that prevented both web and mobile calls for the production owner account.
- Fixed Twilio inbound voice signature validation for the complete call payload,
including CallToken and similarly named Call* fields. Real customer calls now pass webhook verification instead of failing with an HTTP 403 before the office forwarding line can ring.
- Fixed the next inbound forwarding failure found in a live call: Twilio can now
retrieve office conference instructions by POST, recorded conferences use Twilio's valid recording mode, and an unanswered, failed, or machine-answered office leg returns the caller to the configured CE Pro voicemail instead of leaving the caller on conference hold audio.
- Matched Twilio's official webhook validator behavior for standard HTTPS port
URL variants. This removes an intermittent signature mismatch where otherwise identical inbound calls could alternate between passing validation and receiving a 403 application error.
Complete mobile QA coverage guide
- Published a release-ready mobile testing guide for testers and QA. It
inventories all 55 current native surfaces, including all 29 native builder steps, and adds role, permission, offline, device-integration, accessibility, security, payment, customer-close, cross-platform, defect-triage, and release sign-off coverage.
- Added connected residential, commercial, fleet, Holiday Lights, specialty
web-handoff, job-payment, offline-recovery, and role-boundary journeys so QA validates the complete business story instead of isolated screens.
- Documented the current native scope boundaries for message composition,
field job mutations, optional Canvass builds, and non-persistent controls so promised-but-unavailable actions are reported instead of accidentally marked as tested.
2026-07-25
Mobile data recovery and compact screen layout
- Corrected the shared mobile authorization boundary so a temporary Supabase
auth or database dependency failure returns a retryable service outage instead of a false Unauthorized response.
- Added structured authorization-stage diagnostics and a bounded live mobile
API smoke command so production session, membership, organization, feature, and dashboard failures can be identified directly.
- Scoped mobile query caches to the signed-in user and organization instead of
the rotating access token. Normal token refresh no longer triggers a whole-app request burst across Home, Route, CRM, Inbox, search, and close flows.
- Allowed safe read loaders to use their organization-scoped fallback for
temporary 500/503 upstream failures while preserving fail-closed behavior for real 401/403 authorization failures.
- Changed the offline-sync banner into an overlay so its hidden animation no
longer leaves a large blank block above mobile screens.
Two live appointment choices in lead follow-up
- Updated every customer-facing email and SMS in the 20-point booking campaign
to offer two concrete appointment openings and then direct the customer to the secure self-booking page.
- Added live booking-option merge fields that recalculate before each delayed
touch using booking hours, minimum notice, the assigned rep, and current lead, estimate, and job conflicts. CE Pro prefers the earliest opening and the earliest opening on a different day.
- Updated campaign call tasks to give the assigned rep the same two live
openings and booking link, replacing open-ended scheduling questions with a specific choice.
- Hardened live-slot loading so it runs only for lead follow-up messages and
safely falls back to the self-booking page if availability cannot be loaded.
- Corrected each suggested choice to show the complete configured appointment
window, such as 3-5 PM, instead of presenting the window's start as an exact appointment time. Customer and rep prompts now ask them to choose a window.
Fusion v3 mobile navigation shell
- Replaced the legacy **Dashboard · Canvass · Proposals · Pipeline · Inbox ·
More sales tab deck with the approved Fusion v3 Home · Canvass · Route · Pipeline · Inbox · Me** shell.
- Added a live Route workspace that orders today's appointments and
follow-ups, opens the matching lead or proposal, and sends stops with saved addresses to Google Maps. The screen includes loading, empty, connection, and missing-address states.
- Kept the full proposal list reachable from the Proposals action in the
Pipeline header instead of consuming a primary tab.
- Corrected Me > About so it reports the native build assigned by EAS
instead of the static app-config fallback. It now also shows the Expo release prefix, channel, runtime, embedded/OTA source, and provides a manual update-check action.
Lifecycle automation safety net
- Added 14 active workflows covering inbound-reply handoff, five- and
fifteen-minute lead-response SLA escalation, missed-appointment rebooking, estimate expiration and reissue, invoice due dates, failed and completed payments, review recovery and referrals, contract renewal, cancellation, and weather rescheduling. Together with the existing library, production now shows 38 active standard workflows.
- Made the 20-point booking campaign reply-aware. An inbound email or text now
stops the remaining automated touches and creates a high-priority assigned rep task.
- Corrected the residential day-30 discount path so an unsigned estimate is
reactivated and receives a fresh 30-day signing window before the first coupon is sent.
- Added scheduled automation events tied to actual appointment, estimate
expiration, invoice due, and commercial contract end dates, with stable deduplication keys for safe retries.
- Added automation dispatch for portal review ratings, Stripe payment failures,
invoices crossing into paid status, and cancelled or weather-held job status changes.
Fusion manager workspace and final UI state pass
- Added a manager-specific mobile Pipeline with pending Red Line approvals,
weekly team revenue, closed-work totals, at-floor performance bars, representative commission totals, and recent estimates ready for reassignment.
- Added full approval review with requested, floor, and suggested price
context, service-level shortfalls, representative history, counter-offer chips, and approve, deny, or counter actions. Stale requests are rejected when the underlying estimate changes.
- Added the manager Canvass Team Map with organization-scoped territories,
live representative locations, GPS breadcrumbs, lasso territory creation, and balanced door assignment. Owners and managers no longer enter the representative bootstrap that previously returned Unauthorized when they did not have a field assignment.
- Added safe estimate reassignment. Red Line floors and commission now
recalculate for the newly assigned representative, affected services are identified, below-floor drafts create or refresh approval, and sending stays locked until the new pricing is reviewed.
- Completed the Fusion visual and state pass across authentication, Proposals,
Pipeline, settings headers, Residential, Measure, Suds Club, Close Kit, Canvass, Inbox, technician, and manager surfaces. The real CleanEstimate app icon now appears on sign-in, every Fusion header uses the waterline, and loading, empty, recoverable error, and offline states preserve the working page shell.
Fusion technician workspace
- Replaced the technician and crew-lead mobile shell with a focused
Jobs, Map, Inbox, and Me deck. Sales pipeline, commission, and organization-pricing tools are no longer shown to production roles.
- Added a date-scoped truck check, a next-run card, remaining assigned stops,
dispatch notes, loading and offline handling, and direct Google Maps route actions. Job and list access remains organization- and assignment-scoped.
- Added technician job execution with a persistent per-job checklist, camera
capture, direct before/after proof uploads, start time, and completion time. Completion requires all checklist items plus at least one verified before and after photo in both the app and the server route.
- Added a route map for assigned stops, a production inbox for dispatch and
crew notes, and a simplified Me screen. Crew leads retain permitted field collection tools; technician accounts do not receive payment or commission surfaces.
- Prevented an unauthorized stale queued Canvass write from blocking a valid
read-only bootstrap after session refresh. The tenant-scoped write remains queued for a later authenticated sync instead of making Canvass unavailable.
Lead appointment self-booking (Clean Estimate V1)
- Added secure, lead-specific scheduling links for both residential and
commercial opportunities. Customers can choose an available site-visit time without creating an account.
- Added organization controls for appointment duration, minimum notice,
booking horizon, and per-day availability under Settings > Portal.
- Added live conflict checks across lead, estimate, and job appointments plus
an atomic database reservation so concurrent customers cannot take the same rep's slot.
- Added automatic rep routing, lead status updates, appointment confirmation
email delivery, and automation-event dispatch after a successful booking.
- Added
[lead.booking_link]to the automation merge-field library and to
every email and SMS in the active 20-point new-lead booking campaign.
- Corrected a Next 16 API-route export incompatibility in the client-account
list route so production builds complete without changing client-account behavior.
Fusion Inbox and customer follow-up
- Added Inbox as a primary Sales tab for customer replies, Red Line
approval state, and recorded proposal views. Its API is authenticated, organization-scoped, role-aware, bounded, and backed by the existing communications and estimate records.
- Added All, Needs you, Messages, and Activity filters with
explicit loading, empty, error, refresh, and offline states. Customer rows open the real SMS history and call actions open the device dialer.
- Added editable quick replies to customer message threads plus
proposal-view nudges and safe follow-up template chips on proposal detail. Templates open the device composer and never send without the representative confirming the message.
- Kept the Sales bottom bar at six destinations by moving Clients into
More when Inbox becomes primary. Existing client list and detail routes remain available with no CRM data or permission change.
Fusion Canvass field experience
- Rebuilt the native Canvass overview as Canvass HQ, preserving verified
goals, XP, competition, territory assignments, GPS state, and offline sync while applying the Fusion v3 field visual system.
- Updated the live field map and door list with compact address search,
territory context, disposition-colored markers, and live-count filters for All, New, Not home, Follow-up, Leads, and Sold.
- Reorganized the property sheet around the six common one-tap outcomes while
preserving manager-configured dispositions, per-door attempt history, CRM context, quick estimates, AI guidance, and do-not-knock protections.
- Added an explicit Save + next field flow. After a verified outcome is
stored, the app advances to the closest eligible door while continuing to isolate offline work by organization and signed-in user.
Fusion residential estimator and Red Line approvals
- Added the feature-gated Fusion residential field estimator with one-screen
customer and property capture, service selection, server-authorized Red Line pricing, representative-private commission details, a customer privacy toggle, Suds Club context, and a pinned running total. Organizations without the fusionEstimator flag continue to open the current production wizard.
- Added explicit below-floor approval requests from mobile. The app shows the
exact service shortfall and captures the representative’s reason; the server recalculates pricing, keeps the estimate in draft, and creates or updates the tenant-scoped manager approval request. A normal save still rejects below-floor pricing.
- Corrected assigned-representative pricing authority so a representative with
Red Line access can submit the intended sold price when manager overrides are enabled. Save and approval confirmations now report a successful sync only after the local proposal is marked synced; otherwise they clearly report a device-queued draft.
- Added the Fusion Measure workflow with Google satellite polygon and path
tracing, on-device square-foot and linear-foot calculations, undo and clear, durable offline property photos, and representative-verified photo measurements that feed the selected service without trusting an image alone.
- Added the full Fusion Suds Club builder and customer-safe preview. The app
uses the organization’s published tier, included services, removable items, upgrades, custom annual pricing, windows, and exact nine-payment schedule; the preview excludes Red Line floors and representative commission.
- Added the feature-gated Fusion Close Kit for synced residential proposals.
Customers now review the exact server-owned scope and price, select a preferred arrival window, sign the canonical revision, complete the required hosted deposit, and see Booked only after signature, work scheduling, and payment are all confirmed by the server. The customer route removes tabs, sync status, Red Line floors, commission, and other representative chrome; handing the device back opens a private win checklist.
- Hardened close-flow retries so a saved signature is never reported as lost
when only scheduling fails. The app preserves the accepted record, returns to scheduling, reuses only a verified deposit checkout link, and refreshes payment state before showing the final confirmation.
Fusion v3 mobile foundation
- Added the shared Fusion v3 mobile design foundation: Barlow and Barlow Semi
Condensed typography, JetBrains Mono money formatting, the approved customer-safe and representative-private color system, consistent field cards and actions, and the cobalt-to-water header line.
- Updated the Red Line pricing primitives with segmented floor tracks,
accessible price adjustments, clear below-floor status, and dark representative-private commission panels. Production estimator behavior remains feature-gated while the new screens are completed.
Mobile session authority and Canvass access
- Fixed the mobile app preserving a stale encrypted session that could render
the signed-in shell while Dashboard, Canvass, Leads, and Clients all received Unauthorized responses. Saved sessions are now validated against Supabase at startup, refreshed once when the access token is rejected, and cleared locally only when both access and refresh credentials are definitively invalid.
- Temporary auth-network or secure-storage failures continue to preserve the
installed session and offline work. A rejected live API token now triggers the same single shared recovery path instead of leaving individual tabs in a broken authenticated-looking state.
- Made public estimate expiry evaluation deterministic for each page visit,
removing a React render-purity failure from the release gate while preserving the existing expired-estimate message and customer behavior.
Speed-to-lead calls, 20-point booking, and residential coupons
- Replaced the standard speed-to-lead sequence with a 60-second outbound lead
call that bridges the assigned rep, automatically drops a voicemail when the lead does not answer, and then enrolls the lead in a 20-point booking campaign spanning SMS, email, and rep call tasks through day 60.
- Added live stop checks before every campaign touch. Booking an appointment,
converting the lead, or marking it lost now ends the remaining outreach, and fixed graph execution so terminal condition branches cannot fall through into steps from the unselected branch.
- Updated the first-estimate-view automation to create one high-priority call
task for the assigned rep across residential and commercial estimates.
- Added a residential-only 30-day unsigned-estimate discount track with 5%,
7%, and 10% codes, plus a secure estimate-portal coupon field that validates eligibility and recalculates the estimate total before signature.
- Separated phone and text preference handling so an SMS opt-out continues to
suppress automated texts without silently suppressing requested service-response calls or voicemail fallback.
Mobile installed-session recovery
- Fixed the installed mobile app returning an authenticated user to login
shortly after launch. Valid encrypted sessions that finish loading after the startup gate now restore in the background, and a delayed signed-out event cannot erase a newer valid in-memory session without server confirmation.
- Revoked or invalid tokens still clear immediately. Temporary auth-network
failures retain the known-good session, protect offline work, and retry confirmation with capped backoff instead of ejecting the user.
Active residential and commercial automation library
- Activated a clean 22-workflow production library covering lead response,
appointment reminders, estimate follow-up, accepted-estimate handoff, completed-job tasks, reviews, overdue-invoice collection, newsletters, missed-call recovery, and long-term residential/commercial win-back.
- Added the commercial cold-drop play as two coordinated workflows: four
emails on days 0, 4, 9, and 16, plus rep call tasks on days 2 and 12 and a 30-day revisit task. General speed-to-lead SMS now excludes canvassing and door-to-door sources.
- Wired first estimate views, estimate acceptance, and overdue invoices into
the automation engine, and added customer-type context to lead, estimate, job, and invoice events so residential and commercial conditions route correctly.
- Archived two empty drafts, a duplicate speed-to-lead draft, and regenerated
QA drafts superseded by the named production workflows, leaving no non-archived draft workflows in the activated workspace.
Fusion v3 mobile design foundation
- Added the first reviewable Fusion v3 mobile UI phase: approved color,
typography, spacing, card, pill, button, waterline-header, and rep-private design primitives now share one token contract. Barlow is used for field copy, Barlow Semi Condensed for display hierarchy, and JetBrains Mono for money.
- Reworked the reusable Red Line presentation with a 32-segment price track,
fixed floor tick, status-aware fill, accessible price adjustment actions, and a navy rep-private commission panel. This foundation does not enable the new estimator workflow or change tenant feature flags.
Mobile installed-app release checks
- Kept Canvass directly after Dashboard in the primary navigation for
eligible field-sales roles while tenant configuration resolves. An older workspace flag can no longer remove the tab after login and shift another button under the user's tap; the authenticated canvassing bootstrap remains authoritative for workspace access.
- Added an installed-Android smoke journey that verifies the exact Expo OTA
release, signs in against the production authentication boundary, waits past the previously reported logout window, backgrounds and resumes the app, and opens Canvass. This release check catches session, navigation, tenant configuration, and live API failures that component tests cannot detect.
2026-07-24
Release candidate — not yet production-enabled
- Fixed a mobile login request storm that could create several password-token
requests within seconds, hit Supabase's rate limit, and destabilize the session. Keyboard submission and button taps now share a synchronous guard, the authentication provider deduplicates any simultaneous request for the same account, and the login screen shows the active OTA release and runtime identifiers for support verification.
- Fixed a mobile session-rotation race that could return an authenticated user
to login shortly after the app opened. A newer valid persisted session now replaces the in-memory token instead of being mistaken for logout, temporary encrypted-storage delays no longer eject an active user, and intentional sign-out still clears the account immediately.
- Fixed the remaining mobile startup failures found during installed Android
testing. The Sign in action now uses a dedicated blue native surface that remains visible even when Android does not paint a pressable background, a delayed stale SIGNED_OUT event no longer ejects a newer persisted session, and profile, membership, and organization hydration now stop on bounded deadlines instead of hanging indefinitely.
- Made the signed-in mobile shell usable while live data catches up. Dashboard
content and navigation render immediately with a compact loading or workspace-retry card; the Dashboard API now has a five-second, no-retry first-attempt budget and its direct fallback stops after eight seconds. Canvass is now a core production tab for eligible field roles while the canvassing bootstrap continues to enforce tenant, role, campaign, territory, and feature access.
- Fixed a mobile sign-in race where a delayed empty Supabase startup event
could erase a newly authenticated session and return the user to login. Successful credentials now apply the returned session immediately, only an explicit sign-out event clears an active account, stalled sign-ins recover with an actionable timeout, and the Android keyboard no longer hides the sign-in action. Profile and workspace reads start in parallel, while React Query no longer repeats retries already completed by the shared mobile API client.
- Fixed an indefinite mobile startup spinner caused by a stalled saved-session
read or by additional data requests running inside Supabase's auth callback. The app now releases the startup gate after five seconds and shows sign-in instead of remaining unusable, while deferred profile and organization hydration continues to restore valid sessions without blocking the auth client.
- Fixed inbound Twilio SMS routing for the production Messaging Service.
Customer replies now defer to the assigned phone number's webhook and appear in Messages; the reply missed during diagnosis was restored to its thread.
- Fixed mobile API screens, including Canvass, becoming unavailable after a
saved Supabase session expired while the app was backgrounded. Native session refresh now follows the app's active/background lifecycle, and the shared mobile API client refreshes one rejected bearer token and retries a safe read once. Writes are still never replayed automatically, organization scoping remains unchanged, and simultaneous 401 failures for the same account share one token refresh instead of creating a refresh storm.
- Hardened the mobile data layer for multi-organization rollout. Every
authenticated mobile request now carries a validated workspace ID and request ID, rejects inactive or inaccessible organizations, uses a 12-second deadline, retries only safe reads, and deduplicates identical in-flight reads. Dashboard, Leads, lead detail/create, global Search, proposal detail, scheduling, signatures, and field payments now use the shared tenant-aware API path. Independent dashboard and search reads run in parallel, list sizes are bounded, representative data remains assignment-scoped, and new organization-first database indexes keep common mobile reads from slowing down as the platform adds customers and franchise locations. The complete mobile API surface is now inventoried at /api/openapi/mobile.json for contract review and API gateway adoption. Corrected the Vercel ignore rule that previously excluded every src/app/api/mobile route from production while trying to exclude only the root native-app workspace; a packaging regression test now protects the authenticated mobile API from returning deployment-level 404s.
- Fixed the native Android failures visible in the field beta: owners and
managers now receive Canvass as a primary bottom tab when the feature is enabled, the duplicate Canvass shortcut was removed from More, Pipeline no longer selects unsupported estimate columns or loads an unbounded history, and its retry state keeps the normal screen context. Rebuilt the More layout with explicit native sizing, one header, accessible touch targets, and bounded text scaling so account and settings cards no longer collapse or overlap. Restored the NativeWind v4 JSX transform used by the remaining class-based mobile screens, and removed guaranteed 404 detours before the live Dashboard, Leads, lead detail/create, and global Search loaders so those screens start their scoped Supabase requests immediately. Owner and manager navigation stays capped at six primary tabs; scheduling access moves to the first More item while field crew keep Jobs as a primary destination. Lead and client message previews now use the existing authenticated mobile thread endpoint instead of a missing communications route. Removed the unsupported manual company-line action that always failed; mobile calling now opens the device dialer and states clearly that those calls are not automatically logged.
- Closed the mobile release audit gaps: customer message actions now open a
working conversation timeline with SMS compose, call, and email actions; job schedule dates stay on the saved calendar day in every US time zone; icon-only and notification controls have accessible names and Android back handling; and production Android builds now fail fast when the required Google Maps key is missing. The key is stored in the protected EAS development, preview, and production environments rather than in the repository.
- Corrected the native app thumbnail and launch branding. iOS, Android
adaptive icons, Android themed/monochrome icons, notification icons, the splash mark, and the mobile favicon now use the current geometric Clean Estimate CE mark instead of the retired droplet-and-PRO graphic.
- Rebuilt the native mobile interface around a field-operations design system.
Sales representatives now have a focused six-tab workspace, Canvass opens on a live shift-and-performance overview before the dedicated map/list, map controls and door filters are optimized for one-handed use, and the property sheet pins Save visit so common dispositions take two actions. The reset also standardizes sign-in, Dashboard, proposal list and detail, Pipeline and deal sheets, Clients and lead detail, Jobs list and field job detail, global search, More, every nested Settings page, estimate selection, all residential/commercial/fleet/holiday-light wizard steps, signature and scheduling sheets, AI coaching, shared cards, inputs, badges, and sticky builder actions. Existing Supabase, GPS, offline, CRM, pricing, autosave, payment, signature, scheduling, and delivery behavior remains intact.
- Added the canvassing command center, manager reporting and drill-down,
territory/campaign/assignment workflows, role-aware leaderboards, privacy settings, and the native field map with assigned targets, offline visits, shift-gated location tracking, reusable-property do-not-knock protection, CRM/estimate handoff, and durable scoped sync.
- Bound public residential and maintenance approval to an exact server-owned
scope, price, pricing revision, organization terms revision, signer, and canonical fingerprint. Stale reviews fail closed, accepted contracts become immutable, and package selection now stages a review instead of accepting without a signature.
- Added a transactional accepted-estimate outbox and reconciliation lease so
interrupted retries can recover jobs, pipeline events, webhooks, and XP without duplicating customer communication or sales credit.
- Rebuilt native residential pricing and delivery around the current
organization catalog, exact measurement units, published Suds Club matrix, permission checks, revisioned offline drafts, canonical server recalculation, private estimate photos, and confirmed provider delivery.
- Aligned the standard customer estimate action with its Decline Estimate
label. It now records the token-authorized declined status and shows the declined confirmation instead of submitting a revision request.
- Fixed large territory imports that could exhaust PostgreSQL transaction
advisory locks. Target rollups now use stable property-row locks and set-based counter, disposition, and shared do-not-knock updates; the regression matrix includes a 10,000-target batch and a 20,000-target local proof.
- Restored explicit trusted-server table and sequence privileges in
migration-only environments so health checks and mobile bearer authentication can read foundational organization and membership data.
- Added a role-aware canvassing load harness with secret-free reports and
per-route latency/error/throttling gates. A bounded local proof against 10,000 targets passed 65/65 requests with 0% errors, 0% throttling, and 201ms p95; target-environment capacity evidence is still required before claiming production scale.
- Added a clean Supabase PostgreSQL 17/PostGIS CI gate. The complete migration
and seed chain applies from zero, database lint reports no schema errors, and the pgTAP authorization/scale matrix passes 24/24.
- Fixed the Vercel ignored-build rule so every production deployment builds
the complete main tip even when its final commit only updates release evidence. Preview deployments still skip changes that cannot affect the web runtime.
- Fixed native build environment drift by removing stale committed Supabase
overrides from every EAS profile. Development, preview, and production builds now select their matching remote EAS environments, with regression coverage preventing the retired project reference from returning.
- Added the live estimator Maps credential to the mobile Maps/Places and
Android Maps variables in every remote EAS environment. Android internal build 45 completed successfully from the merged canvassing main revision. Native acceptance verified the map, address list, disposition sheet, and active-shift controls, and corrected a territory-selector layout issue that could squeeze the Android map. The Rolling Suds Lancaster-Harrisburg workspace already has canvassing enabled, while signed-device Maps validation, real Firebase configuration, and interactive iOS signing setup remain release checks.
- Corrected the mobile residential inline-price editor type contract so the
production TypeScript validation remains clean for both its editable text field and pressable display state.
- Added a dedicated canvassing production-readiness endpoint. It fails closed
unless the private server geocoding key and all 44 runtime Supabase relations/RPCs are available, rejects unauthenticated probes before contacting Supabase, and exposes detailed missing paths only to an authorized release monitor.
- Applied the complete 46-migration production backlog to the live PostgreSQL
17 project after a rollback-only pass against the real production schema and data. The Supabase ledger is now 224/224, every canvassing table has RLS enabled, trusted-server table/RPC grants are complete, and an authenticated organization-member campaign read succeeds.
- The protected production check now reports 44/44 required Supabase
runtime paths with zero missing paths. The database side of the earlier Could not validate canvassing campaign access failure is resolved. Production now also has the required sensitive server geocoding variable, backed by the Google Maps credential already used by estimator address lookup, and the refreshed Vercel deployment is ready at cleanestimate.pro.
- Fixed the misleading Could not validate canvassing campaign access
failure shown when the production database contract is absent. Web access checks and normalized native write failures now classify missing canvassing tables, columns, and RPCs as a non-retryable setup condition; the command center clears stale data, shows a safe amber setup notice, returns a stable CANVASSING_SETUP_INCOMPLETE code, and confirms that no campaign data was changed.
- Completed the merged web verification at 441 files / 2,052 tests, clean
TypeScript, and a 434-route/page production build. Mobile verification is now 60 suites / 402 tests, Expo Doctor 19/19, and successful Android/iOS static exports.
- Production enablement remains gated on production-like authenticated load,
a separately restricted server geocoding credential, remote EAS Maps/Places and Firebase secrets, signed Android/iOS physical-device GPS and offline acceptance, store review, and completed privacy/legal approvals.
2026-07-23
- Fixed slow Estimates navigation. The Estimate workbench now opens before its list data finishes loading, shows a clear loading state, and uses a lightweight assignment lookup instead of waiting for the full Team-management response and one Auth lookup per employee.
2026-07-07
- Redesigned the logged-in admin workspace. The app shell now uses a cool-gray canvas, teal primary actions, a narrower dark slate sidebar, a flatter top header with global search plus New estimate, and smaller rounded corners across shared admin surfaces.
- Reworked the owner Command Center layout. KPI cards now lead the page, Daily queue is a table with priority, area, due timing, status, and next action, workflow shortcuts sit directly below it, and alerts/reports/commercial/holiday snapshots sit in the right column.
- Replaced vague dashboard helper copy with direct labels for the queue, reports, service trends, commercial totals, and holiday lights totals.
- Standardized authenticated admin pages on the same teal primary color and reduced oversized rounded cards, heavy shadows, and older blue accents across reports, schedule, team, settings, AI, commercial, franchise, billing, jobs, messages, estimates, and operations surfaces.
2026-07-01
- Added the required SMS consent checkbox to public workspace signup. New owners must enter a phone number, check the consent disclosure before submitting, and the owner profile now stores the consent flag, timestamp, and exact disclosure text for opt-in audit history.
2026-06-27
- Launched the Suds Club membership builder in the residential estimate builder, replacing the old Maintenance Plan on both web and mobile with one shared pricing engine so quotes never differ between devices. Reps pick a tier by home square footage (the base annual price fills in automatically; homes 4,500 sq ft and up get a custom quote), customize the plan by removing included services or adding extras such as pergola or garage/shed, and add an exterior window-cleaning upgrade priced as windows × price/window × cleanings per year. Pricing updates live with an upfront payment, nine monthly payments, and the yearly total plus tax, and the saved plan reopens identically on web and mobile.
- Added a Suds Club admin area under Settings → Suds Club where managers configure the maintenance multiplier, window defaults, minimum yearly floor, tier base prices, and the full square-foot-tiered standard-price matrix.
- Stopped the public estimator from showing internal pricing/commission notes or the margin calculator to customers, and branded customer email and SMS with the company name instead of "Your service provider".
- Fixed job creation for brand-new customers and added validation on scheduled job dates so typos like the year 2206 are rejected.
- Fixed manual cash and check refunds so recording one works again and correctly reduces the invoice balance.
- Fixed the sales pipeline so dragging a card to Proposal Sent sticks and the Viewed status filter works.
- Fixed client lifetime value to update when an estimate is accepted, and saved new client phone numbers in a consistent format.
- Tightened permissions so field technicians no longer see crew compensation, pricing, or billing they should not, and hid management controls for view-only roles.
- Fixed the customer portal so estimate requests submit successfully, customers can download and print invoices, and e-signing a quote works end to end.
- Made Pay Link and Push to Workiz report honestly when an integration is not configured instead of showing a false success.
- Fixed the Sales Cycle analytics dashboard so it loads without error.
- Fixed lead sources so new sources appear immediately and accept fractional commission percentages such as 7.5%.
- Sanitized customer names on input to prevent unsafe content in generated PDFs and emails.
2026-05-10
- Updated the authenticated browser Sales Workspace so the Quote tab now uses the same residential estimator flow as the main web app, including client/property entry, service pricing, add-ons, maintenance options, Red Line handling, autosave, and review/send. Local development CSP now allows the React dev runtime to hydrate normally so Sales Workspace controls can be clicked during testing.
2026-05-08
- Added the authenticated browser Sales Workspace for
sales.cleanestimate.pro. Sales reps, managers, and owners can now use a phone-first web page after login to see assigned appointments, search customers, call or text from customer cards, open message history, save new leads assigned to the signed-in rep, build itemized quote totals with deposit math and Red Line floor checks, book lead appointments back to the live schedule, and work recent follow-ups while the native mobile apps continue toward release. - Added the new Maintenance Plans module for robust residential and commercial service agreements. Teams can now create fixed or open-ended plans, add multiple services with independent schedules, scope services to specific plan years, add commercial sites, preview generated year/month timelines, activate plans into dispatch jobs, edit individual occurrences, and manage pause, resume, cancel, duplicate, renew, and extend actions.
- Added system maintenance templates for residential basic annual, residential seasonal, residential premium multi-year, commercial quarterly, and commercial multi-site plans. Pricing previews now support per-service, fixed-total, and hybrid contract modes with monthly, quarterly, annual, per-visit, and one-time billing cadences.
- Tightened the maintenance occurrence editor so rescheduling happens through date edits and drag-to-month moves, while commercial site-specific dispatch jobs now keep the selected site property link.
- Hardened the maintenance plan options loader so an unavailable templates table no longer blanks the builder; customers, properties, and active services still load, and required lookup errors now name the failing option set.
- Changed the residential estimator handoff so Build robust plan saves the estimate draft first, opens the robust maintenance plan builder in a separate tab, and links the plan back to the source estimate instead of replacing the in-progress estimate.
- Hardened robust maintenance plans started from a saved residential estimate so the builder rehydrates the source estimate customer before save and normalizes estimate tax percentages into plan tax rates, preventing blank-customer
Invalid UUIDsaves. - Improved maintenance-plan setup errors so missing production database migrations now surface the underlying relation, column, or foreign-key message instead of a generic load/save failure.
- Fixed saved maintenance-plan totals so estimated annual value and total contract value are refreshed on every plan save from the same generated service occurrences the builder previews.
- Fixed module-to-maintenance handoff for commercial building, fleet, and holiday-lights proposals. Raw module proposal ids now resolve to their synced shared estimates before the maintenance builder loads, and commercial building linked estimates now mirror and display the proposal review annual value from initial line items, maintenance visits, tax, and portfolio discounts instead of stale stored aggregate or initial-clean totals.
- Preserved the legacy residential maintenance-plan path while adding a bridge from the old estimator builder into the new robust module.
2026-05-07
- Fixed commercial building proposal saves that started from a native CRM client. Commercial building drafts now use the same proposal-contact resolver as fleet proposals, so selecting a saved CRM customer or changing the contact on an existing building proposal no longer trips a foreign-key save failure behind the generic Failed to create proposal message.
- Fixed a follow-up set of commercial fleet review issues found in the estimator audit. Fleet review auto-saves now use the same payload as normal draft saves so assigned reps and lead sources are retained, commercial fleet Finalize Proposal now opens the saved proposal instead of feeling inert, and duplicating from the newer Fleet workspace keeps the office inside that Fleet route.
2026-04-28
- Hardened the public health check after the page-speed stress run showed Supabase-backed probes degrading while public pages stayed fast. The health endpoint now aborts its Supabase reachability query at the timeout boundary so repeated uptime checks do not leave extra database requests running during a Supabase incident.
- Followed up on the page-speed audit with a smaller Clients loading path and lighter telemetry ingest. The Clients workspace now opens its page shell without waiting on the full account tree API during server navigation, then hydrates rows through the existing client-side loader, and route telemetry rate limiting no longer spends an extra database RPC before writing sampled timings.
- Started the admin usage-reduction pass for the highest-traffic workspaces. Dashboard metrics keep exact visible totals while caching the heavy chart RPC, Estimates opens without server-side self-fetches and keeps exact workbench counts with cached summary metadata, Schedule caches its reference data briefly, and admin route telemetry samples more heavily by default so real logged-in slow pages show up faster.
- Extended the exact-number guarantee across admin dashboards and reports. Revenue, pipeline, schedule, clients, commissions, crew pay, franchise, retention, operations, lead-source, holiday-lights, sales-cycle, geography, and margin reporting now avoid hidden row caps, display money to the cent, and fail visibly when a report source cannot be loaded instead of showing guessed or partial totals.
- Tightened Revenue Analytics so money reports stay exact instead of fast-but-approximate. Revenue rollups now page through every matching accepted record, include fleet/building/holiday lights in the time series, sum in cents, fail closed on query errors, and display penny-level totals on the revenue report.
2026-04-27
- Reduced shared admin-page load pressure after Schedule and Clients were still taking several seconds to open. Admin org resolution now skips unnecessary franchise descendant lookups, sidebar gamification no longer performs setup/write work during normal navigation, admin API middleware rate limiting no longer spends a database RPC on protected office reads, and the Clients list avoids unused count and all-org health work where it can.
- Fixed an admin alert feedback loop that could make the logged-in app feel slow. The alert bell now reads without regenerating managed alerts, dashboard alert generation is throttled, unchanged alert rows are no longer rewritten, realtime only refreshes on new alerts, and slow alert fetches stop instead of stacking in the browser.
- Hardened the East-backed login and worker recovery path. Password sign-in now surfaces a clear auth-service timeout instead of spinning forever, minute-by-minute background workers have incident kill switches and lower default concurrency, super-admin pages stay dynamic during deployment, and the public ingress rate limiter now fails closed quickly when a hot key is locked instead of holding database workers until gateway timeout.
- Expanded the incident scheduler pause so
DISABLE_CRON_JOBS=truenow stops follow-ups, drip campaigns, proposal expiration, automation waiting runs, scheduled automation triggers, franchise summary refreshes, background jobs, and webhook delivery before those routes open Supabase connections. This gives the team a safer recovery path when Supabase warns that the project is near Disk IO Budget exhaustion. - Reorganized the admin sidebar around daily office work. The top group now reads Daily work and pins Command Center, Inbox, Estimates, Schedule, and Jobs, while Pipeline and AI Hub move into the growth/admin area so the left rail matches the actual day-to-day workflow more closely.
- Updated the owner dashboard into Command Center. The page now shows the Daily admin queue before reports, charts, and secondary shortcuts.
- Refocused the admin Estimates page into an Estimate workbench with priority cards for drafts, viewed follow-ups, unassigned estimates, and accepted job handoffs, plus row-level Next action controls for the most common send, follow-up, job, payment, schedule, review, and detail workflows.
2026-04-26
- Added the Phase 0 production telemetry baseline for scaling work. CE Pro now samples production route and API timing, exposes service-role-only exports for top route latency,
pg_stat_statements, and Supabase connection snapshots, and documents the stop condition, business-load target prerequisite, kill switches, and MRR-based cost gates before any speculative scale phases continue. - Moved commercial fleet and building proposal delivery onto the background job queue. The send dialog now acknowledges a queued delivery immediately while the worker renders the proposal PDF, sends email/SMS, retries transient failures, and records final delivery state in the background;
COMMERCIAL_PROPOSAL_SEND_QUEUE_DISABLED=truerolls the path back to synchronous delivery.
2026-04-25
- Added docs-site discovery surfaces for
docs.cleanestimate.pro. The help center now publishes a generated/sitemap.xml,/site-map,/llms.txt,/llms-full.txt, and/robots.txt, all sourced from the MDX docs catalog so search engines and AI ingestion tools can find the current documentation. - Connected the mobile CRM and residential estimate handoff to live Clean Estimate data. Signed-in mobile users now load, create, and update customers through authenticated live client APIs, client/lead New estimate actions carry CRM context into the builder, and mobile residential address entry now uses the same property lookup service as the web estimator to prefill square footage, stories, lot size, and year built.
2026-04-24
- Repaired live customer-portal Stripe invoice payments after QA found Checkout sessions could open but paid portal invoices stayed pending. The production app URL environment value was corrected, and the central Stripe webhook now recognizes portal invoice Checkout metadata so successful test-mode payments mark the portal invoice paid while declined cards remain unpaid.
- Added Red Line pricing to the mobile residential sales flow. Eligible sales reps now see per-service floors, status, and visible commission in the mobile pricing/review steps, below-floor mobile edits are clamped or blocked before save/send, mobile pricing settings show the active Red Line model, and queued mobile residential proposals now carry the Red Line snapshot for audit.
- Hardened the Android mobile sales QA blockers found in the live app pass. Mobile API calls now default to
app.cleanestimate.pro, mobile-created customers show back up in CRM search and detail, proposal-client records no longer dead-end the client page, commercial building proposals cannot advance to final review with missing client/property/service/pricing/frequency data, modal headers respect safe areas, and Android Back now asks before discarding unsaved lead or customer details. - Restored self-serve workspace signup so new owners can create an account, create their organization, receive starter setup data, start a 44-day trial, sign in, and land in onboarding from
/signup. The public CTAs now point at the trial signup path, stale waitlist signup blocks were removed from the public marketing surface, and the new regression test covers owner creation, org creation, membership creation, bootstrap seeding, duplicate-workspace blocking, and rollback on partial signup failure. - Hardened the first-run onboarding finish path. Service toggles now save in one parallel pass instead of holding the setup flow through a long sequence of individual saves, and the final dashboard handoff now shows an Open Dashboard fallback link if the workspace completion save succeeds but the admin dashboard is slow to render.
- Added a read-only browser QA script for the super-admin portal and used it against the live site. The pass now checks the signed-out redirect guard, super-admin magic-link sign-in, Dashboard, Organizations, organization detail, Users, Waitlist, Activity, and empty-result filters, while the underlying fixes stabilize super-admin table hydration and allow the deployed Google Analytics collect endpoint through CSP so real portal failures are easier to spot.
- Added a live browser QA script for self-serve signup and onboarding. The pass creates a fresh AgentMail inbox, signs up a new owner/org, completes onboarding, verifies the database state, confirms password login, and opens a real delivered access-link email; the dashboard Lead Sources shortcut now points at the live Lead Source ROI report instead of a missing analytics route.
2026-04-23
- Added a safe dual telecom provider path for Phone / Voice. Workspaces can now choose Esendex or Twilio, provider-specific settings and token-protected webhook URLs are shown in Settings, outbound SMS/manual messaging/ETA texts/automation calls use the active provider, and legacy Twilio workspaces keep Twilio validation and callback behavior. Provider switches also fail safe by turning voice off until the selected provider has a ready number and forwarding setup.
- Tightened the live QA accessibility blockers found in the admin and customer estimator flows. Icon-only alert, table action, route, and schedule controls now have screen-reader names, admin and estimator filters expose clear select labels, customer/property inputs are associated with labels, XP progress bars are named, dark-sidebar contrast is stronger, login/signup paragraph links no longer rely on color alone, the customer portal footer has stronger contrast, and the admin theme toggle no longer risks a hydration mismatch during page load.
- Fixed the customer magic-link login page on
app.cleanestimate.pro/customer/login. Signed-out customers can now open the login form directly instead of getting caught in a redirect loop from the protected customer portal shell.
2026-04-22
- Cleaned up the main admin dashboard top section so the report shortcuts now live inline beneath the office snapshot instead of as a separate skinny right-side rail. The awkward Open the next read panel copy is gone, the new section simply reads Top reports, and the dashboard header area is easier to scan at laptop widths.
- Tightened the live portal referral gate and the public discovery surfaces after the latest production QA pass. Unknown emails on the portal referral tab are blocked again instead of falling into the referral-code enrollment step, known customers without a saved code can still create one, and the asphalt-maintenance module is once again listed across
/site-map,/sitemap.xml, and/llms-full.txt.
2026-04-21
- Repaired another live Rehash popup failure after reproducing the exact broken estimate against the real workspace schema. The detail drawer and inline Rehash quote-save path now tolerate older workspaces that are still missing legacy estimate pricing columns like
minimum_adjustmentandtaxable_amountinstead of crashing the popup with a generic request failure.
- Expanded the Rehash detail drawer into a true quote-editing popout. Managers can now see each residential service's list price, current quoted price, and saved Red Line floor side by side, adjust quoted service prices inline without leaving Rehash on standard residential service-line quotes, and save those line-item changes directly from the drawer while the app keeps the floor guardrails intact. Package and maintenance-plan quotes now stay read-only in that drawer and point managers back to the full quote editor instead of allowing a risky partial inline save.
- Hardened the Rehash popup after live failure reports. The quote drawer now fails open when older records have malformed parent-estimate links, invalid legacy rep ids, or bad Rehash template rows, so the manager can still open the quote summary instead of getting a generic request failure.
- Added Red Line room summaries to the Rehash queue so managers can spot discountable residential quotes faster. Queue rows and the Rehash detail drawer now show quoted service totals versus the saved Red Line floor, plus a clear room / at-floor / below-floor badge for faster win-back review.
- Hardened the Rehash setup diagnostics again after more live rollout feedback. The queue and analytics loaders now tolerate optional missing Rehash columns where they can, the setup warning now includes the exact missing
estimates.rehash_*or disposition fields when the production database is still missing the core Rehash foundation migration, and unrelated missing-column errors no longer get mislabeled as a Rehash rollout problem. - Tightened the Rehash setup fallback after another live production report. The queue no longer treats missing legacy
message_templatesstorage as a fatal Rehash-foundation failure, so/admin/rehashcan still load with the built-in staged copy while only the actual Rehash estimate/disposition schema remains a hard requirement. - Repaired the live Rehash route guard after another audit pass.
/admin/rehashis now registered in the admin permission map, so direct visits to the queue no longer bounce back to the dashboard before the Rehash access checks can run. - Audited the new Rehash Phase 4 rollout and repaired three gaps before the next production pass: Rehash analytics now fails soft with the same setup warning as the queue when a workspace is missing migrations, the Commissions page no longer hard-crashes in that partial-rollout state, and payroll exports now keep legacy accepted lead-source payouts instead of skipping them.
- Began Phase 4 of the residential Red Line rollout. The Rehash queue now works much better on mobile, Pricing Manager can toggle Rehash template A/B testing, Rehash analytics now tracks template performance and queue-pressure aging buckets, and the Commissions dashboard can export payroll-ready CSVs including recovered Rehash split payouts.
- Hardened the Rehash rollout path after the first live feedback. If a workspace is still missing part of the Rehash database setup, the Rehash page now loads with a clear setup message instead of failing outright while the queue API crashes.
- Began Phase 3 of the residential Red Line rollout with the new Rehash bolt-on. Residential quotes can now require a saved Rehash disposition, unsold quotes can auto-enroll into the staged win-back sequence, managers get a dedicated
/admin/rehashqueue with text, call, email, touch, dead, and re-quote tools, and the new Rehash analytics page tracks recovered revenue plus saved recovered-close attribution. - Hardened the residential Red Line rollout after another audit pass. Residential estimates now fail closed to standard pricing when an assigned rep record can no longer be resolved instead of borrowing Red Line access from whoever happened to open the quote, explicitly enabled non-sales teammates now appear in the residential rep-assignment list, and the Commissions dashboard now keys its Red Line badge off the saved Red Line snapshot instead of assuming every stored commission amount came from Red Line.
- Began Phase 2 of the residential Red Line rollout. Residential reps can now save below-floor drafts when manager override tracking is enabled, request a Red Line override directly from the estimator, and owners or managers can approve or reject that request from the review step or the estimate detail page before the quote is allowed to send.
2026-04-18
- Added background autosave to the residential estimate wizard for internal office users. New and edited residential drafts now save as you work instead of waiting for Step 3, pending changes are flushed if the office clicks away right after editing, the review/send tools reuse that same saved draft, and service or add-on price overrides can be typed directly into the estimator instead of relying on browser spinner arrows.
- Tightened CRM navigation and lead handoff workflows. New leads now open directly on their detail page after creation, estimate/job/invoice workspaces now include clearer View Client shortcuts, and lead-launched residential estimates keep the linked customer context without forcing duplicate data entry.
- Expanded the mobile sales flow into a close-ready proposal detail experience. Reps can now open a secure customer-facing proposal from mobile, hand the device to the customer for on-the-spot acceptance and signature, and launch either a 25% deposit checkout link or a full-balance payment link without leaving the proposal screen.
- Added
sales_repaccess to the mobile payment-link creation endpoints so sales users can launch hosted Stripe Checkout links from proposal detail instead of waiting on a manager or crew-lead role. - Broadened the mobile proposal shell to recognize all live estimate wizard families. Dashboard cards, proposal lists, and client proposal history now normalize commercial building, generic quote, asphalt maintenance, residential, fleet, and holiday-lights estimates correctly instead of hiding unsupported module types behind fallback labels.
- Added guided mobile web handoff cards for Generic Quote and Asphalt Maintenance, so sales reps can still launch those live quote builders from the app and then come back to mobile proposal detail for presentation, signature, and payment follow-up.
- Fixed the mobile Present and Close handoff to prefer the live customer estimate view before the legacy proposal path. That keeps holiday lights, fleet, commercial building, asphalt, and other quote-core-backed estimates from opening a dead-end customer URL when reps launch the close flow from mobile.
- Fixed the residential mobile builder entry path to reset correctly when starting a brand-new proposal, preventing old in-memory quote state from bleeding into the next residential estimate.
- Fixed a CRM handoff gap when launching residential estimates from lead and client context. New Estimate actions now carry the linked client, lead source, service address, and saved property square footage into the wizard automatically so the office no longer has to re-enter customer details after starting from a lead or client record.
- Made the Red Line rollout easier to understand during setup. The residential estimator now shows an inline notice when Red Line is hidden because the workspace setting is off or the assigned rep is on standard pricing, and the Team / estimate-assignment lookups now fail soft in older workspaces that have not finished the Red Line membership migration yet.
2026-04-17
- Added the first Red Line pricing foundation to the residential estimator. Floor pricing can now be derived from the live suggested service price instead of a separate flat table, so square footage, service size, and dirtiness all flow into the floor automatically before reps adjust pricing.
- Added real-time Red Line estimator feedback for eligible users. Residential service rows can now show the floor, status, and commission context while pricing, review screens carry that same information forward, and the API now rejects attempts to save Red Line estimates below the floor.
- Added the first admin controls for the Red Line rollout. Pricing Manager now includes a Red Line section for workspace-wide toggles, commission percentages, and per-service floor percentages, while Team Management now includes a per-user Red Line access toggle for mixed-comp teams.
- Started persisting Red Line payout data with residential estimates and canonical line items so the Commissions dashboard can use explicit commission amounts and flag those rows as Red Line instead of relying only on the older 5% / 10% lead-source model.
- Followed up on the Red Line rollout after the local audit. Residential estimate saves now keep
redline_snapshotpopulated even for standard-pricing deals, the estimator and API now resolve Red Line access from the assigned sales rep instead of whoever happens to be editing the estimate, and team role changes now reset the Red Line access toggle to that role's default instead of leaving stale settings behind.
2026-04-14
- Hardened the first Lead Connectors rollout after the local audit pass. Public website-form connector endpoints now enforce an IP bucket before the contact-aware rate limit, review-queue items can only be approved or rejected once, connector import-run logging now has database-level replay guards on external ids and idempotency keys, connector-backed API imports preserve explicit
existing_client_idhandoff into the shared lead-intake pipeline, and CE Pro now enforces one active connector per provider/transport pair instead of silently choosing the oldest active record. - Tightened email-forward lead parsing so phase 1 now requires an active email-forward connector before inbound emails can auto-create leads. Low-confidence email candidates continue to pause in the review queue when a connector threshold is configured, but the old no-connector parser fallback no longer creates leads on its own.
- Added the first phase of the new Lead Connectors platform in Settings > Integrations. Workspaces can now configure connector-driven lead intake for trusted API imports, browser-safe website forms, and forwarded email parsing from one shared admin surface instead of treating every source like a separate custom integration.
- Added connector-level field mapping, defaults, test imports, recent import history, and a low-confidence email review queue. Teams can now normalize outside field names into CE Pro lead fields, apply source or assignee defaults, test the connector before launch, and approve or reject uncertain parsed emails before they become live leads.
- Hardened duplicate suppression for connector-based lead intake by carrying external ids and idempotent request keys through the shared lead pipeline, while the docs now include a dedicated Lead Connectors guide and updated integrations onboarding steps.
2026-04-10
- Canonicalized authenticated web entry points to the dedicated app host. Marketing-site Log In and direct admin links now send users to
app.cleanestimate.pro, root-domain requests for/admin,/login, password reset, and auth callback routes now hand off to the app host automatically, and invite plus recovery emails now generate their setup links on the same app subdomain instead of mixing root-site and app-site entry paths.
2026-04-08
- Hardened the Esendex phone rollout right after the first audit pass. Voice transfer scripts now preserve the provider command syntax Esendex expects, voice status callbacks now reuse the tokenized webhook URL contract shown in Settings instead of drifting from protected environments, and the Phone / Voice setup now clearly stays on text-to-speech greetings for Phase 1 instead of advertising custom audio playback that is not live yet.
- Replaced the old Twilio-shaped phone setup with Esendex across the app and docs. Workspaces now configure a shared Esendex number, SMS license key, and Voice license key from the Phone / Voice settings card, while the docs now point to the new Esendex setup guide instead of the retired Twilio instructions.
- Shipped the Esendex phone stack for daily office use. Shared SMS sends and replies now run through Esendex, inbound customer calls transfer to the saved office line, missed calls log into Messages, and the phone timeline now uses provider-neutral message and call ids instead of depending on Twilio-only SIDs.
- Updated automation call behavior for the Esendex rollout. Make Call and Voicemail Drop now use the shared Esendex number, phone triggers now cover
Call Received,Call Answered,Missed Call, andCall Completed, and the docs now clearly mark AI Voice Agent and inbound voicemail recording playback as out of scope for this Phase 1 release.
2026-04-05
- Smoothed another big chunk of the mobile page-by-page QA pass. Proposal builders now keep the residential step state in sync without mutating state during render, the standalone proposal detail screen now has working call, email, and maps actions plus a proper back header, the estimate-type picker now gives reps clearer field-quoting guidance with step counts, the dashboard sync indicator now behaves like a real queued-sync shortcut instead of a dead notification bell, and client notes save more reliably when a rep leaves the notes field.
- Fixed the mobile client detail and proposal shells so they line up with the live estimate data model instead of a stale proposal-table contract. Client detail now pulls proposal history from current estimate records, proposal lists stop breaking when you scroll or refresh, and remote proposal cards no longer show fake
0%margin badges when margin data is unavailable. - Reworked the mobile pipeline around what field users can actually act on. Pipeline cards now normalize service labels correctly, the summary bar shows Needs Follow-Up instead of a misleading always-zero average margin metric, empty pipelines explain what to expect, and the deal detail sheet now gives real proposal, call, and email actions when contact data exists.
- Cleaned up the mobile account/settings shell on phones. The Settings hub now opens on a safer padded account card with organization context, and the Profile, Organization, Notifications, Offline, and About screens now respect the safe area so content no longer crowds the top system chrome on smaller devices.
- Fixed the first mobile startup blockers that were making the current Expo shell feel dead on arrival during testing. The app no longer crashes during startup because of WatermelonDB model field syntax, the web-safe auth storage path now avoids the
expo-secure-storefailure on browser-based QA runs, and the tab shell no longer registers the Jobs tab against the wrong nested route. - Refreshed the mobile auth flow so the first-run experience looks intentional instead of bare. The sign-in and forgot-password screens now use branded intro and recovery cards, stronger spacing, proper input chrome, and consistent action buttons instead of the older stacked text-and-button layout.
- Cleaned up the mobile dashboard shell for small screens by removing the duplicate tab header and replacing the cramped quick-action strip with larger proposal-type cards that hold up better on phone-width layouts.
- Fixed the mobile auth shell so route protection now lives in the nested auth and app layouts instead of firing redirects from the root navigator. Internal beta testers should no longer see the startup/login warning banner that previously made the Android dev client feel broken before they even signed in, and the local debug build now suppresses the two known development-only native-library warnings that were still pinning a generic warning bar to the bottom of the login screen.
2026-04-04
- Hardened the Clean Estimate Pro mobile internal beta for the current Expo app instead of splitting into separate iOS and Android codebases. Mobile preview builds now use the internal beta lane, the app reads the required public Expo/Supabase support config consistently, profile editing now writes back to the live user-profile schema, and field proposal drafts reopen through one shared deep-link contract instead of occasionally landing on the wrong step.
- Added the first crew-lite mobile ops slice behind scheduling. Eligible roles now get a dedicated Jobs tab, job-assigned notifications open the same typed job route as the rest of the app, job detail now reads the live customer/job fields correctly, and crews can review schedule, address, invoice balance, crew alerts, and payment collection from one mobile screen.
- Replaced the remaining mobile residential placeholder pricing and tax assumptions with shared live config reads. The residential builder now applies the active web pricing config and shared state tax overrides, while the mobile Pricing Defaults and Tax Rates settings screens now act as read-only references to the same data sources the web app uses instead of writing to stale legacy fields.
- Finished the crew-alert rollout on the current generic-quote and job stack. Generic quote line items now expose a dedicated internal crew-alert field again, estimate edit/save flows preserve that value, jobs created from those quotes keep the alert on each line item, and both the web job detail screen plus the mobile crew job screen now show a clear Crew Alerts callout without exposing those notes to customers.
- Added internal crew-alert support to commercial building proposals, generic quotes, and downstream job detail views. Office staff can now save field-only instructions alongside customer-facing line-item descriptions, commercial building catalog adds prefer the shared price-book description when available, and both quote flows preserve crew alerts when drafts are reopened without exposing those notes on customer-facing proposals, quotes, or PDFs.
- Made commercial building line-item descriptions actually visible and editable in the wizard as a dedicated Customer Description field, then backfilled clearer default scope copy across the stock commercial building services, seeded fleet vehicle types, and built-in residential add-ons so default catalog rows stop starting blank when they are reused in shared quote-core line items. Older commercial draft rows that were saved blank now also repopulate that customer description when the proposal is reopened.
- Unified customer-facing item descriptions across the remaining quote builders. Residential services and add-ons now expose editable Customer Description fields in both the office wizard and the public standalone estimator, generic quote detail notes now render on the customer quote page and PDF, and fleet vehicle rows plus add-ons now carry editable customer descriptions through builder, review, customer proposal, and PDF flows.
2026-04-02
- Fixed the lead-creation modal launched from linked client records on shorter laptop viewports. The New Lead dialog now becomes vertically scrollable instead of trapping the save button below the fold when the selected-client flow expands with address and appointment fields.
2026-04-01
- Fixed a residential estimate save failure on quotes that included mirrored offer children such as proposal packages and maintenance plans. CE Pro now allows the residential offer-mirror workflow to persist multiple child estimate rows for the same parent quote instead of tripping the one-to-one source-proposal uniqueness rule that only applies to linked commercial, fleet, and holiday-lights estimates.
- Followed that residential mirror hotfix through the rest of the office and platform surfaces. Revenue, service-mix, geography, retention, franchise, admin-alert, sales-rep dashboard, residential overview, commissions, estimate-list, marketing audience, sales follow-up schedule, and super-admin estimate rollups now treat mirrored residential offer children as internal support rows instead of counting them as extra quotes, so maintenance or package siblings no longer inflate analytics, stale-deal alerts, rep follow-ups, estimate lists, or back-office totals.
2026-03-31
- Tightened customer-engagement tracking across messages, quotes, and invoices. Public estimate, proposal, maintenance, and shared-quote opens now wait for the alert write instead of leaving those inserts as best-effort background work, so live admin popups are more reliable when a customer opens the document.
- Added invoice-viewed engagement alerts to the hosted customer invoice page. Token-based invoice opens now log an
Invoice viewedsystem event in the customer thread, update the invoice's first-viewed timestamp, and raise the same live popup plus alert-bell notification pattern used for proposal and quote views. - Expanded outbound email history in the Messages thread. Resend email webhooks now keep first/latest open timestamps, first/latest click timestamps, open counts, click counts, and the latest clicked URL on the outbound message metadata, while the thread UI shows those details directly under the email bubble instead of only a single
OpenedorClickedbadge. - Hardened Resend status syncing so later open events no longer downgrade a previously clicked email back to
Opened. The email row now keeps the higher click state while still counting each additional open that Resend reports. - Made those customer-engagement events much more visible inside Messages. Quote, proposal, estimate, maintenance proposal, and invoice views now render as highlighted activity cards in the thread, and outbound emails now show a dedicated engagement panel for email opens and tracked link clicks instead of leaving that history buried in tiny status text.
2026-03-30
- Hardened the Twilio phone-call rollout after the first deep audit. Inbound conference callbacks no longer misclassify successful conversations as missed calls, voice recordings now post to the dedicated recording webhook instead of disappearing into the generic status callback, completed-call automations no longer fire on no-answer outcomes, voicemail drops now fail closed when a live human answers, and the legacy auto-dial migration now carries forward usable phone visibility before the old table is retired.
- Added Phase 1 Twilio phone calling across CleanEstimate Pro. The shared Twilio number can now receive inbound customer calls, forward them to the office line, record voicemail, log call and voicemail events into the unified Messages timeline, and raise missed-call or voicemail alerts for the team.
- Added Twilio voice support to the automation engine. Workflows can now trigger from
Call Received,Call Answered,Missed Call,Voicemail Received, andCall Completed, and they can execute liveMake CallandLeave Voicemailsteps instead of treating those call nodes as unsupported placeholders. - Expanded the Phone / Voice settings and docs so operators can configure forwarding numbers, voicemail greetings, voice readiness, recording, and missed-call alerts from the same Twilio integration area used for SMS.
- Fixed customer-engagement alerting around sent quotes and invoices. Proposal, estimate, and invoice emails now log under the live Resend provider id used by outbound delivery, older mismatched rows can self-heal when Resend open webhooks arrive, and the admin alert bell plus live popup feed now raise a new alert every time a customer opens the email or opens the proposal page itself across residential, maintenance, fleet, commercial building, and shared quote flows. Public quote and proposal pages now also ignore common crawler and link-preview traffic so bot fetches do not create false viewed alerts or premature viewed-state changes.
- Fixed the new asphalt module entry path so it always opens the live editor. The canonical
/admin/asphalt-maintenanceroute now forwards into the working asphalt quote builder, the admin route-permission guard recognizes that module path instead of redirecting it back to the dashboard, Quick Start uses that stable module path, and global search keeps a separate New Asphalt Quote action for the explicit create flow instead of leaving some clicks or bookmarks on a missing landing page. - Fixed the next asphalt send regression for older shared-manual drafts. When a generic or asphalt quote is missing its persisted
public_quote_token, the send stack now seeds and saves that shared quote token before email/SMS delivery, so older drafts can still send with a working shared quote link. If secure signing is not configured, the customer still gets a view-only link instead of the office seeing a false "Secure estimate links are temporarily unavailable" error. - Fixed another customer-link delivery failure behind estimate sends. Quote, proposal, maintenance-plan, and shared quote link generation now fall back to the configured public site URL when the older dedicated app URL env is blank, so sends keep working instead of throwing a generic secure-link error during production config drift.
- Followed that send hotfix with the remaining link and state-hardening pass. Holiday-lights sends now use the same public-site URL fallback as the other customer-link flows, and shared quote token repair now merges against the latest stored delivery state before it writes so newer payment or send-tracking metadata is not wiped while older drafts self-heal.
- Fixed the next estimate-send troubleshooting blind spot. Office send errors no longer collapse unrelated delivery failures into the generic secure-link warning, so missing proposal data, missing maintenance plans, and email-provider setup issues now surface their own actionable messages while true customer-link setup problems keep the secure-link error.
- Fixed the actual generic and asphalt send blocker behind that misleading warning too. Quote delivery no longer selects rollout-only pricing columns like
minimum_adjustmentandtaxable_amountfrom the liveestimatestable before building the email, so workspaces on older production schemas can send quote emails again instead of failing the send before Resend is ever called. - Removed the last false secure-link warning on shared quote sends. If a quote already has a persisted public quote token, CE Pro now treats that shared
/quote/[token]route as fully configured even whenCUSTOMER_TOKEN_SECRETis missing, so successful generic, asphalt, residential, fleet, commercial building, and holiday-lights sends no longer tell the office the delivered link is only "view-only."
- Hardened the new automation visual builder after the first live review pass. The canvas Wizard button and mobile fallback now route back into the real workflow edit screen instead of a dead
/wizardURL, new drag-and-drop nodes save with UUID-safe ids, graph saves no longer briefly wipe all edges before rewriting them, and the save badge now shows Save failed correctly when a graph write errors instead of getting stuck on a stale Saved state. - Rebuilt the automation visual builder into a real graph editor instead of the old loose card pile. Workflows now return step positions and edge labels from the API, save the full graph through one dedicated graph endpoint, auto-arrange into a readable layered layout when positions are missing, preserve manual drag edits after save, and expose clearer canvas save states plus Auto Arrange, Fit Workflow, and Reset View controls. CE Pro also now surfaces the visual builder much earlier through Open Visual Builder on the new automation flow and Build Visually on template cards instead of hiding the canvas until after a workflow is already saved.
- Built and activated a shared Estimate Accepted automation flow through the production automation builder so accepted estimates now trigger a customer thank-you email, a customer thank-you text, and an internal follow-up task for scheduling and next steps from one shared workflow instead of requiring per-module handoff rules.
- Fixed the automation workflow detail page for newly created workflows. The single-workflow API now returns the same run counters and last-run metadata the overview cards expect, and the detail view now falls back safely if those metrics are still empty on a brand-new workflow.
- Fixed the last live signature-save failure on commercial proposal acceptance. Fleet and commercial building proposal signatures now recreate the missing proposal-signature storage bucket automatically before retrying the upload, and CE Pro now has regression coverage across all three signature pipelines: shared estimate/quote signatures, commercial proposal signatures, and holiday-lights contract signatures.
- Fixed the last shared quote acceptance-auth gap across estimate types. Shared
/quote/[token]pages for residential, proposal packages, maintenance plans, fleet, commercial building, holiday-lights linked estimates, generic quotes, and asphalt quotes now treat the saved public quote token as a valid interaction token for signatures, package selection, maintenance acceptance, public PDFs, and deposit actions, so customers and office previews no longer see a false "online approval unavailable" state just because the server could not mint a separate customer-token URL. - Fixed the last public-acceptance preview mismatch across estimate types. Office-side View Public Page actions for fleet, commercial building, holiday-lights linked estimates, and the older raw estimate/proposal/maintenance routes now resolve the same valid customer-link contract used in sent emails and texts, so internal previews no longer fall into a read-only "online approval unavailable" state just because they opened from an older module URL.
- Closed the last two module rollout seams found during a live cross-module smoke pass. Residential estimates now stay compatible with older
addon_structuresschemas that do not exposecreated_atyet, and holiday-lights draft saves now keep the shared estimate-list projection writingdelivery_stateas JSON so seasonal quotes no longer fail on partially upgraded production databases. - Fixed the fleet proposal create path after the quote-core rollout for older production schemas. Fleet and commercial proposal list-projection sync now write
delivery_stateas JSON again, so first draft saves from Admin > Fleet > New Proposal no longer fail on workspaces that still had the older estimate-list trigger definitions in place. - Tightened that fleet launch flow again so proposals opened from a lead now preload the lead source into step 1 and carry it through the first draft save automatically instead of dropping the source unless staff reselected it by hand.
- Launched the Asphalt Maintenance module across the product and website. Teams can now create asphalt quotes from the office navigation, Quick Start, and global search, reopen them in a dedicated asphalt editor, publish the live asphalt marketing page, and keep asphalt quotes on the shared quote-core send, PDF, payment, and customer-link path instead of hiding that workflow as a proof-only slice.
- Fixed the last shared-manual quote hardening gaps that surfaced during the asphalt rollout. Asphalt edits now fail closed instead of silently flattening back into generic quote data, untaxed shared manual quotes stay untaxed when reopened, maintenance acceptance copy uses the workspace brand instead of a hardcoded provider name, and the shared quote delivery-state alignment migration now handles already-JSONB projection rows safely during deploy.
- Refined the admin workspace chrome again so XP now lives in the left sidebar between the workspace section and Quick Start on every admin page, while the page-level header keeps Search everywhere and only shows the duplicate Help and Alerts actions inside the content area on desktop. Mobile pages now rely on the fixed top bar for Help and Alerts instead of repeating those bubbles again inside the page body.
2026-03-29
- Started the asphalt maintenance proof slice for the unified quote core. CleanEstimate Pro now has a registered
asphalt_maintenancewizard plugin plus shared global baseline price-book items for single-coat sealcoat, double-coat sealcoat, crack fill, line striping refresh, and patch repair, which proves a net-new estimate type can plug into the shared quote core without adding a new send stack, payment stack, public page stack, or quote tables. - Extended that asphalt proof slice into the shared manual-quote persistence path too. The asphalt quote builder can now prepare a real shared quote-core payload with
wizard_key = asphalt_maintenance, shared payment/send/public compatibility, wizard-specific measurement snapshots, and asphalt document/review metadata without introducing a new estimate route or another bespoke quote save service. - Finished the next asphalt proof read path too. Office estimate lists, type counts, type filters, and estimate-detail edit actions can now surface wizard-backed asphalt maintenance quotes as their own estimate type instead of collapsing them into the generic quote bucket, while still routing those rows back into the shared quote editor correctly.
- Tightened the asphalt proof edit/save round-trip. Reopened asphalt quotes now reload their description and internal notes from the
asphalt_maintenancemodule namespace correctly, and asphalt canonical line items keep their ownsource_wizardplus sharedprice_book_item_idlinkage instead of being flattened back through the generic quote normalizer during save. - Tightened the asphalt proof shared-quote lane too. Asphalt maintenance quotes now stay on the same shared manual quote path for customer links, public quote rendering, pricing normalization, send-time generic detection, and PDF selection instead of depending on
generic-only checks that could miss wizard-backed manual quotes. - Finished the last shared-manual asphalt office blind spots. Asphalt-backed quotes can now reopen through the shared office quote editor without failing the old
estimate_type = genericfilter, the shared estimate create/update routes now keep asphalt on the shared manual lane instead of falling back to residential-only assumptions, and office-side send copy now uses asphalt-specific quote wording when staff send those quotes from the shared editor. - Tightened that last shared-manual send layer again so asphalt quote sends now record
asphalt_maintenance_quotein shared delivery history and quote events instead of being mislabeled as generic, and shared manual-quote delivery now falls back to a neutral sender label if the workspace name cannot be loaded instead of defaulting to another provider's brand name. - Fixed another final quote-core data-contract batch: shared public quote token lookups now use the indexed quote-token expression instead of a slower JSON containment scan, the estimate-list projection now stores shared delivery state as JSON so payment fallback badges keep reading the live quote payment metadata, asphalt zero-footage drafts no longer create a phantom
$0sealcoat row, and canonical total math now preserves non-taxable line-item value when a taxable subtotal is supplied separately. - Fixed the shared price-book settings rollout for existing workspaces. Older orgs now backfill their active residential pricing under the same nested
residential_pricing_configkey the quote-core readers expect, so the move into shared price-book settings preserves current pricing instead of silently snapping those workspaces back to the platform defaults. - Extended quote-core payment tracking on estimates. Stripe pay-link creation, successful estimate payments, and office-recorded estimate payments now write shared quote payment metadata and payment-state history onto the canonical estimate record, which keeps shared quote links, office payment follow-up, and quote event history aligned with the actual balance state.
- Hardened that estimate payment sync again so it now merges against the latest shared quote delivery state before it writes. A payment update no longer risks overwriting newer shared quote-link or send-history metadata that landed a moment earlier on the same estimate.
- Finished the next estimate-payment unification step by teaching the public estimate page, shared quote page, and office estimate list to fall back to the shared quote-core payment state when the older flat estimate payment columns are stale. That keeps payment badges and pay-link actions aligned with the latest quote delivery/payment history during the rollout.
- Accepted residential maintenance agreements now keep their linked recurring templates aligned after later office edits too. If the team changes an already accepted maintenance estimate, CE Pro resyncs the recurring job-template series from the updated maintenance plan instead of leaving future recurring visits on the older scope.
- Removed a redundant second residential package-offer hydration during customer package selection. Signed package accepts now validate from one shared offer-core pass instead of doing the same database rehydrate twice inside the public proposal-selection flow.
- Pricing Manager saves, restores, onboarding pricing setup, and org bootstrap now all keep the active residential pricing config mirrored into the workspace default price book. That means the shared quote-core price-book layer stays current immediately after an office pricing change instead of waiting for a later catalog repair to catch up.
- Fixed another holiday-lights quote-core reporting gap. The first customer open on a holiday-lights quote now advances the seasonal source quote itself from Sent to Viewed instead of only stamping the viewed timestamp, so holiday-lights list filters, viewed-status dashboards, and seasonal reporting stay aligned with the real first open.
- Tightened that holiday-lights lifecycle again so Viewed quotes still stay resendable from the office, shared
/quote/[token]opens record as shared-quote opens instead of portal-only opens in the seasonal activity trail, and traced zone-based draft edits no longer fail just because the manual linear-feet field was left at zero. - Continued the shared offer-layer migration for residential quotes. Customer dashboard links, customer estimate-list links, portal proposal lookup links, and office resend/SMS offer routing now all classify residential package proposals versus maintenance offers through one shared offer rule instead of each surface re-checking the old parent estimate JSON on its own.
- Started the next shared offer-layer ownership cut for residential package proposals. Customer package selection now rehydrates proposal options from the mirrored
estimate_offer_groupsrecords before it validates the request, so signed customers can still accept the right package even when the rollout has already moved those proposal options off the parent estimate JSON. - Pushed that residential offer-layer ownership further into acceptance flows. Customer maintenance-agreement approvals, maintenance proposal PDF preparation, and customer package acceptance now keep the linked child offer quotes in sync with the parent estimate state so later reads, sends, and recurring-template automation stay aligned to the same accepted offer.
- Moved another holiday-lights ownership seam behind the shared quote-core services. Holiday-lights create/edit routes now reuse one shared draft-save path for pricing recalculation, linked-estimate sync, design updates, and bundled power-wash linkage, office or customer Declined / Expired actions now write the matching shared quote event onto the linked estimate instead of stopping at the seasonal source record, and holiday-lights opens through the shared
/quote/[token]page now feed their viewed timestamp and seasonal pipeline move back into the source seasonal quote too. - Reworked the shared price-book foundation so CE Pro can carry one platform-wide default price book and keep each organization's custom default book private. Catalog bridges still write org-local custom books during the migration, but shared fallback reads can now land on the global default baseline when an org has not customized its own book yet.
- Tightened that shared pricing model again so org-specific price-book reads now behave like overrides on top of the shared default book instead of an all-or-nothing switch. If one workspace customizes a shared catalog item, CE Pro keeps that override private to that workspace while untouched shared items still inherit from the global baseline.
- Hardened that overlay model again so shared baseline price-book items still participate in residential lookup and repair flows even after an org-specific sync runs, non-legacy org overrides can replace shared defaults cleanly by name, and hourly fleet vehicle pricing now keeps its minimum quoted price when it round-trips through the shared price book.
- Moved another commercial ownership seam into the shared quote-core layer. Fleet and commercial building draft edits now let the quote services own status-field bookkeeping and linked-estimate sales-transition logging, while commercial proposal send routes now reuse the shared quote-link preparation helper instead of rebuilding customer URLs in each route.
- Followed that pricing-foundation change with a tighter residential catalog write path. Residential service, add-on, and custom-template admin saves now update only the touched shared price-book rows instead of rebuilding the whole residential catalog bridge after each edit, while still rolling the legacy save back if the shared price-book write fails.
- Tightened recurring maintenance-template sync so CE Pro no longer re-ends already-ended recurring templates every time a maintenance agreement is reaccepted or resynced. Historical ended templates now keep their original
ended_ataudit trail unless they are actively being changed again. - Tightened the fleet and commercial linked-estimate bridge again so draft saves now treat canonical quote-core line-item sync as part of the save itself. If the linked estimate cannot keep its canonical line items aligned, the draft save rolls back instead of leaving the proposal tables ahead of the shared quote workspace, and older linked-estimate fallback writes now reuse the existing estimate number instead of creating duplicate orphaned estimates on retry.
- Followed that with another ownership cut for fleet and commercial building drafts. Those draft-save services now own the linked shared estimate write directly and only use the old
sync-to-estimatespath as a compatibility loader wrapper, which keeps the proposal save and the shared estimate workspace on one tighter success-or-rollback path. - The Create Recurring Template shortcut on accepted fleet and commercial building estimates now prefers canonical quote-core line items from the linked estimate workspace and carries the source estimate id into the saved recurring template. That keeps recurring-template prefill aligned with the shared quote-core breakdown, gives later recurring automation a direct quote-to-template link to follow, and surfaces the source quote link directly on the recurring-template list and detail screens.
- Accepted residential maintenance agreements now create linked recurring job templates automatically. CE Pro seeds one recurring template per repeating seasonal visit pattern from the shared maintenance offer quote, rolls the public maintenance-accept action back if that recurring-template setup fails, and records the quote-core conversion so recurring residential work starts from the same canonical quote data instead of a manual rebuild.
- Tightened the commercial catalog bridge so fleet vehicle-type saves plus commercial building service/surface-type creates now fail closed if the shared price-book write cannot be completed. Instead of quietly updating only the old settings tables and leaving
price_book_itemsstale, CE Pro now rolls those admin changes back and surfaces a real save error. - Applied that same fail-closed catalog rule to the residential and holiday-lights admin settings paths too. Residential service, add-on, and custom-template writes, plus holiday-lights inventory creates and edits, now roll back if the shared price-book bridge cannot be updated instead of silently leaving the legacy catalog tables ahead of the unified quote-core catalog.
- Narrowed those shared catalog writes again so fleet vehicle types, commercial building service/surface types, and holiday-lights inventory now update only the touched shared
price_book_itemsrows instead of rebuilding each module's entire shared catalog bridge after every admin mutation. - Tightened the shared commercial quote-link handoff so fleet and commercial building resends now reuse the saved
/quote/[token]link from the linked estimate workspace before falling back to an older signed estimate URL. If the linked commercial source record can no longer be reloaded, the shared quote route now fails closed instead of showing the wrong residential/generic page. - Fixed commercial customer totals so fleet and commercial building proposal views only apply tax when the saved proposal actually includes a tax rate. Blank tax proposals no longer assume a default 6% tax on the public page, and recurring building portfolio summaries now use the saved maintenance visit pricing in the per-visit totals instead of quietly showing the one-time initial-clean price there.
- Moved residential estimate updates from
PATCH /api/v1/estimates/{id}onto the shared residential quote-core save path, so supported API-side updates now preserve canonical quote snapshots, canonical line-item sync, delivery-state metadata, and shared quote events instead of bypassing that newer persistence layer. - Hardened the shared estimate send and status routes before more wizard families move onto them. Holiday-lights sends from linked estimate workspaces now stay on the seasonal delivery helper, office-side estimate status changes now require estimate permissions inside the active workspace, and commercial linked estimates now refuse the wrong shared send path instead of silently using the residential sender.
- Shared estimate and quote public pages now show the workspace organization name or a neutral product fallback instead of a hardcoded Rolling Suds label, so customer-facing public links stay correctly branded across workspaces during the quote-core rollout.
- Expanded the shared quote page into the first commercial linked-estimate slice. Fleet and commercial building linked estimates with persisted public quote tokens can now open through
/quote/[token], and the shared route renders the matching commercial proposal layout instead of forcing those linked-estimate previews back through legacy signed estimate URLs. - Wired holiday-lights linked estimates more directly into that same shared public-token model by carrying the source seasonal estimate id on the linked estimate row, so shared quote-core public links can rebuild the holiday-lights summary when office or customer flows open the linked estimate outside the dedicated portal send path.
- Followed that holiday-lights public-link rollout into delivery too. When a linked holiday-lights estimate already has a persisted public quote token, CE Pro now delivers the shared
/quote/[token]URL in seasonal email and SMS sends instead of defaulting back to the older portal link, while still keeping the portal as the fallback path if the shared quote link is not ready yet. - Tightened shared delivery again so quote SMS sends no longer fall back to a broken unsigned estimate URL when secure customer-link generation is unavailable, and holiday-lights email/SMS delivery events now stay linked to the originating estimate record for cleaner communication history.
- Fixed a fleet quote-core regression that could show optional
$0.00add-ons as real estimate line items. Non-included fleet add-ons now stay out of the shared estimate breakdown unless they actually carry a billed price, while intentionally included add-ons still render as included items. - Fixed commercial building custom-item pricing in the shared quote core so quantity-based custom service rows now derive a real per-unit rate from the saved total instead of copying the whole line total into
unit_priceand breaking downstream math. - Finished another residential offer-layer hardening pass across the summary surfaces. Customer dashboards, customer estimate lists, portal proposal lookups, and the office estimate list now rebuild residential package-proposal and maintenance-plan classification from the mirrored shared offer records instead of relying only on the legacy parent estimate JSON, so those links and badges stay correct during the quote-core migration.
- Fixed one more legacy residential handoff into the shared quote page. Older
/p/[id]public links now preserve whether the customer opened a base estimate, package proposal, or maintenance proposal when they redirect into/quote/[token], which keeps quote-view analytics aligned with the original public context. - Tightened the shared residential quote page so it now behaves correctly in read-only mode. If secure customer-token signing is temporarily unavailable, customers can still open the shared quote link to review pricing and scope, but package selection, maintenance-plan approval, and customization actions stay hidden instead of failing after the click.
- Preserved residential proposal and maintenance-plan analytics when those older public links hand off to the shared quote page. Proposal-option links and maintenance-plan links now keep their own public-view context, so reporting and follow-up history no longer collapse those opens into a generic quote-view event.
- Moved another residential offer-layer read seam onto the shared quote core. Residential customer/public reads can now rebuild proposal-option and maintenance-plan data from mirrored
estimate_offer_groupschild quotes, so those pages stay aligned to the shared offer model even while the older parent-estimate JSON remains in place for compatibility. - Extended that residential offer-layer hydration into customer actions and document reads too. Residential package selection, residential send classification, and maintenance-plan PDF reads now rebuild proposal-option or maintenance-plan state from the mirrored shared offer records before they decide which package flow, send path, or maintenance document to use, which keeps those actions aligned with the shared offer model during the migration.
- Hardened the shared commercial proposal lifecycle helper so accepted fleet and commercial-building proposal writes stay org-scoped just like the shared send path, which reduces the chance of future cross-workspace drift as more commercial flows move onto the quote core.
- Hardened the next Sprint 8 ownership seam too. Fleet proposal PATCH requests now strip unexpected fields before they hit the shared draft-save service, fleet linked estimates keep
lost_notesaligned with the proposal workspace, residential shared price-book lookups now honor org overrides correctly in their item-id resolution path, and commercial resend prep now preserves the same shared quote token plus linked-estimate send history even if the status flip hits a transient failure.
2026-03-28
- Started the remaining commercial catalog bridge into the shared price-book foundation. Fleet vehicle types plus commercial building service/surface catalogs now read through the shared
price_book_itemslayer when available, and the matching admin create/update/delete routes refresh that shared catalog copy after legacy commercial settings writes so the next wizard migrations can stop depending on standalone catalog tables. - Tightened that commercial quote-core bridge so fleet and commercial building linked estimates now preserve the real shared
price_book_item_idon their canonical line items for bridged vehicle and service catalog rows instead of only copying the catalog metadata into the linked estimate snapshot. - Filled in another shared quote-event gap for commercial quote-core migration. Fleet and commercial building draft create/edit saves now write quote-core
createdandsavedevents on their linked estimate records as soon as the linked estimate sync succeeds, so commercial draft activity shows up in the same event stream the generic, residential, and holiday-lights quote flows already use. - Moved fleet and commercial building draft-save ownership into shared quote-core services instead of leaving the full persistence choreography inline in the proposal routes. Those draft saves now complete linked-estimate sync before the API returns, and they roll back more cleanly if the shared estimate workspace cannot be updated, which keeps commercial proposal drafts and their linked estimate records from drifting apart during save failures.
- Moved the public fleet and commercial building signature routes onto one shared commercial acceptance helper. Both proposal types now use the same signature upload, accepted-alert, pipeline, linked-estimate sync, and accepted-estimate follow-up path, which keeps customer signatures resilient even when later follow-up bookkeeping has a temporary issue.
- Moved fleet and commercial building proposal send follow-up onto one shared quote-core helper too. Those proposal sends now refresh the linked estimate workspace before the API returns, and the shared estimate record now writes its sent-event and send-history state from the same commercial send path instead of relying on a best-effort background sync after delivery.
- Moved commercial accepted follow-up onto the shared quote-core path for both customer signatures and office-side won-stage actions. Fleet and commercial building proposals now sync the linked estimate workspace and reuse the same accepted follow-up automation path whether the proposal was signed publicly or marked won from the internal commercial pipeline.
- Moved the first public fleet and commercial building proposal view onto one shared quote-core helper too. Customer opens now record the source proposal view, pipeline movement, linked-estimate viewed status, and shared quote event from one path instead of depending on a best-effort linked-estimate sync after the public page already rendered.
- Started the holiday-lights quote-core bridge too. Linked holiday-lights estimates now normalize through a dedicated wizard plugin and write canonical shared quote-core line items plus quote snapshots, so the shared estimate workspace can carry a structured holiday-lights breakdown instead of only the older compatibility JSON line-item snapshot.
- Followed that holiday-lights bridge with a V2 pricing alignment pass. Zone-based holiday-lights quotes now sync their linked estimate totals from the saved 1-year or 3-year contract term instead of the old midpoint install range, the office editor reopens with the saved contract term and proposal media intact, and holiday-lights design records now keep the proposal media payload alongside the design snapshot.
- Extended that holiday-lights selected-term pricing alignment into the rest of the office surfaces too. Holiday-lights send copy, AI review totals, sales-cycle math, client-detail totals, and revenue / lead-source reporting now use the saved 1-year or 3-year contract price before falling back to the older low/high range fields, so the number the office sees stays aligned with the customer’s actual contract term.
- Moved holiday-lights accepted follow-up work onto the same shared quote-core acceptance helper used by the newer estimate flows. Customer accepts and office contract-sign actions now re-sync the linked estimate workspace first, then reuse the shared accepted-event, pipeline, webhook, gamification, and accepted-job follow-up path instead of keeping a separate seasonal acceptance branch.
- Tightened that holiday-lights acceptance bridge so customer status changes and office contract signing now update the source seasonal quote through the same shared helper too. The holiday-lights sales pipeline records the acceptance from one path, and already-accepted quotes no longer rewrite
accepted_atwhen a contract record is patched later. - Started the holiday-lights inventory catalog bridge into the shared price-book foundation too. The office inventory list and holiday-lights dashboard low-stock count now read shared bridged SKU rows when they exist, and holiday-lights inventory create/update actions refresh the shared
price_book_itemscopy after legacy SKU writes so that catalog can migrate onto the same shared pricing infrastructure as the other estimate wizards. - Moved holiday-lights quote sending onto the same shared review-and-send path used by the other estimate wizards. The admin holiday-lights quote page now opens the shared double email/text modal before anything is sent, seasonal send completion now writes linked-estimate send history and sent quote events from the quote-core path, and holiday-lights sends keep their portal link instead of falling back to the standard residential customer page.
- Moved holiday-lights portal quote viewing onto the shared quote-core path too. When a customer opens the holiday-lights portal from a delivered quote link, the latest open seasonal quote now records its viewed timestamp, public pipeline move, and linked-estimate viewed event from one shared helper instead of leaving those portal opens outside the unified quote send/view history.
- Started the unified quote-core foundation under the existing estimates system and migrated generic quotes onto the first shared slice. Generic quote saves now write canonical quote metadata plus canonical line items, preview/send/open actions all reuse one persisted customer quote link, and the shared
/quote/[token]page is now the source of truth for generic quote delivery and acceptance. - Hardened that first generic quote-core slice so generic quote edits no longer silently reset sent quotes back to draft, quote send/view/accept activity now records as Generic Quote instead of leaking into residential pipeline history, and the shared
/quote/[token]page now stays viewable in read-only mode when customer-link signing is not configured instead of crashing on open. - Tightened the local quote-core foundation before Phase 2 so the new quote tables now carry org-scoped access policies, the three-axis quote fields are written by both residential and synced module estimate writers, generic quote send history only records channels that actually delivered, and the shared generic quote page now shows the workspace organization name instead of a hardcoded brand label.
- Closed another generic quote-core cleanup pass before Phase 2: generic quote creation no longer burns two estimate numbers per save, generic edit saves now preserve numeric estimated-hours values from string-based form posts, tenant scoping was added to canonical line-item rollback reads, and the generic send flow now refuses to deliver a quote when no working customer link can be generated.
- Started the residential catalog bridge into the shared price-book core. Residential estimator entrypoints and the services/add-ons/custom-template admin reads now hydrate through the shared price-book-backed catalog layer, keeping the office and public residential wizard aligned to one normalized catalog surface while the legacy pricing screens remain in place.
- Added the first residential wizard plugin under the shared estimate-core registry and started feeding residential estimate saves into the canonical quote line-item table. Residential create/update still preserve the legacy estimate fields and line-item snapshot for compatibility, but the shared quote core now receives the residential service/add-on/custom-item breakdown too.
- Hardened that residential bridge so estimator page loads now read the shared residential catalog without trying to resync the price book on every render, public residential estimators now merge partial pricing configs with the same defaults used by the office flow, and canonical residential line items now carry real shared price-book item links instead of saving as unlinked shadow rows.
- Finished another residential quote-core hardening pass so estimate edits now persist roof, gutter, and saved measurement-source fields correctly, add-on rebuilds match stored selections by label instead of fragile array position, and PDF/email regeneration waits for the canonical residential line-item sync before it rebuilds customer-facing documents.
- Followed that residential hardening pass with safer rollback behavior when quote-core sync fails during save, and kept template-based custom items linked to the shared residential price-book rows even though editable custom items use runtime-generated row ids in the estimator UI.
- Moved residential estimate create/update onto the shared quote-core persistence layer instead of leaving save ownership inline in the residential API routes. Residential writes now save quote snapshots and quote events alongside the canonical line-item sync, residential send plus public view flows now write the same shared delivery-state and quote-event telemetry used by generic quotes, and API-created residential estimates now land on that same shared quote-core path too.
- Continued the quote-core migration by moving accepted-side effects onto one shared helper for generic and residential estimate flows. Customer signatures and accepted-status updates now share the same event logging, webhook dispatch, pipeline history, gamification credit, and accepted-estimate automation path instead of duplicating those follow-up actions in separate route branches.
- Extended that shared residential acceptance path into customer package selection too. When a signed customer accepts one of the residential proposal options, CE Pro now runs the same accepted-event, pipeline, webhook, gamification, and accepted-estimate automation flow instead of only overwriting the selected package totals on the estimate row.
- Extended the shared quote-core public link model into residential base estimates too. New residential saves now keep a persisted public quote token in
delivery_state, office/customer open actions can reuse the shared/quote/[token]page for the base estimate, and the shared public quote page can now render residential estimates alongside generic quotes while proposal-option and maintenance-plan pages remain on their specialized screens. - Started the residential offer-layer bridge under that same quote-core rollout. Residential proposal options and maintenance plans now mirror into hidden child estimates plus shared
estimate_offer_groups/estimate_offer_membersrecords, and customer proposal-package picks or maintenance-plan decisions now keep those shared offer pointers in sync even though the current residential public pages still render from the legacy JSON fields for compatibility. - Moved the next residential customer-link slice onto the shared quote page. When a residential estimate already has a persisted public quote token, the legacy
/estimate,/proposal,/maintenance-proposal, and unified/p/[id]customer links now redirect into the shared/quote/[token]experience so the customer sees one quote page with the estimate, package-option, and maintenance tabs instead of separate residential public readers. - Pulled another commercial quote-core seam into shared ownership. Fleet and commercial building proposal sends plus public acceptance now reuse the same shared lifecycle writer for source-proposal status updates, sales transition logging, pipeline advancement, and linked-estimate follow-up instead of each path maintaining its own copy of that lifecycle bookkeeping.
- Started the first fleet quote-core migration slice under the existing commercial proposal flow. Fleet linked estimates now normalize through a real
fleetwizard plugin and write canonical shared quote-core line items, so the estimate workspace tied to a fleet proposal keeps a structured fleet service/add-on breakdown instead of only the older compatibility JSON snapshot. - Extended that same quote-core bridge pattern into commercial building proposals. Linked commercial building estimates now normalize through a dedicated wizard plugin and write canonical per-property service line items, so the shared estimate workspace holds a structured building-service breakdown instead of relying only on the older compatibility snapshot.
- Restored the owner-facing Admin > Bug Reports inbox so it no longer disappears when ownership records are reordered, and widened that inbox back to the owner's accessible org scope so bug reports from any managed workspace keep rolling into the same review queue instead of narrowing to only the currently switched child workspace.
- Added direct Residential and Holiday Lights shortcuts to the main Settings hub so those workspace pages are easier to find without adding more permanent items to the left sidebar.
2026-03-27
- Consolidated residential estimates, generic quotes, holiday lights, fleet proposals, and commercial building proposals onto one shared review-and-send modal. The office now edits the email and SMS copy in the same module everywhere, generic quotes no longer fire immediately without review, and the residential plus holiday-lights send routes now honor the edited email/SMS copy instead of silently falling back to their old defaults.
- Fixed the fleet proposal Review & Send margin mismatch so the review summary and AI review now use the saved quote's real fleet pricing, wash-time assumptions, visit schedule, and fleet cost settings. The old hardcoded preview calculator that could show a healthy gross margin while the quote was actually below target is gone, and seasonal proposals now use the full quote mix instead of only one season's visit math.
- Hardened estimate and invoice attachments so uploads now accept a defined set of images, PDFs, TXT or CSV files, and common Word/Excel/PowerPoint documents, then verify the uploaded bytes before saving. Renamed or mismatched files are rejected with a plain-language message instead of being stored anyway.
- Tightened another production error-handling pass across sign-in, review-request creation, estimate-list fallback loading, photo deletes, and attachment deletes. Those flows now keep raw auth, storage, and database details in server logs while the operator sees the shorter action-oriented message.
- Moved Jobs and Invoices tag filters fully into the database join path instead of loading every matching client id into app memory first, which makes tagged list views hold up more cleanly in larger workspaces.
- Fixed the Google Places address dropdown inside the office lead and client dialogs so staff can click an autocomplete suggestion directly again instead of having to use the keyboard to apply it.
- Fixed fleet proposal draft creation when the client is prefilled from a lead or native CRM record. The fleet save flow now creates or reuses the correct proposal-side contact automatically instead of failing until the office switches to a custom manual client, and it no longer rewrites another saved proposal contact just because two records share the same office email or phone.
- Fixed the Fleet Inventory step so active org vehicle types load with the full pricing/category data the wizard expects, while the fleet read path now stays limited to fleet/commercial readers instead of exposing fleet pricing and margin settings to broader estimate-only roles.
- Reworked the admin header search into a more compact trigger so the command palette still opens from the top rail or
Cmd/Ctrl+K, but the workspace chrome no longer leads with the oversized search field. - Fixed seasonal fleet proposal totals across review, customer view, and PDF export. Saved fleet drafts now persist their month-range schedule split, seasonal biweekly service now counts as two visits per configured month instead of averaging to an extra visit, and regenerated proposals keep the same annual math after reopen or resend.
- Reworked fleet proposal PDFs so seasonal pricing now prints as separate standard and alternate schedule sections, while the Scheduling & Service Plan page lists every service month in the split cadence instead of collapsing the whole year into one generic frequency line.
- Fixed commercial building and fleet proposal PDF cover dates so saved date-only proposal records print on the correct calendar day instead of shifting backward by one day in U.S. time zones.
- Added editable commercial proposal default terms in Admin > Commercial > Settings for both fleet wash and commercial building proposals, then wired those defaults into the PDF generators whenever a proposal does not override its own terms. The main Settings workspace and left navigation now expose the commercial dashboard/settings routes more directly, while Admin > Fleet > Settings keeps the fleet-specific setup tabs and links into the shared proposal-defaults editor.
- Hardened the public commercial and fleet signature flow so customers can no longer accept those proposals without a valid saved signature image. Signed-proposal bell alerts now refresh live in the admin header, accepted-alert writes retry before giving up, and downstream activity/pipeline/automation hiccups no longer turn a completed customer signature into a false failed-sign result after the proposal is already accepted.
- Fixed the shared commercial-client detail API for fleet/commercial proposal workspaces so linked proposal-side contacts load again on older production schemas that never had a
proposal_clients.updated_atcolumn. The linked tags/contact card no longer falls through to Failed to load commercial client for those records.
2026-03-26
- Extended every current
trialingworkspace by another 30 days on the live system, using the later of the existing trial end or "today" as the base so already-expired workspaces were restored instead of staying blocked. - Raised the default trial length for newly provisioned waitlist workspaces from 14 days to 44 days, and updated the app's public trial copy to match.
- Fixed Google Maps preview availability so the internal Property Visual, MapMeasure, and holiday-lights Street View routes can fall back to
NEXT_PUBLIC_GOOGLE_MAPS_API_KEYwhenGOOGLE_MAPS_SERVER_API_KEYis missing, while still surfacing the plain-language unavailable message if that public key is browser-only or denied for server-side geocoding / Street View requests. - Added the shared Property Visual workspace to the standalone Generic Quote Builder, so office users can open Street View, satellite imagery, CompanyCam links, and quote-linked site photos from the
/admin/estimates/quoteflow. New standalone quotes can now save a draft in place from the panel to unlock persistent photo uploads before the quote is sent.
2026-03-25
- Fixed the lead-creation dialog so teams with client-management access can create a brand-new customer inline instead of leaving the workflow to build the client first. The same dialog now auto-selects the only saved property when a linked customer has exactly one address on file.
- Fixed the Google Places suggestion handling in lead and client address flows so clicking text inside an autocomplete suggestion no longer closes the dialog before the address is applied.
- Fixed commercial building and fleet proposal launch links that start from a lead record. New proposal pages now honor
lead_idas well asclient_id, so the customer preloads correctly on step 1. - Stabilized the admin Estimates list first render so date and stale-follow-up badges hydrate deterministically instead of tripping the production React mismatch error on page load, while keeping the displayed estimate date on the office user's local calendar.
2026-03-22
- Added a dedicated Supabase region-migration guide plus repo-side cutover, audit, and validation runbooks so the Oregon-to-Virginia project move has an explicit checklist for auth settings, mobile config, storage buckets, edge functions, and the post-cutover stress rerun.
- Fixed two stress-test regressions on the admin office data layer: lead-source admin reads no longer request a non-existent
updated_atcolumn, and Jobs/Invoices pagination now returns an empty page instead of a500when the requested offset is beyond the currently available rows. - Followed that pagination fix with a production-shape compatibility patch so the empty-page guard also catches the real PostgREST
416 Range Not Satisfiableresponse format returned by Supabase. - Added a repeatable developer stress-test harness and runbook for the core office hot paths, including the server-rendered admin dashboard/list pages, paginated Jobs and Invoices APIs, cached reference-data reads, and the health/queue checks we now rely on for scale validation.
- Followed up on the Phase 2 async-delivery pass so queued recovery, setup-link, account-reset, and manual admin-email jobs now retry on transient provider failures instead of dead-lettering after a single blip once the user already received a success response.
- Tightened the new direct-upload attachment surface so invoice attachment reads and writes now follow billing permissions instead of estimate permissions, which closes the cross-surface access mismatch between estimate-only and billing-only roles.
- Hardened the Phase 4 paginated Jobs and Invoices list helpers by sanitizing free-text search terms before they are interpolated into PostgREST OR filters, preventing punctuation-heavy searches from breaking or reshaping the filter expression.
- Scrubbed the remaining enqueue-failure email logs so forgot-password, setup-link, account-reset, and admin-email queue failures stop writing plaintext recipient addresses into structured logs.
- Followed up on the Phase 4 scale pass by hardening the root request proxy against injected active-org context headers, so request-scoped org hints are now stripped from inbound traffic and only re-applied inside the trusted admin proxy flow.
- Tightened the baseline
/api/admin/*middleware throttling again so those buckets now key off the authenticated admin user when one is present, which prevents one fast user behind a shared office IP from rate-limiting everyone else on the same route shape. - Fixed a cached lead-source regression in the admin reference-data layer so active-only lead-source reads stop leaking inactive records back into office pickers and settings screens.
- Removed the invoice-number race from admin single-create, admin bulk-create, and API v1 invoice creation by moving all three paths onto one shared atomic allocator seeded from existing invoice numbers for the current org and day.
- Smoothed the server-hydrated Jobs and Invoices admin pages so their initial React Query payload is treated as fresh instead of immediately double-fetching crews, trucks, and first-page list data on mount.
- Tightened the background-job cron worker to claim a smaller bounded batch per run, and added dedicated created-at indexes for the primary Jobs and Invoices admin list queries so those Phase 4 list pages hold up more cleanly under real office traffic.
- Followed up on the Phase 5 operations pass by making
/api/healthfail fast when Supabase or the internal queue snapshot is slow, instead of hanging until the hosting platform times the request out. - Tightened the campaign delivery worker so one failed recipient no longer aborts the rest of the batch or skips the campaign stats reconciliation step.
- Scrubbed structured email-worker logs so recovery and manual admin-email jobs stop writing full recipient addresses into the production log stream, while still leaving masked recipient detail for debugging.
- Fixed the background-job processor metrics so jobs that fail before they can be finalized are tracked as stuck processing work instead of being misreported as dead-letter completions they never actually reached.
2026-03-21
- Started the Phase 1 platform-stability pass so authenticated admin, onboarding, super-admin, and logged-in customer routes now run through a shared root session proxy instead of relying on scattered session-refresh behavior. That gives the app a more reliable baseline for session continuity as traffic grows.
- Added a baseline middleware rate limit across
/api/admin/*, so every admin API route now inherits a shared protection floor even before any tighter route-specific throttles apply. - Followed up on that root session work so mixed-auth office API surfaces like
/api/email,/api/sms,/api/estimates/*, and/api/holiday-lights/*also refresh browser session cookies when staff reach them from the app, while inbound webhook routes remain outside the proxy on purpose. - Added a new uncached
/api/healthendpoint for uptime and deployment monitoring, and tightened the shared Supabase bootstrap path so missing critical env vars now fail loudly instead of surfacing later as partial runtime breakage. - Added app-wide route and global error boundaries so unexpected render failures now fall back to a recovery screen with retry actions instead of dropping staff onto a blank white screen.
- Tuned that new reliability layer so the baseline admin API limiter now buckets by normalized route shape instead of forcing every office user behind one shared IP bucket, and the shared crash fallback now routes people back to the right product surface instead of always sending everyone to
/admin. - Began the Phase 2 async-delivery pass by moving one-time marketing sends, manual admin inbox emails, forgot-password delivery, setup-link emails, and account-reset emails onto the shared background jobs queue. Those flows now return faster and finish delivery on the minute-by-minute worker instead of waiting inline on email fan-out.
- Added short-lived server-side caching plus tag-based invalidation across the admin services, add-ons, lead-sources, pricing, and tags reads, and added TTL caching to the office dashboard metrics so common admin screens stop hammering the same database queries on every load.
- Followed that hardening pass with stricter admin-boundary and auth work: sensitive admin routes now rely on explicit permission helpers instead of auth-only org membership, new recovery and invite emails prefer the server-established auth callback path instead of the older client token handoff, session timebox and inactivity limits are now active in Supabase config, and the external API v1 CORS behavior is narrowed to an explicit allowlist instead of returning authenticated responses to every origin by default.
- Started the 10K framework work by introducing a shared org-cache abstraction, aligning admin proxy org-selection with the same active-org resolution model the page layer uses, adding hot-path indexes for estimates, jobs, and campaign messages, and increasing the background-job worker duration and throughput budget for the async queue.
- Rebuilt the admin Jobs and Invoices workspaces around paginated query-backed list APIs, moved those pages onto server-first initial loads plus React Query hydration for smoother first paint and cheaper refreshes, and removed the old fixed 200-row ceiling from those two high-traffic office views.
- Tightened that Phase 4 framework pass again so request-scoped active-org hints are validated against the same franchise-aware membership rules instead of being blindly trusted, and the new Jobs and Invoices pagination now pushes search and status filtering through the server query path so records do not disappear just because they are not in the first loaded slice.
- Added a structured logging layer around the background worker and internal delivery jobs, expanded
/api/healthso internal monitors can inspect queue backlog with the cron bearer secret, and documented the incident, monitoring, and backup/restore operating model in the platform runbooks. - Followed up on the Phase 2 queueing rollout by fixing two campaign correctness edges and one auth-recovery edge: active background-job dedupe keys now only block duplicate work while a matching job is still pending or in flight, campaign dispatch stat reconciliation now runs through an atomic database sync instead of an app-side last-writer-wins update, and forgot-password throttling now keys off the normalized email address while collapsing immediate duplicate recovery jobs into one queued send.
- Followed up on the Phase 3 auth-boundary work by separating read and destructive permissions more cleanly: estimate deletes now require
estimates.manage, lead detail reads now use the read-side client permission instead of the lead write tuple, and the API v1 response helper only emitsVary: Originwhen it actually returned a CORS allow-origin header.
2026-03-20
- Added shared custom date-range filtering across the admin Estimates, Leads, Jobs, Invoices, and Pipeline workspaces. Office staff can now use quick presets or open an exact start/end picker instead of being limited to fixed canned windows.
- Added a Jump to date picker on the Schedule board so dispatch can open a specific service date immediately instead of stepping day by day through the calendar header.
- Hardened the customer portal and logged-in customer pages so proposal lookups, estimate history, and message history stay isolated to the correct live provider workspace. Customers who share the same email across multiple companies now see a safe direct-link prompt instead of mixed account data, and proposal search results still load even when secure customer-link signing has not been configured yet.
- Tightened admin invoice payment-link generation so the office only reuses or writes hosted payment links for live invoices in the active workspace.
- Added the same preset and exact custom date-range controls across the Analytics report suite, including Revenue, Margins, Services, Sales Cycle, Geography, Lead Source ROI, Retention, Operations, Schedule, Franchise, Automations, and Holiday Lights.
- Followed up on the analytics date-range rollout so incomplete custom URLs now fall back safely instead of expanding into accidental open-ended ranges, custom typed dates use the same local-day boundaries as presets, and first-load server rendering now matches the shared picker logic across the analytics pages.
2026-03-19
- Fixed the fresh account-recovery and workspace-setup email flow so valid invite and password-reset emails no longer die on the Invalid or expired reset link screen. Recovery, invite, magic-link, and super-admin reset emails now land on a client-side auth handoff that finishes the Supabase session before sending the user into the password form or dashboard, and the expired-link card can now send a fresh access email inline without bouncing the operator through a separate forgot-password page first.
- Followed up on that auth-link recovery rollout so the Need A New Link? retry action on the temporary Finishing Sign-In handoff now keeps the original workspace redirect attached too. Multi-workspace waitlist owners and brand-new team members no longer lose the invited org context if they need one more recovery email from the handoff step itself.
- Closed the last false
500save edge on residential estimate and generic quote creation. First-time draft saves now reload the just-written estimate summary when the insert succeeds but PostgREST does not return the full row immediately, so the office no longer sees a failed-save toast after the draft is already stored. - Hardened the invite and password-recovery flow again for provisioned workspaces. Forgot password is now rate-limited, no longer reveals whether an address is waiting on first-time setup versus a normal account, and expired invite recovery now routes through the shared forgot-password screen instead of firing another access email directly from the reset page.
- Followed up on that auth hardening with another reliability/security pass. Customer-token estimate status updates are now write-scoped to the estimate's owning org, office-side estimate signing is rate-limited too, and pending-invite recovery now falls back to the standard reset path instead of surfacing a server error when the auth-admin lookup has a temporary hiccup.
- Tightened team-access resend safety so the system verifies the generated auth user matches the membership before any workspace access email is sent. That closes a bad-edge case where a mismatched setup link could have been delivered before the guard tripped.
- Fixed commercial proposal draft persistence so the selected sales rep now saves with both fleet and building proposal drafts, and brand-new building drafts keep their property rows and service line items on the first save instead of dropping that scope until a later edit.
- Hardened commercial proposal client creation on both fleet and building draft POST routes so new proposal-side client records are normalized and validated before they are inserted. Malformed emails or bad client payloads now fail as clean input errors instead of quietly saving broken proposal contact records.
- Fixed the admin Jobs tag filter for larger workspaces so tag searches are scoped before the 200-row list cap. Offices with more than 200 jobs now get the full matching tagged set instead of a silently incomplete subset from only the newest rows.
- Hardened the public estimate customer-action flow so only live sent/viewed proposal links can be accepted, declined, or signed. Draft estimates no longer jump straight to an accepted state just because someone has a token, and the public-route auth test suite now covers the estimate status endpoint too.
- Fixed invited workspace access again so waitlist-provisioned owners and brand-new team members who click Forgot password before completing first-time setup now get a fresh workspace setup link automatically instead of a generic reset email that cannot finish activation.
- Fixed the Bulk Schedule job write path so newly created jobs are marked for the same live environment that the Admin Jobs list reads. Bulk-created jobs now appear immediately in Admin > Jobs instead of being hidden by the list filter.
- Fixed the admin Jobs list API so the page no longer crashes on workspaces where the old
jobs.tagscolumn is absent. Jobs load again, recently created work reappears immediately in Admin > Jobs, and the tag filter continues to use the linked client tags instead of a missing job-side field. - Fixed the shared linked-contact tags card on commercial building proposal detail pages so stale or deleted linked contacts no longer throw a false Client not found toast during page load. Those proposals now open cleanly and show an unavailable state for the missing linked record instead of interrupting the office with an error toast.
- Fixed the shared analytics export picker so the print-friendly export no longer downloads with a fake
.pdffilename. The analytics export menu now labels that option as Print, and downloaded files keep the correct server-provided name and.htmlextension for browser printing or Save as PDF. - Fixed commercial and fleet proposal deep links that start a new quote from an existing client. New proposal pages now honor the incoming
client_idon load, prefill the selected client on step 1, and avoid the loader/invalid-id failures that previously showed up when staff launched commercial or fleet proposal creation from a client shortcut. - Fixed admin team invite and resend emails so the generated setup link keeps the invited workspace id in the redirect path, and access-email sends now fall back to a recovery-style setup link when the direct magic-link path is unavailable. Team members now return to the same admin workspace that sent the invite instead of falling back to a generic
/adminredirect or a failed resend toast. - Fixed the admin invoice detail API so invoice pages no longer depend on a removed
customer_phonecolumn just to load. Older invoice records now open reliably again, which also keeps the invoice-side payment workspace reachable instead of falling through to a false Invoice not found state. - Fixed the admin-side estimate acceptance preview so office users can complete the same View Public Page signature flow without the sign step silently failing just because they are signed in as staff instead of following a customer-token link. Accepted estimate status changes are also more resilient now, so a follow-up automation or sales-pipeline logging hiccup no longer makes the UI report a failed save after the estimate status already changed.
- Fixed the shared office Collect Payment modal width on estimate detail so the payment workspace now honors its full desktop frame instead of collapsing into the small default dialog size and squeezing the left-side payment setup pane into unreadable vertical text.
- Fixed the standard estimate send and resend flow so CE Pro now confirms email delivery before it reports success, and already-sent quotes can be resent without tripping the old draft-only guard that produced "Quote updated but failed to send" after a legitimate retry.
- Fixed the commercial building proposal send flow so the review step now confirms the email destination after a successful delivery, and follow-up pipeline/history sync issues are surfaced as warnings instead of a misleading hard failure after the client email already went out.
- Fixed the commercial building client picker and linked tags flow so native CRM contacts can be swapped with Change during edits without crashing the wizard, and older building proposals tied directly to native clients no longer show a false Client not found error on the detail page.
- Fixed the commercial building proposal wizard so the Property Type, Primary Surface, and service-catalog pickers load again on both new and edit flows instead of failing behind silent 500s. The review step now also includes an explicit Save Draft button, and the send area explains whether the draft or client email is the missing requirement when the email action is disabled.
- Fixed the commercial building catalog loaders so proposal editors with the broader commercial proposal permissions can still see the Property Type, Surface Type, Service Type, and pricing-setting options. When a saved workspace catalog still fails to load, the wizard now falls back to built-in building defaults instead of leaving those dropdowns and the service catalog empty.
- Fixed the residential estimate wizard so client-prefilled New Estimate links now keep the selected CRM client attached through save instead of dropping the known
client_idand trying to recreate the customer during estimate creation. - Fixed office-side residential estimate send fallback when secure customer-link signing is not configured. Staff can now still send the estimate email with the PDF attached, and the send flow returns a clear warning that the browser-based estimate link is temporarily unavailable instead of blocking delivery with a
CUSTOMER_TOKEN_SECRETsetup error. - Fixed invoice send retries for workspaces that still do not have secure customer-link signing configured. The office can now send the invoice email anyway, with any stored billing links included when available, instead of the whole send flow stopping on a generic setup failure.
- Fixed the invoice detail payment workspace so long customer names and emails now wrap cleanly on the page, and the invoice-side Collect Payment keyed-card flow no longer trips the intermittent office crash caused by the dialog re-registering its Stripe submit callback every render.
- Fixed the Manage Billing button so the Stripe portal return URL is built from the current request origin instead of a potentially stale app URL setting. That removes the "Not a valid URL" failure mode when admins open the billing portal.
- Fixed the mobile Messages layout so the inbox uses mobile-safe viewport sizing and long customer names no longer overflow the thread header on smaller screens.
- Fixed the analytics export whitelist so Retention, Operations, Schedule Efficiency, and Franchise reports now export through the shared CSV/PDF endpoint again instead of returning a 400 for valid report types.
- Fixed the shared analytics export button so failed report exports now show a visible error message instead of silently doing nothing when a report-specific export throws. That gives the office immediate feedback on Retention, Operations, Schedule Efficiency, Franchise, and any other shared analytics export path that cannot generate a file.
- Fixed the customer estimate acceptance flow so signed estimates now keep the accepted state even if follow-up automation has a transient failure, and customer request-changes notes now stay attached to the rep-facing estimate record instead of getting dropped during the save fallback path.
- Fixed the waitlist join flow so duplicate submissions now return a retryable conflict instead of locking the form into a success state. Users can try a different email after a failed join attempt without refreshing the page.
- Fixed the holiday-lights send queue so admin quote sends now stay on the holiday-lights email and SMS delivery path instead of falling back to the residential estimate sender behind the scenes.
- Fixed holiday-lights proposal sending so the office now gets a real email-delivered confirmation before CE Pro reports success, failed sends stay retriable instead of silently flipping the quote to sent, and the header action becomes Resend Proposal after the first successful delivery.
- Hardened the measurements and recurring-template admin routes so load/save/generate failures now show plain-language errors instead of raw database messages, while still preserving the expected not-found and validation statuses for the user.
- Hardened the mobile payment, public invoice checkout, and Stripe billing-portal error paths so staff and customers now see plain-language payment or setup guidance instead of raw provider and Stripe exception text. Safe business-rule feedback like missing records or invalid amounts still stays readable, while the detailed failure context now stays in server logs.
- Fixed the shared office Collect Payment dialog so long invoice labels, customer names, and receipt email text now wrap inside the modal on invoice detail instead of overflowing past the dialog frame.
- Fixed invoice collect-payment retry behavior so invoice pay-link actions reuse an already stored invoice link instead of failing when the customer-token setup path is unavailable, which also keeps older invoices resilient during send/link retries.
- Updated the Workiz setup flow so the copied value is now just the webhook endpoint while the org-specific webhook key is copied separately. That keeps the secret out of the copied URL/query string while still letting teams assemble the full Workiz callback URL when they configure the integration.
- Fixed a late admin review batch across job detail editing, refunds, client updates, promotions, and holiday-lights estimate sidecars. Job detail no longer opens in a false dirty state with the Save button stuck on, Stripe refund actions now write an internal adjustment trail before the gateway refund is issued and failed Stripe refund attempts now mark that pending trail as failed instead of leaving it hanging, client detail updates fail safely instead of returning a truncated row after a refresh error, missing promotion deletes now return a real not-found response instead of silently succeeding, and holiday-lights sidecar creation now uses the same narrower config read path as the main holiday-lights estimator flow while keeping later client-link backfills scoped to the same workspace record.
- Improved another backend throughput pass for automations, drip marketing, Workiz sync, and proposal expiration jobs. Matching one inbound event to multiple workflow runs now executes those runs in parallel instead of serially, large drip campaigns process due recipients faster once each message claim is secured, Workiz lead backfills upsert each page concurrently, and the nightly proposal-expiration sweep now preloads loss stages per org before it advances expired proposals.
- Tightened another stability pass across billing, integrations, and estimate editing. The Billing page now keeps its main subscription controls visible even if invoice-history, referral, or AI-usage side data has a temporary loading failure; Workiz settings no longer leave the full inbound webhook secret sitting inline on screen; revoked external API keys now flip inactive immediately in the admin UI; and estimate detail status changes now surface the actual save failure instead of silently falling back to a generic toast.
- Continued the admin API hardening pass across clients, schedule, commercial proposals, jobs, inbox threads, holiday-lights routes, and automation/follow-up flows. These routes now keep raw database/provider errors out of the UI while still logging the detailed failure context server-side, and a broad set of high-traffic reads now fetch explicit columns instead of whole rows by default.
- Shipped the missing public v1 estimate update path so
PATCH /api/v1/estimates/:idis now a real idempotent endpoint instead of a docs gap. External integrations can now patch estimate customer/contact metadata, address/property details, assignment, draft-or-sent status, source, and expiration through the same scoped API-key model as the rest of the public v1 surface, and the OpenAPI contract plus developer examples now match the shipped behavior. - Fixed a follow-up review batch in brands, operations QC, and workflow scheduling. Brand edits now leave untouched fields alone instead of resetting saved colors or optional settings, checklist-template edits no longer reactivate inactive templates unless you explicitly flip them back on, waiting workflow runs now verify that their wake time has actually arrived before the engine resumes them, and automation Least Loaded assignment now compares only active estimates, jobs, and leads instead of lifetime historical totals.
- Fixed a follow-up review batch across automations, estimates, AI review, and recurring templates. Automation Assign User now distinguishes stable round-robin routing from real least-loaded routing based on current assigned leads/jobs/estimates, delayed workflow runs resume from Waiting correctly, bulk estimate status changes now report real batch success instead of a false failure toast, optional admin note fields no longer overwrite untouched values with
null, holiday-lights AI review compares nearby work against the first-year annual contract price when present, and visit-capped recurring templates now count manual skips or catch-up occurrences correctly so they end on time.
2026-03-18
- Followed up on the payments and analytics refactors after review. The office keyed-card flow now keeps the Stripe card form mounted when staff adjust the amount or tip, but it requires an explicit Refresh Card Entry before charging so the final Stripe intent stays in sync, and sales-cycle timing now honors commercial decline timestamps when they exist instead of stretching loss timing to a later generic close date.
- Followed up on the latest inbox/dashboard refactor after review. Switching to a different customer conversation now clears the previous draft and resets the compose area before the new thread finishes loading, targeted inbox summary refreshes no longer apply sidebar workflow filters while patching a single row, and the admin dashboard now keeps empty commercial gross-margin cards on a clean dash while the sales rep pace card reflects new estimates created month-to-date instead of only currently sent ones.
- Followed up on the dispatch and inbox refactors after another review pass. The route header no longer shows a misleading stop-level hourly-only label, route optimization guidance is now documented more clearly for truck-days without a saved shop origin, and targeted inbox fallback refreshes now normalize legacy phone and email thread keys before deciding a conversation row is truly missing.
- Fixed the commercial and fleet proposal creation flows so selecting a native CRM contact or entering a new client no longer crashes the building client picker or sends synthetic ids into the proposal save APIs. Building proposals now handle sparse client-name data safely, and both commercial proposal modules now create or reuse the correct proposal-side contact record before saving.
- Fixed the admin estimate send/resend flow so the quote builder now carries the generated customer token through the final send step, and email delivery routes now fail loudly when the mail provider is missing instead of reporting a successful send with no outgoing message.
- Clarified the external API docs so the Developers section now explicitly shows that public v1 integrations can write notes to client, lead, and job records, with ready-to-use
POSTandPATCHexamples for each workflow. - Fixed manager-led first-run onboarding for unfinished workspaces, especially franchise child locations invited through an operator workspace. Managers can now save the Company Info, Services, Pricing, and completion steps during onboarding instead of getting stuck behind generic save failures, and the onboarding UI now shows the actual API error text when a step is rejected.
- Hardened the automation engine's internal bearer-secret handling so delayed workflow resumes, scheduled-trigger processing, direct run execution, and event-to-workflow matching now all use the same secret precedence and timing-safe bearer-token validation. This reduces edge-case failures where one internal automation endpoint could accept a request that another identical engine path rejected in the same environment.
- Added a stricter browser Content Security Policy across the web app and aligned frame protections with the product's actual surfaces. CleanEstimate Pro still blocks external embedding, but the built-in same-origin message drawer can now load correctly while Stripe, Google Maps, Supabase realtime, analytics, and approved embedded panels continue to work under explicit allowlists.
- Standardized high-risk billing, messaging, password-reset, webhook, and super-admin error paths so the UI now shows plain-language setup or action messages instead of leaking raw Stripe, Twilio, Mailgun, Supabase, or database error text. Common business-rule feedback like invalid payment amounts or missing records still stays readable, while low-level diagnostics now stay in server logs.
- Added shared Zod-backed validation for the highest-risk messaging mutation routes, including manual SMS sends, manual email sends, inbox note creation, and conversation ownership/needs-response updates. Those inbox actions now reject malformed payloads consistently before they hit Twilio, Mailgun, or the unified messaging write path.
- Expanded shared validation again across admin team and dispatch actions, including approval requests, handoffs, alert actions, rep availability changes, role-permission saves, territory edits, crew creation, ETA sends, job-status updates, route-day assignments, and crew-job assignments. These admin flows now reject malformed ids, ZIP codes, dates, and permission payloads before they can write partial schedule or settings data, and several of the touched routes now return cleaner setup errors instead of raw database messages.
- Expanded the same validation pass into operations settings, including subcontractors, business locations, equipment records, and equipment maintenance logs. Those forms now reject malformed emails, ZIP lists, dates, rates, costs, and lifecycle payloads before they can save inconsistent operations data, and the touched routes now return cleaner admin-facing errors instead of raw database messages.
- Extended that operations hardening into inventory items plus quality-control checklist and report workflows. Inventory writes now validate categories, quantities, reorder levels, unit costs, and reorder dates before saving, while QC templates and reports now validate nested checklist items, pass/fail states, notes, scores, and photo references before they create report history.
- Hardened another settings/configuration batch covering brands, module-brand assignments, lead sources, and tag creation. Those admin forms now validate slugs, module keys, commission rates, sort order, ids, and tag colors before saving, use safer fallback handling when older tag tables are still rolling out, and return cleaner setup errors instead of raw database messages.
- Tightened another workspace-settings pass covering pricing, email reply slugs, Twilio phone number storage, Google review links, and schedule dispatch defaults. These settings now validate E.164 SMS numbers, custom inbox slugs, Google review URLs, route-origin coordinates, truck-capacity values, and pricing version payloads more consistently, while the settings UI now surfaces the review-link and slug errors directly instead of failing silently.
- Hardened the service catalog admin too. Service, add-on structure, and custom item template saves now validate slugs, ids, prices, sort order, and units more consistently, the matching admin APIs return cleaner setup errors instead of raw database messages, and the Services page now keeps edit dialogs open and shows the actual failure reason when a save or toggle request is rejected.
- Tightened the estimate detail mutation flows for rep assignment, appointment scheduling, and estimate-to-job conversion. Those actions now validate ids, dates, time windows, and override flags more consistently, keep cross-org or archived users from being assigned as reps, reject invalid appointment ranges before they save, and return cleaner admin-facing errors instead of raw database messages.
- Hardened the marketing campaigns, promotions, and legacy follow-up sequence admin APIs too. Campaign drafts now validate scheduled send windows and structured drip-step payloads before saving, promotions now enforce cleaner promo-code/date-window validation and safer admin-side errors, and legacy follow-up sequence/template saves now reject malformed step payloads or invalid business-hour ranges instead of partially overwriting the workflow.
- Tightened the external API key admin flows as well. API key create/update screens now validate environments, scopes, rate limits, and expiration values more consistently before saving, the request-log endpoint now validates log-limit input, and the API key admin routes return cleaner setup errors instead of raw provider or database messages.
- Improved commercial pipeline stage saves so bulk stage edits now validate the full payload before writing and update stage rows in parallel instead of one at a time. Reordering or renaming a full stage board should feel faster now, and malformed stage batches fail cleanly instead of leaving a partial stage configuration behind.
- Tightened the dispatch route-sync save path as well. Daily route timing syncs now validate dates, stop ids, metrics, and map coordinates before writing, coordinate backfills run in parallel instead of nested per-stop awaits, and route-sync storage failures return cleaner dispatcher-facing errors instead of raw database messages.
- Followed up on the same hardening pass with a cleanup batch for edge cases the review caught. Inventory items can now be patched one field at a time without re-sending every create-only field, campaign drip steps reject blank message bodies, legacy follow-up sequence APIs now respect the same
marketing.viewandmarketing.managepermission split as the rest of Marketing, step reads stay tenant-scoped, proposal handoffs now require the same estimate-manage permission as other assignment changes, team approval/handoff routes no longer leak raw database errors, shared API sanitization now still logs unexpected failures even when a route forgets to pass a custom log context, and the public promo-code validator now rate-limits repeated bursts so anonymous code-guessing is harder. - Continued the performance pass in AI and automation analytics too. AI lead-score recalculation now persists refreshed scores in one bulk upsert instead of one row at a time, and the automation analytics sequence-performance panel now rolls up enrollment counts in one org-scoped query instead of issuing a separate enrollment query for every legacy follow-up sequence.
- Finished the same loop-await cleanup in bulk job scheduling. Bulk Schedule now resolves truck assignments in parallel and appends per-truck route-stop groups concurrently, so larger multi-property batches with route mode should create jobs and initial stop order faster before dispatch opens the board.
- Started the inbox performance pass too. The admin messages page now only refetches the open thread when that thread's own messages change, and the communications contact-summary read now uses a tighter recent-message window instead of scanning an oversized backlog on every reload.
- Continued the inbox performance pass with a more targeted live-refresh path. The shared Messages list now refreshes only the contact rows that changed in the common case instead of reloading the entire inbox after every new message, and the communications summary route now looks up only the clients tied to the recent contact set instead of hydrating every client in the workspace just to decorate the inbox list.
- Followed up on the inbox and promotions hardening pass after review. Service-restricted promo codes now stay blocked until the estimate includes a matching service instead of validating when service data is missing, targeted inbox sidebar refreshes now fall back to a full reload instead of dropping a conversation row on a summary miss, and inbox read-marking now catches both normalized thread keys and raw inbound phone values more reliably.
- Cleaned up another commercial admin error-leak cluster. Commercial proposal template and fleet vehicle-type admin actions now return plain-language save/delete failures instead of raw database messages, while still logging the detailed failure context server-side for support and debugging.
- Followed up on those commercial admin routes after review. Fleet vehicle-type edits now return a real not-found response when the record has already been removed from the active workspace, and proposal-template edits now validate PATCH payloads before save so bad field types fail cleanly as input errors instead of throwing inside the route handler.
- Tightened another small admin error-handling cluster around AI settings, AI conversation history, estimate attachments, and estimate photos. Those routes now return plain-language load/save/upload failures instead of raw provider or database text, while the detailed diagnostics stay in server logs for support.
2026-03-17
- Smoothed the missing Google Maps server-key error path so Property Visual previews now show a plain-language unavailable message instead of exposing the raw
GOOGLE_MAPS_SERVER_API_KEY is not configured.config string in the admin UI. - Refreshed the
/adminworkspace with the CleanEstimate Pro brand kit while keeping routes, workflows, and page structure intact. The admin shell now uses the brand blue/slate palette, tighter shared surfaces, brand-safe sidebar states, stronger mono KPI treatment, and updated report/schedule/settings chrome without changing behavior. - Fixed the generic quote and estimate edit save-response path so successful draft saves no longer return false
500errors after the record is already stored, and estimate detail pages now surface customer change requests with submitted totals plus included/removed line items. Manual SMS sends and Stripe billing-portal launches also return clearer setup and provider errors instead of opaque server failures. - Tightened the admin shell again so the top search, XP, and Help/Alerts bubbles now share the same height and visual treatment, the franchise org switcher has stronger contrast in the dark sidebar, and the oversized Operations Command hero on the main dashboard has been replaced with a smaller office snapshot plus direct report links.
- Followed up on the admin shell spacing so the XP, Help, and Alerts bubbles now sit in cleaner individual pills with tighter alignment and less wasted space in the top bar.
- Followed up on the admin shell cleanup by removing the non-essential role, focus, and mode card from the left rail, shrinking XP progress into a compact top-bar pill, and simplifying the sidebar create menu into a single Quick Start dropdown bubble.
- Simplified the admin workspace header by removing the oversized command-center banner. The top of the admin app now focuses on a full-width search bar with Help and Alerts actions on the right, while workspace plan and trial badges moved into compact pills on the Settings and Account pages.
- Updated franchise reporting visibility so it now turns on automatically when a workspace already has franchise hierarchy or multi-location capability enabled, instead of requiring a separate franchise-reporting toggle.
- Updated the first-run onboarding screen to use the shared CleanEstimate logo header and removed the duplicate text-only wrapper so new workspaces land in a cleaner branded setup flow.
- Updated the password setup and forgot-password screens to use the same shared CleanEstimate logo header so the activation and recovery flow matches onboarding instead of showing the older text-only branding.
- Fixed invite and password-setup emails so shared provisioning scripts and resend flows no longer leak
http://localhostlinks outside a real development session. Workspace setup emails now fall back to the production CleanEstimate URL automatically when a non-dev process has a local app URL configured. - Fixed early onboarding for newly provisioned workspaces so starter services, pricing, add-ons, and related defaults now self-heal if bootstrap records are missing. The setup UI now keeps the Services and Pricing steps usable instead of leaving those screens blank or throwing a pricing-save error.
- Fixed early-access workspace activation so waitlist-approved users no longer get dumped back into public waitlist/signup prompts when their original invite link expires or has already been used.
- Waitlist and team-access emails now preserve the invited workspace context more reliably, including a password-setup recovery path for provisioned users who exist in auth but have never completed first-time sign-in.
- Improved auth fallback screens so expired invite and magic-link flows prefill the invited email, route users toward a fresh setup link, and avoid treating provisioned workspace owners like brand-new public signups.
- Fixed the expired setup-link recovery flow again so the reset screen now resends the actual workspace setup path for pending users, keeps the UI on the recovery state after resend, and preserves the target workspace context for waitlist-provisioned owners.
2026-03-16
- Refreshed the core UI across the admin workspace, public marketing site, and mobile app around a shared operator-first visual system. The admin shell now uses a darker command-style sidebar and updated workspace chrome, the homepage showcases the live dashboard/operations/analytics/AI/mobile story, and the mobile tabs now use the tighter card, KPI, and section styling from the same system.
- Updated the public pricing page so every plan card now shows Pricing coming soon instead of public numeric plan prices while still keeping plan names, feature differentiation, and waitlist CTAs visible.
- Expanded the Analytics area into a shared reporting hub without removing the existing revenue, margin, lead source, service, geography, holiday-lights, or automation reports. Revenue now has its own first-class page, and the analytics rail adds retention, operations, schedule efficiency, and franchise reporting.
- Upgraded Margin Analysis with sold-to-job conversion coverage plus clearer callback reporting that separates explicit callback-tracking records from inferred callback signals in job notes.
- Upgraded Lead Source ROI with module filters, rep attribution, upsell revenue, and brand-funded source rollups, and added a new brand-funded toggle to Lead Sources so franchise and ad-fund reporting can use durable source tagging.
- Added retention reporting for healthy, at-risk, dormant, and new accounts, including quick Generate Quote actions that jump into a prefilled estimate flow for the selected client.
- Added operations reporting that combines crew productivity, crew pay totals, AR aging, and overdue invoice reminder actions using the same AI follow-up flow already available from invoices.
- Added schedule-efficiency reporting for daily production, schedule fill rate, on-time performance, and reschedule/cancellation breakdowns.
- Added franchise-gated reporting for royalty, ad-fund, and territory utilization rollups using the existing organization feature-flag model.
- Limited the admin Marketing area to the approved workspace during tester rollout. Other workspaces now see marketing in read-only mode and cannot create, activate, edit, delete, or send campaigns and promotions.
- Fixed team access links so invited users who already belong to another CleanEstimate Pro workspace now land in the invited workspace instead of getting stuck in the wrong org or being pushed toward waitlist/signup flows.
- Expanded the admin XP leaderboard so each user can now see every achievement already earned on their profile plus the remaining role-track and universal achievements that are still available to unlock.
- Fixed the admin Estimates tab loading path again so the summary RPC stays bound to the Supabase client instance, resolving a server-side
500that could leave the Estimates page blank even when the underlying estimate data was healthy. - Fixed the admin Estimates center so it can fall back to the unified
estimatestable when the newer estimate-list projection objects have not been applied in the database yet, restoring list loading and search during rollout windows. - Hardened admin job detail loading so optional Crew Pay and assignment lookups no longer turn the whole page into a
500when newer payroll tables are missing from the live schema cache. - Hardened invoice creation and invoice detail loading around secure customer-link signing. Draft invoices now still generate even if customer-link signing is not configured yet, and invoice detail pages stay readable instead of failing outright.
- Updated invoice send and payment-link actions to return a clear setup error when secure invoice-link signing is unavailable, instead of a generic server error.
- Improved the billing portal error path so Stripe customer-portal configuration or customer-record problems now surface as a temporary availability/setup message instead of an opaque failure.
- Added rollout-safe fallbacks for missing payment and crew-pay relations in the live schema cache so office billing and job workflows degrade gracefully while databases catch up.
- Finished Franchise Phase 4 beta hardening with 15-minute franchise summary refreshes, durable org-switch and cross-org franchise audit logging, franchise plan gating for parent-workspace management, descendant-read RLS coverage on the core org-scoped workflow tables used in the beta, and live-only franchise summary rollups.
- Added the first self-serve franchise admin workspace on top of the beta hierarchy foundation, including the admin sidebar org switcher, franchise overview, descendant workspace roster, child-workspace detail screens, child-workspace user invites, operational reports, and the new Settings --> Franchise page for rollout guidance.
- Added the Phase 2 franchise beta APIs for active workspace switching, parent-org overview, child-org management, descendant user listing, and child-org invites, plus the new
franchise.viewandfranchise.managepermissions that gate those flows. Parent org admins can now keep inactive child workspaces visible for management and reactivation instead of losing them from the hierarchy tree after deactivation, and successful workspace-switch responses now tell the admin client to refresh session metadata immediately. - Laid the franchise hierarchy foundation in the database by adding parent/child org metadata, organization relationship records, scoped multi-org membership fields, reusable hierarchy traversal helpers, and locked-down relationship-table access for upcoming franchisor, operator, and multi-location account management.
- Reconciled the developer docs, navigation, examples, and changelog around the actual public
/api/v1surface. The shipped external API remains the core workflow resources plus webhook management; the previously documented proposal and reporting endpoints are deferred and are no longer presented as live production routes. - Fixed franchise bug-report visibility so parent workspaces can review descendant workspace bug reports from the parent dashboard instead of only seeing reports created directly inside the active workspace.
- Hardened franchise workspace switching to accept readable workspace slugs as a rollout-safe fallback when a stale browser session submits an older identifier format.
- Hardened franchise workspace switching again so stale browser sessions can also recover when they submit an exact readable workspace name instead of the current UUID or slug format.
- Fixed franchise workspace switching for legacy root workspaces whose ids are UUID-shaped but not version-stamped UUIDs, so operators like Lancaster can switch back from child workspaces without hitting
Invalid workspace identifier.
2026-03-15
- Added crew tip collection and visibility across invoices, admin payment collection, mobile job payments, and the Crew Pay dashboard. Customers can now choose 10%, 15%, 20%, custom, or no tip from the taxable amount, while cash, check, and card tips stay visible by method in crew-pay reporting.
- Cleaned up the remaining mobile TypeScript regressions in the dashboard proposal list, About screen asset loading, and CRM client-provider mappings so the Expo app compiles cleanly again.
- Rebuilt Crew Pay around versioned policy settings instead of hardcoded crew percentages, adding editable pool rates, solo and leader caps, pay-period and overtime controls, minimum-wage overrides, commission exclusions, crew split templates, route-day hourly-only overrides, and job-level participant compensation editing with callback tracking.
- Followed up on the Crew Pay rebuild with payroll hardening fixes: minimum-wage top-ups now report deficits correctly, hourly overtime only counts hourly-only hours, callback-origin jobs no longer dilute the rest of a capped crew day, route-day save errors surface in the UI, and the new crew-pay policy tables now enforce tenant-scoped RLS plus database-level payout guardrails.
- Added rollout compatibility fallbacks for Crew Pay so the admin crew-pay pages can still render safely while older org databases catch up on the latest split-template and compensation schema changes.
- Fixed the inverted
commissionTopUpformula so minimum-wage top-ups now correctly report employer deficits instead of commission surpluses. - Fixed overtime threshold tracking so commission hours no longer count against the hourly-only overtime threshold, preventing incorrect overtime pay on mixed-pay crews.
- Excluded callback-origin jobs from daily cap group accumulation so they no longer dilute the capped commission pool for other jobs on the same crew-day.
- Added RLS policies and positive-value CHECK constraints to
crew_pay_policiesandjob_compensation_settingsto enforce tenant isolation and prevent negative rates or unbounded commission shares at the database level. - Added commission share sum-to-100% validation in the job compensation editor UI and error feedback on route-day assignment save failures.
- Replaced hardcoded 57/43 lead/tech pay preview splits in the schedule day route view with the active policy's configurable
leaderMaxShare. - Added effective-date future validation to the crew pay policy editor so past-dated policy versions cannot be created from the admin UI.
- Tightened Supabase type safety across the app by introducing strict client factories alongside the legacy loose path, fixing high-value schema drift in the admin dashboard, automations analytics, AI estimate review, chat route, Meta and Angi webhooks, and the holiday-lights customer portal mapper.
- Replaced the dead inbound automation webhook auth path with the external API v1 scoped key model, requiring
webhooks:writescope, SHA-256 hashed key comparison, and timing-safe verification. Existing integrations using the oldwebhook_api_keystable orX-Webhook-SignatureHMAC path must migrate to scoped API keys. - Fixed the
safeHashEqualfunction in the inbound webhook auth path to returnfalseon invalid hex inputs instead of comparing two zero-filled buffers. - Wired the strict middleware Supabase client into the auth session refresh path and replaced the unguarded inline service client with the singleton
createStrictServiceClient. - Added shared JSON boundary helpers for safely parsing
unknownSupabase JSON columns into typed objects, arrays, and scalars without rawascasts. - Added an integration connections helper to replace direct
organizations.integrationsJSONB reads with a dedicatedintegration_connectionstable lookup pattern. - Stabilized the flaky public route auth test and added a real
typecheckscript topackage.json. - Started the Phase A reliability pass by fixing the commercial bulk-scheduling template conversion regression, scoping admin team activity commercial proposal reads back to org-owned proposal IDs, switching CI back to Linux, and aligning the Mailgun inbound email setup docs around Bearer-header auth instead of legacy query-string secrets.
- Followed up on the Phase A review by adding mobile Jest coverage to CI, escaping customer delivery email templates, documenting the required
CUSTOMER_TOKEN_SECRETandGOOGLE_MAPS_SERVER_API_KEYdeployment settings, recording residential MapMeasure applies asproperty_data_source = "mapmeasure", and clarifying that Workiz lead updates stay scoped to the org that owns the sync. - Rebuilt the admin Estimates list and linked client estimate history on a unified indexed read model that combines residential estimates, generic quotes, holiday lights records, fleet proposals, and commercial building proposals, so search, tags, rep filters, source filters, and pagination now run in SQL instead of the old in-memory multi-table merge path.
- Hardened map preview and measurement APIs by moving admin and holiday-lights Google geocode and Street View lookups behind internal server routes, adding burst protection to MapMeasure endpoints, and improving inline visual-panel error states when Google cannot resolve a preview.
- Started the Phase B reliability pass by switching follow-up, automation, SMS, invoice-payment, and legacy proposal-delivery links onto the canonical tokenized customer-link builders, so customer-facing estimate, proposal, maintenance, and invoice links stay valid across web and mobile sends.
- Fixed mobile offline proposal delivery retries so failed background
send-proposalcalls stay queued instead of being marked complete, and made follow-up service merge fields tolerant of both legacy string arrays and the newer structured service-selection payloads. - Finished the next estimate-list correctness pass by filling the missing projection upsert fields, passing estimate type filters through the summary RPC, exposing
entity_typein the admin estimates API, and adding shared proposal-client tags so fleet and commercial proposal records participate in the same tag filters as residential estimates. - Made client-account primary-contact promotion transactional and added shared tag management to fleet and commercial proposal detail pages, so concurrent admin edits cannot leave duplicate primaries and commercial contacts can now power unified estimate-list filtering.
- Followed up on the Phase B account work by preventing non-primary client moves from clearing another account's existing primary contact, and by adding a rollout-safe fallback when the new
set_client_account_primary_contactRPC has not been applied yet during deployment. - Finished the client account-integrity pass by moving contact-account membership changes onto a transactional RPC path as well, so primary-contact promotions and account moves no longer depend on a separate post-update reconciliation step.
- Fixed the remaining legacy mobile proposal-delivery gap by adding a signed
/p/:proposalIdcustomer-token path for proposals without linked estimates, restoring secure SMS and email delivery for direct mobile-created proposal records instead of leaving those sends stuck on a missing-link error. - Hardened customer delivery links again by moving HMAC signing onto the dedicated
CUSTOMER_TOKEN_SECRET, adding token expiry to estimate, proposal, maintenance, and invoice links, tightening the legacy proposal viewer's PDF allowlist, and fixing portal proposal lookups to filter in SQL instead of silently dropping older customer proposals after an org-wide top-50 cutoff. - Rebuilt the Phase C security pass on top of the shipped Phase B baseline, making shared public rate limiting fail closed by default, keeping public-customer-token and property lookup paths on explicit in-memory fallback only where needed, and finishing the admin auth-wrapper migration for the remaining high-traffic client, estimate, lead, schedule, and marketing routes.
- Locked the admin Property Visual and Street View proxies behind estimate-view permission checks, required the server-side
GOOGLE_MAPS_SERVER_API_KEYfor those routes, rejected non-image Street View upstream responses, and moved the holiday-lights photo designer's Street View fetches onto the same internal proxy path. - Followed up on the Phase C audit by restoring the intended auth-only access model on lead, estimate, follow-up sequence, client-delete, and job-delete routes that still rely on record-level role checks, adding missing org and environment guards to follow-up step replacement, invoice payment-link writes, and schedule completion re-reads, and making public property auto-fill fail closed again when shared rate-limit storage is unavailable.
2026-03-14
- Hardened core security and customer-delivery flows by enforcing admin API permissions server-side, locking holiday-lights AI mockups behind authenticated permissions plus image-size limits, moving inbound email webhooks to bearer-header auth, rate-limiting manual email/SMS/review sends and selected AI actions, and fixing signed customer links for SMS, Stripe returns, and public estimate/proposal access.
- Migrated app email delivery from Resend to Mailgun, including batch marketing sends, inbound reply routing, delivery/open/click/failure webhooks, and the deployment DNS guidance needed to run the new email stack.
- Added MapMeasure to the shared satellite Property Visual Panel so residential, commercial building, fleet, and holiday lights workflows can save geodesic map measurements with live draft totals, keyboard drawing shortcuts, and workflow-aware apply actions.
- Hardened MapMeasure persistence and edit flows with atomic commercial proposal graph saves, safer residential estimate reconstruction on reopen, and saved measurement round-tripping for map-based property values.
- Followed up with additional MapMeasure hardening for org-scoped target validation, draft undo and double-click stability, safer inline CompanyCam previews, and stricter commercial proposal payload sanitizing.
- Hardened background follow-up, drip, and waiting-automation processing with claim-safe batching, restored the intended follow-up and drip cron cadence, and reduced duplicate-send risk during overlapping scheduler runs.
- Rebuilt the public homepage with an estimate-first story, stronger mobile and desktop layout, and a sharper power-washing-specific visual design.
- Updated the shared marketing navigation, footer, and sticky mobile CTA so the public site keeps the same stronger positioning across pages.
- Added a new docs article covering the public website and waitlist experience.
- Shipped and hardened external API v1 with scoped live and test API keys, atomic idempotent writes, stricter state validation, request correlation, rate-limit fail-closed behavior, cursor pagination, durable outbound webhook delivery, and a typed
/api/v1/openapi.jsoncontract. - Followed up with the final external API pre-production hardening pass for exact key-environment verification, live-only webhook queue processing, and additional idempotency guardrails.
- Published Phase 2 proposal and reporting API planning/docs work, but the public
/api/v1contract remained limited to the core workflow resources plus webhook management. - Documented the three supported SMS consent flows in the public privacy policy and mirrored that legal guidance in the docs site.
- Hardened background follow-up, drip, and waiting-automation processing with claim-safe batching, restored the intended follow-up and drip cron cadence, and reduced duplicate-send risk during overlapping scheduler runs.
- Began moving estimate delivery fan-out onto a durable background job queue so send actions can return faster while PDF, email, SMS, and Workiz tasks finish in the background.
- Followed up with the first scalable-monolith hardening pass: estimate sends now return async delivery state details, Workiz sync uses the shared background job queue, org-level estimate send burst protection is enforced in Postgres, and the remaining cron sweepers now use overlap-safe database leases.
2026-03-13
d969ff4Guarded empty automation stats states in the admin UI.e50027eFixed automations admin session fetches.56ffb6eAdded super-admin waitlist provisioning.7ae422cAuto-seeded QA automation workflows.2a90947Shipped automation engine templates and runtime fixes.2007595Added the missing commercial route helper.6b50429Fixed the remaining open bug sweep items.f3d4bd9Finished the remaining open bug sweep fixes.
2026-03-12
f6701abFixed residential estimate draft edit persistence.0f3201fShipped missing shared modules required for Vercel builds.3d016f2Fixed lead, job, payment, and delete UX flows.e739c55Fixed the schedule Google Maps loader conflict.e56bb06Fixed estimate override conversion to jobs.36d9d1eFollowed up with additional estimate override conversion fixes.4eb08e8Fixed automation step availability and fleet save guards.bd0897fSimplified shared flows and removed dead code.019ed8bFixed the automation test route build regression.36e8b21Hardened automations and external URL flows.62de95cFixed the Margin Calculator render path whencostSettingsis missing.fcf71c2Linked Settings to the new Automations area and added a legacy banner.24bd131Addedautomations.viewandautomations.managepermissions.566a121Added the AI-assisted Margin Calculator Engine with optimization suggestions.73c3203Added Automation Engine v2 with a visual workflow builder.1b6e0faAdded bulk multi-property job scheduling.
2026-03-11
65c9bfdRestored navigation discoverability surfaces.e8dbea7Fixed public/ailanding page access.3afe3c5Added hard delete flows for core records.f362481Temporarily disabled SMS sending through an environment flag.bda1ffaRebuilt the schedule dispatch board.466ea66Fixed payment dialog warning text layout.3b26ceaFixed payment dialog overflow behavior.88b642eWidened the payment collection dialog.a94a03eAdded client navigation from lead detail.3a1de78Fixed payment history modal layout.9ca5f56Fixed AI Hub tone and formatting.0103b5bAdded client account job scheduling.ab29c14Fixed AI Hub assistant quick actions.c440734Switched AI lead scoring to legacy-safe columns.0d2e155Hardened the AI lead scoring estimate query.3f8f694Fixed AI lead score recalculation fallback behavior.dadb963Added a shared tag picker workflow.2e763e0Fixed recurring template scheduling.db24460Fixed payment balance carryover and receipts.ddaf665Fixed truck assignment compatibility.3579c1fRequired start and end dates for new jobs.9b12543Removed PWA app prompts.2bf6856Fixed crew and truck job assignment.b7a1b67Polished payment collection dialog layout.7e49990Fixed crew pay modeling and team compensation.bd8b6b8Fixed estimate portal approvals and additional pending bugs.
2026-03-10
0e57f6fFixed bug reporting and client workflows.c771115Dispatched accepted bugs to the OpenClaw runner.f407082Added Telegram notifications when bug issues are created.4f9d925Synced vetted bug reports to GitHub issues.dc00011Added client address book and account setup flows.021a1ccFixed the admin gamification runtime crash.30e1c5bPolished estimate detail and XP navigation.7384a51Improved invoice send and payment workflows.
2026-03-09
9639502Fixed messaging, conversion, and invoice workflows.d9b0008Stabilized admin performance and inbox threads.970afa2Added recurring job templates.e23fee5Updated the quick start create menu.6c78c24Installed missing build dependencies for Stripe andhtml2canvas.8c2654fFixed the commercial cost assumptions display data key mismatch.8bd4ff9Fixed the Resend webhook build cast.b8d9a9dFixed email reply routing fallback behavior.c8d5a81Fixed inbox sync and message delivery updates.64c3552Fixed the admin sidebar and widened the workspace.804119fFixed dispatch assignment and settings navigation.b90ef28Added legacy payments schema handling in the payment endpoint.e9780baAdded a unified payment collection workflow.96d4081Hardened webhook authentication and payment processing.17b3d21Switched the marketing site to waitlist mode and disabled self-serve registration.2594332Hardened the admin toaster and gamification seeding.b8579ccFixed a Next 15 API-routeparamsawait regression that caused 500 errors.2541fc3Fixed gamification org membership profile lookups.4ba42d8Fixed ambiguous org membership profile lookups.79ea433Added automatic bug report screenshots.81c7ed3Restored waitlist CTAs on marketing pages.875c1f5Added the gamification foundation and leaderboard.3d4b2dbUpdated the privacy policy and terms of service with official legal content.35778b8Improved team onboarding and archive lifecycle behavior.ef2fc30Fixed follow-up schema drift and restored admin links.f3b3904Added the admin help button and docs search results.9682720Added guarded MCP inbox write tools.9b923a3Added MCP Phase 1 agent access.e69baaeAdded actual job cost tracking to CRM margins.064f403Added unified inbox internal notes.95dfe22Bridged Workiz intake and hardened CRM analytics.2ad92b4Routed admin lead creation through intake.98fd69eUnified intake adapters and hardened sales timestamps.a4aa1bcDeduplicated unified email message logging.41cfec3Hardened lead intake and inbox filters.
2026-03-08
6242c0cAdded inbox ownership and response controls.9114acbRolled out the unified messaging foundation.65d8a76Fixed the bug report email truncation suffix.33a46c2Kept message threads scrollable during refresh.431ce4cAdded a bug report queue poller for automations.cf65b48Improved bug report email details and JSON export.da618d1Added bug report webhook alerts.e5f38f3Fixed admin message scroll locking.3e00a57Added the bug reporting system and app hardening work.728029fFinished the CRM buildout and QA hardening push.
2026-03-07
5cadd2bRestored Automations in the Settings nav.2bfbcc2Fixed searching existing customers while scheduling jobs.85f7657Hardened audited estimate and billing flows.f7bcfefShipped a lead-first workflow and added a jobs creation entry point.d34c3b7Fixed the client account migration rollout.582242fAdded nested client accounts and estimate pagination.f89aa5dFixed holiday lights measure and drawing flow.4c0fbf7Retired the legacy estimator and fixed the job invoice flow.f2382a1Hardened estimate intake and send permissions.ad46c31Fixed secure estimate links and auto-dial routing.787cd99Fixed customer portal estimate links.b270e16Restricted portal actions to known customers.c13fe52Hardened public portal throttling and Workiz sync.843bea3Finished a broad hardening and product redesign pass.
2026-03-06
3c3dad3Unified web and mobile pricing logic and deduplicatedescapeHtml.
Was this article helpful?
Still need help? Contact support