Changelog
Pulse / Horizon latency + mail config fail-fast (2026-09-16)
- EloSync-Backend: Queued mail middleware (
ApplyEmailRuntimeConfig) fails jobs immediately on configuration errors (invalid/missing Postmark token, etc.) instead of retrying. Task digests and CRM daily summaries mark those failures as permanent (retry_after = null) and stop reclaiming them; digests also gain a max-attempt cap. Horizonsupervisor-generalswitches fromautobalancing to ordered (balance => false) with max 2 processes so eight queues no longer spawn ~8 workers on small VPS RAM..env.exampledocuments productionPULSE_INGEST_DRIVER=redis+pulse:work. Pest:MailTransportFailureTest,TaskDigestDeliveryServiceTest,EmailQueueRuntimeConfigTest. - EloSync-Docs: Laravel Forge daemons — document Horizon process-cap behavior, add
pulse:work+pulse:restartfor Redis Pulse ingest; changelog. - Ops: Fix Postmark server token in Central/Tenant mail settings; set
PULSE_INGEST_DRIVER=redis, add Forgepulse:workdaemon, redeploy sohorizon:terminatepicks up the new worker limits.
Contract PDF + e-signature accept links (contracts 1.5.0) (2026-09-16)
- EloSync-Backend: Contracts gain
sentin the status machine (draft → sent|active|terminated;sent → active|expired|terminated;active → expired|terminated).POST /contracts/{id}/sendissues a hashed acceptance token +signature_requestedactivity;POST …/acceptance-link/ email (when Sent) rotate the link; publicGET/POST /api/tenant/v1/public/contracts/accept/{token}(throttlecontract-acceptance) accepts with signer name/email/IP →active, recordssigned, invalidates the token. StaffPOST …/acceptactivates from Sent; draft may still activate without signature via status. PDFGET …/pdf(contracts.view, throttlecontracts-pdf); emailPOST …/email(contracts.send). Permissionscontracts.send/contracts.accept. Catalog contracts 1.4.0 → 1.5.0 (migrate-only + CatalogSeeder). Pest:ContractAcceptanceTest. - EloSync-Frontend: Guest page
/#/accept/contracts/:token; contract view shows Send for signature, Copy accept link, Activate without signature, Download PDF, Email customer, and signer metadata. - EloSync-Docs: Contracts user/developer/API/deployment + roadmap; changelog.
Quotation e-signature accept links (quotations 1.9.0) (2026-09-16)
- EloSync-Backend: Sent quotations issue a hashed acceptance token. Staff can
POST /quotations/{id}/acceptance-link(quotations.send) or email a Sent quote (link appended to the message). PublicGET/POST /api/tenant/v1/public/quotations/accept/{token}(throttlequotation-acceptance) shows a summary and accepts with signer name/email/IP — transitionssent → accepted, recordssignature_requested/signedtimeline events, and invalidates the token. Reject/expire/internal accept clear outstanding links. Catalog quotations 1.8.0 → 1.9.0 (migrate-only + CatalogSeeder). Pest:QuotationAcceptanceTest. - EloSync-Frontend: Guest page
/#/accept/quotations/:token; quotation view shows Copy accept link, active-link hint, and customer acceptance metadata. - EloSync-Docs: Quotations user/developer/API + roadmap; changelog.
AI Leave triage tools (ai 1.16.0) (2026-09-16)
- EloSync-Backend: Ask EloSync adds
get_leave_request,get_pending_leave_requests, plus confirmed writesapprove_leave_requestandreject_leave_request. No assign/timeline-note tools. Approve/reject requireleave-management.approve, mirror HTTP viaLeaveAiSupport+LeaveRequestService(self-approve blocked for non-admin). Reject requiresreview_notes; approve supports optionalreview_notes/deduct_salarywith the same override-notes rule as the HTTP review request. Catalog ai 1.15.0 → 1.16.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz. - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Leave cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Credit Note triage tools (ai 1.15.0) (2026-09-16)
- EloSync-Backend: Ask EloSync adds
get_credit_noteplus confirmed writesupdate_credit_note_status,assign_credit_note, andadd_credit_note_note(text only). Status tool usesAiToolAnyOfPermissions(credit-notes.updateorissueorapplyorvoidorrefund) and mirrors Payment-style dedicated actions viaCreditNoteAiSupport::authorizeStatusChange+issue()/apply()/void()/refund()(Draft target rejected). Confirm-time assign re-validatesEligibleCreditNoteAssignee; note confirm re-checksmax:5000. Get payload returns number, title, amounts, linked invoice,assigned_to(user id), andassignee_name. Catalog ai 1.14.0 → 1.15.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz. - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Credit Notes cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Quotation triage tools (ai 1.14.0) (2026-09-16)
- EloSync-Backend: Ask EloSync adds
get_quotationplus confirmed writesupdate_quotation_status,assign_quotation, andadd_quotation_note(text only). Status tool usesAiToolAnyOfPermissions(quotations.updateorsendoraccept) and mirrors HTTPPOST …/statusauth viaQuotationAiSupport::authorizeStatusChange+changeStatus. Confirm-time assign re-validatesEligibleOpportunityAssignee; note confirm re-checksmax:5000. Get payload returnstitle(nonumber),assigned_to(user id), andassignee_name. Catalog ai 1.13.0 → 1.14.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz. - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Quotations cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Estimate triage tools (ai 1.13.0) (2026-09-16)
- EloSync-Backend: Ask EloSync adds
get_estimateplus confirmed writesupdate_estimate_status,assign_estimate, andadd_estimate_note(text only). Status tool usesAiToolAnyOfPermissions(estimates.updateorsendoraccept) and mirrors HTTPPOST …/statusauth viaEstimateAiSupport::authorizeStatusChange+changeStatus. Confirm-time assign re-validatesEligibleEstimateAssignee; note confirm re-checksmax:5000. Get payload returnsassigned_to(user id) andassignee_name. Catalog ai 1.12.0 → 1.13.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz. - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Estimates cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Lead assign + note tools (ai 1.12.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds confirmed writes
assign_leadandadd_lead_note(text only) alongside existing Lead reads andupdate_lead_status. Assign usesleads.assign+EligibleLeadAssigneeat propose and confirm (optional reason); note confirm re-checksmax:5000.get_lead/search_leadsexposeassigned_to(user id) andassignee_name. Catalog ai 1.11.0 → 1.12.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (propose≠mutate, unassign, suspended assignee, view-only / no-assign exclusions). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Leads cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Payment triage tools (ai 1.11.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_paymentplus confirmed writesupdate_payment_status,assign_payment, andadd_payment_note(text only). Status tool usesAiToolAnyOfPermissions(payments.updateorpayments.postorpayments.void); confirm callspost()for Posted andvoid()for Void (Draft target rejected). Confirm-time assign re-validatesEligiblePaymentAssignee; note confirm re-checksmax:5000. Get payload returnsassigned_to(user id) andassignee_name. Catalog ai 1.10.0 → 1.11.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (propose≠mutate, post/void gates, soft-delete, unassign, suspended assignee, view-only confirm deny). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Payments cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Purchase Order triage tools (ai 1.10.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_purchase_orderplus confirmed writesupdate_purchase_order_status,assign_purchase_order, andadd_purchase_order_note(text only). Status tool usesAiToolAnyOfPermissions(purchase-orders.updateorsendorreceiveorcancel) and mirrors HTTPPOST …/statusauth viaPurchaseOrderAiSupport::authorizeStatusChange+changeStatus. Confirm-time assign re-validates eligible assignee; note confirm re-checksmax:5000. Get payload returnsassigned_to(user id) andassignee_name. Catalog ai 1.9.0 → 1.10.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (propose≠mutate, send/receive/cancel gates, soft-delete, unassign, suspended assignee, view-only confirm deny). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Purchase Orders cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Project triage tools (ai 1.9.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds confirmed writes
update_project_status,assign_project, andadd_project_note(text only) on top of existingget_project/ search / overdue. Status requiresprojects.updateand usesProjectService::changeStatus(same as HTTPPOST …/status). Confirm-time assign re-validatesEligibleProjectAssignee; note confirm re-checksmax:5000. Get payload returnsassigned_to(user id) andassignee_name. Catalog ai 1.8.0 → 1.9.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (propose≠mutate, illegal transition, soft-delete, unassign, suspended assignee, view-only / no-assign exclusions). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Projects cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Expense triage tools (ai 1.8.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_expenseplus confirmed writesupdate_expense_status,assign_expense, andadd_expense_note(text only). Status tool usesAiToolAnyOfPermissions(expenses.updateorsubmitorapproveorrejectorpayorcancel) and mirrors HTTPPOST …/statusauth viaExpenseAiSupport::authorizeStatusChange+ExpenseService::changeStatus(Paid →pay([])). Confirm-time assign re-validatesEligibleExpenseAssignee; note confirm re-checksmax:5000.get_expense_pending_approvalreturnsassigned_to(user id) andassignee_name. Catalog ai 1.7.0 → 1.8.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (approve-only visibility, approve deny without perm, draft→approved transition fail, pay with paid-from account, unassign, suspended assignee, view-only confirm deny). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Expenses cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Invoice triage tools (ai 1.7.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_invoiceplus confirmed writesupdate_invoice_status,assign_invoice, andadd_invoice_note(text only). Status tool usesAiToolAnyOfPermissions(invoices.updateorinvoices.sendorinvoices.void) and mirrors HTTPPOST …/statusauth (Unpaid→send, Cancelled→void, else update) viaCustomerInvoiceService::changeStatus. Confirm-time assign re-validatesEligibleInvoiceAssignee; note confirm re-checksmax:5000.get_overdue_invoicesreturnsassigned_to(user id) andassignee_name. Catalog ai 1.6.0 → 1.7.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (send-only Draft→Unpaid, void deny, ledger cancel guard, invalid transition, soft-delete, unassign, suspended assignee, view-only confirm deny). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Invoices cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Opportunity triage tools (ai 1.6.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_opportunity,get_opportunity_stages, plus confirmed writesupdate_opportunity_stage,assign_opportunity, andadd_opportunity_note(text only). Stage tool uses integerstage_id.search_opportunitiesreturnsstage_id, assignee id, andassignee_name. HTTPPOST …/stagetenant-scopesstage_id. Confirm-time assign re-validatesEligibleOpportunityAssignee; note confirm re-checksmax:5000. Catalog ai 1.5.0 → 1.6.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (propose≠mutate, invalid stage, confirm-without-update, soft-delete, unassign, suspended assignee, view-only / no-assign exclusions, cross-tenant stage HTTP). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Opportunities cross-links; VitePress sidebar; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Task triage tools (ai 1.5.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_taskplus confirmed writesupdate_task_status,assign_task, andadd_task_note(text only). Status tool usesAiToolAnyOfPermissions(tasks.updateortasks.complete) so complete-only staff match HTTP complete/reopen. Confirm-time assign re-validatesEligibleTaskAssignee; note confirm re-checksmax:5000. Catalog ai 1.4.0 → 1.5.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz (complete-only happy path, non-complete deny, reopen deny, unassign, soft-delete, suspended assignee). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Tasks cross-links; VitePress sidebar; roadmap; production readiness (Go — A1/R1 remediations closed); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
AI Help Desk triage tools (ai 1.4.0) (2026-09-15)
- EloSync-Backend: Ask EloSync adds
get_help_desk_ticketplus confirmed writesupdate_help_desk_ticket_status,assign_help_desk_ticket, andadd_help_desk_ticket_note(text only). Status confirm mirrors Help Desk close/reopen/update gates. Confirm-time assign re-validatesEligibleHelpDeskAssignee; note confirm re-checksmax:5000. Catalog ai 1.3.0 → 1.4.0 (migrate-only + CatalogSeeder). Pest: write confirmation + registry authz + residual negatives (propose-close, unassign, illegal transition, soft-delete). - EloSync-Docs: AI tools / Tenant AI API / AI Assistant + Help Desk cross-links; VitePress sidebar entries; roadmap; production readiness (Go); changelog.
- SPA / Mobile: No code changes — existing pending-action Confirm/Cancel UI is tool-agnostic.
Auth branding split layout + Branded visual chrome (2026-09-14)
- EloSync-Backend: Per-page auth panel image settings (
auth_image_*_path) on Central and tenant catalogs; public bootstrap exposesauth_image_*_url. Branding upload assets expand beyond logo/favicon (auth images max 8 MB; logo/favicon max 2 MB). Tenant logo / favicon / button_color / auth images resolve and write only when Branded is entitled; otherwise Central defaults apply (stored overrides ignored until Branded returns). Pest: Central + tenant settings branding suites. - EloSync-Frontend: Guest
AuthLayoutis form-left / image-right with centered logo and copyright. Central and Tenant Settings → Branding show recommended dimensions and larger previews. Tenant Branding gates visual chrome behind Branded (Marketplace upsell); support email + invoice company profile stay available. - EloSync-Docs: Branded / central / tenant settings + auth notes; changelog.
Task edit status/priority validation (2026-09-14)
- EloSync-Frontend: Task create/edit form keeps status and priority in react-hook-form via
Controller+ required enums (no|| 'open'/|| 'medium'display fallbacks that masked empty values). Empty strings no longer reach the API as invalid status/priority on edit. Vitest:task-form-values. - EloSync-Docs: Changelog.
Workspace File a complaint (Help Desk) (2026-09-14)
- EloSync-Frontend: Tenant shell File a complaint entry (user menu + command palette) opens a quick dialog that creates a Help Desk ticket (subject, description, category, priority). Gated by Help Desk entitlement +
help-desk.create. Toast includes ticket number and Open ticket link. Distinct from Give Feedback (Central). One-session Playwright human workflow (complaint-dialog.spec.ts). - EloSync-Backend: Catalog help-desk 1.9.0 → 1.10.0 (MINOR; shell quick-create surface). CatalogSeeder aligned.
- EloSync-Docs: Help Desk + Give Feedback user guides; Help Desk developer guide; production readiness (Go); changelog.
Hybrid list e2e + calendar date display (2026-09-14)
- EloSync-Frontend: Playwright page objects/specs align with hybrid list sheets (
Open full page, sheet Cancel as button|link, create peeks). Stock adjust dialog closes before query invalidation (no stuck pending button). Project form resets only whenid/updated_atchange so cache rewrites do not wipe in-progress edits.formatAppDatetreatsYYYY-MM-DDcalendar dates as wall days (no timezone day-shift for pay periods and similar). Payroll e2e targets the created Draft pay run via create-response periods. Vitest: calendar date-only formatting. - EloSync-Docs: Tenant settings timezone note for calendar date-only formatting; changelog.
Party-first billing hubs & statements (2026-09-12)
- EloSync-Backend: Contact/company billing-summary and statement (+ PDF); vendor purchasing-summary and statement. Aged receivables rows include contact/company identity. Statement credits count applied notes only; vendor totals exclude drafts. Statements expose
opening_balance+ closingbalance_due. Catalog contacts 1.4.0 → 1.5.0, companies 1.1.0 → 1.2.0, vendors 1.1.0 → 1.2.0. Pest: party billing summary/statement suite. - EloSync-Frontend: List deep links
?contact=/?company=/?vendor=with clearable chips; party hubs on contact/company/vendor views (balances + related docs incl. credit notes; hub ErrorState); statement pages with opening/closing labels; vendor “purchasing activity” copy; aged receivables customer column + drill-downs. Playwright: contact + company hub + PDF smoke. - EloSync-Docs: Contacts/companies/vendors user + developer guides; payments list API query params; financial reports aged receivables note; party billing production readiness (Go); changelog.
Invoice PDF tax registration ID (2026-09-12)
- EloSync-Backend: Invoice PDF seller header prints workspace
tax_registration_id(Settings → General) when set; omits the line when blank. Included inBrandedDocumentPdfContextcompany profile + PDF cache fingerprint. Catalog invoices 1.9.2 → 1.9.3. Pest: PDF HTML show/hide. - EloSync-Docs: Invoices user/developer guides + tenant settings PDF note; tax types already referenced the field; changelog.
Invoice list search by customer (2026-09-09)
- EloSync-Backend: Invoice list
searchalso matches related contact name/phone/company text and company name/phone (still matches title/number). Catalog invoices 1.9.1 → 1.9.2. Pest: customer field search. - EloSync-Frontend / Mobile: Invoice list and payment/credit-note invoice pickers hint that name, company, and phone are searchable; allocation keywords include phone.
- EloSync-Docs: Invoices user/API/developer guides + payments allocation search note; changelog.
Workspace upload policy + Task media (2026-09-09)
- EloSync-Backend: Workspace setting
storage.upload_policy(allowed image/video/document types + separate max sizes, platform-capped). SharedWorkspaceUploadPolicyenforces limits on Chat, Expenses, Help Desk, Documents, Feedback, Knowledge Base, Lead imports, WhatsApp, and Tasks. Task + task-note multi-file attachments on S3/uploads disk with quota accounting (batch byte sum before store; mid-batch failure rolls back objects/rows). Catalog storage → 1.1.0, tasks → 1.4.0 (+ CatalogSeeder aligned). SettingsupdateManyresolves admin catalog outside the write transaction. Pest: upload policy + task attachments (batch quota,storage.manage403, cross-tenant download/delete, mid-batch rollback). - EloSync-Frontend: Settings → Storage upload limits UI (
storage.manage); Tasks create/view/note attachments;useUploadPolicyclient hints from storage usage wired into Documents, Help Desk, Expenses, Knowledge Base, Feedback, Team Chat, WhatsApp, and Lead import (accept+ client-side type/size checks). Playwright one-session human flow:npm run test:e2e:upload-policy. - EloSync-Docs: Storage / tenant settings / Tasks / Documents / Expenses / object-storage / API notes; production readiness (Go); changelog.
Automation real-world coverage (2026-09-08)
- EloSync-Backend: Removed dual
AutomationEventBridge(sole fan-out =IntegrationEventDispatcher). Wired Contacts / Quotations / Expenses / Employees / Payments / Credit Notes / Projects / Estimates / Contracts / Purchase Orders / Leave / Documents / Knowledge Base / Assets triggers; fixed invoice module slug toinvoices. Templates gainrequired_modules+ entitlement filter. Catalog automation 1.1.1 → 1.2.0 → 1.3.0 (+ companion module MINOR bumps; CatalogSeeder versions aligned). Pest: CrossModule samples, template entitlement negatives, registry↔dispatcher parity. - EloSync-Frontend: Builder respects catalog
available(Module not installed); activate disabled until entitled; one-session Playwright human workflow (test:e2e:automation). - EloSync-Docs: User recipes, developer Automation + Module Development Automation hooks checklist; 1.3.0 production readiness (Go); changelog; deferred Automation trigger notes cleared for wired modules.
Automation related-context builder (2026-09-08)
- EloSync-Backend: Semantic
trigger_assignee; tag/stage by id or name/slug;create_tasknote-link + opt-increate_lead_follow_up; Catalog 1.1.0 → 1.1.1 (migrate + CatalogSeeder). Pest related suites. - EloSync-Frontend: Builder pickers, multi-notify, schedule config, Manual Run related-record dialog; Playwright related-context chrome.
- EloSync-Docs: User/developer guides + related-context production readiness (Go); changelog.
Attendance location + edit history (2026-09-07)
- EloSync-Backend: Self check-in/out, login/2FA/passkey auto check-in, and manual create/update stamp IP from the request and best-effort lat/lng from the client. Manual create/update require
change_reason. Newattendance_record_activitiestimeline (GET …/timeline) records who/why/field diffs. Catalog attendance 1.3.0 → 1.4.0. - EloSync-Frontend: Check-in/out dialogs, create/edit form, and tenant login/2FA/passkey send best-effort geolocation. Create/edit require a reason. Attendance view shows Edit history.
- EloSync-Docs: Attendance user/developer/API/deployment guides; changelog.
Consistent 12-hour AM/PM casing (2026-09-07)
- EloSync-Frontend: Tenant dashboard widget times and the impersonation expiry banner use
formatAppTime(workspacetime_format) instead of browsertoLocaleTimeString, so 12-hour clocks always show uppercase AM / PM like the rest of the SPA. - EloSync-Docs: Tenant settings timezone note; changelog.
Kanban production scroll hardening (2026-09-07)
- EloSync-Frontend: Production follow-up on board scroll: column scrollports no longer use
touch-none(finger pan works; drag stays on cards), board height uses a fixed100dvh - 16remchrome budget, dnd-kit auto-scroll skips the document and targets board/column scrollers, anddata-tour="module-table"is only onModuleListSurface(data-kanban-boardon the board). Vitest + Playwright board visibility anchors updated. - EloSync-Docs: Shared UI / module tours + board module developer notes; changelog.
Kanban column + board scroll containment (2026-09-07)
- EloSync-Frontend: Shared
KanbanBoardgives each column its own vertical scroller (titles stay fixed) and contains horizontal overflow inside the board surface (min-w-0+scrollbar-modern), matching table-style contained scroll instead of browser page scrollbars.ModuleListSurfaceis width-bounded (w-full min-w-0). Applies to Leads, Tasks, Opportunities, Projects, Help Desk, and To-Dos. Vitest:kanban-board. - EloSync-Docs: Tasks developer guide shared-board note; changelog.
SPA stale-chunk reload after deploy (2026-09-07)
- EloSync-Frontend: After a production deploy, open tabs that hit a missing Vite lazy chunk auto-reload once instead of showing the root “Something went wrong” screen (
vite:preloadError,lazyWithDeployRecovery, ErrorBoundary recovery). Vitest:spa-deploy-recovery. - EloSync-Docs: Production runbook, frontend build artifacts, shared layout; changelog.
Fix migrate:fresh --seed demo tenant ID (2026-09-06)
- EloSync-Backend:
DatabaseSeederno longer mutes Eloquent events forlocal:seed-demo.WithoutModelEventson the whole seeder blocked StanclGeneratesIds, so creatingdemo-crmfailed withField 'id' doesn't have a default value. Central seed still runs insideModel::withoutEvents();TenantService/TenantFactoryalso set string IDs explicitly as a safety net. - EloSync-Docs: Changelog.
Per-employee hours + late salary ladder (2026-09-06)
- EloSync-Backend: Settings → Attendance adds per-employee schedule toggle, late deduction ladder (
Xlates →Yday salary), and toggles for absent / unpaid-leave salary deductions. Employees store optional on-site and remote start/end times. Late remote check-ins are status Late (work mode still remote). Pay runs storelate_count/late_deduction_days; payslips list them. One late day never deducts; ladder highest-match applies. Catalog employees / attendance / payroll → 1.3.0. - EloSync-Frontend: Attendance settings UI for the new toggles and ladder editor; employee form work-hours fields; pay-run lines show late count/days.
- EloSync-Docs: Attendance, payroll, employees, tenant settings, API notes; changelog.
Account decimals, payroll currency, employee number (2026-09-06)
- EloSync-Frontend:
formatCurrencyalways shows two decimal places (e.g.0.87,$100.00). Payroll profile create defaults currency to the workspace currency from Settings. Employee create prefills the nextEMP-####(still editable). Vitest:utils.currency. - EloSync-Backend: Payroll profiles omit-currency create uses workspace currency (
TenantSettingService).GET /employees/meta/next-numberfor create forms. Catalog employees 1.1.0 → 1.2.0, payroll 1.2.1 → 1.2.2. - EloSync-Docs: Employees / payroll user + API guides; changelog.
Payroll list/peek approve + ModuleRecordSheet cache fix (2026-09-05)
- EloSync-Frontend: Pay runs list row menu and quick peek expose Approve / Mark paid (permission-gated) so draft runs do not require the full page. Shared
ModuleRecordSheetnow cachesApiSuccessResponse(same shape as dedicated view pages) and accepts optionalrenderViewActions— fixes peek → Open full page hanging on Loading withinstaleTime. Playwright payroll suite approves from the list. - EloSync-Backend: Catalog payroll 1.2.0 → 1.2.1.
- EloSync-Docs: Payroll user guide workflow; module-development hybrid-sheet cache note; changelog.
SPA scroll to top on navigation (2026-09-05)
- EloSync-Frontend: Client-side route changes reset window scroll to the top of the new page (
ScrollToTopin the router). Query-string updates on the same path (filters, pagination) do not. Vitest:scroll-to-top. - EloSync-Docs: Shared layout and Tenant Application navigation notes; changelog.
List peek overviews (2026-09-05)
- EloSync-Frontend: List/board record sheets that used the empty
ModuleRecordSheet“Quick peek” stub now passrenderOverviewwith key fields (status, dates, assignee, amounts, etc.). Covers Tasks and other tenant CRUD peeks (Contacts, Companies, Opportunities, Activities, Meetings, To-Dos, Projects, Help Desk, and the remaining stub modules). Billing/line-item peeks were already field-complete. Open full page remains the escape hatch for notes, activity, and deep work. - EloSync-Docs: Tasks user guide and module-development hybrid-sheet guidance updated; changelog.
Platform hybrid list sheets (2026-09-04)
- EloSync-Frontend: Shared
ModuleRecordSheet+EntityFormEmbedProvider/useEntitySheetStateroll list-side create/peek/edit sheets across tenant CRUD modules (mirror Leads). Simple modules: New /nopens the sheet; row/board peek uses tabs-ready sheet with Open full page. Heavy billing/line-item modules (invoices, quotations, estimates, credit notes, payments, purchase orders, journals): view peek only — create/edit stay on dedicated builder pages. Dedicated routes and deep links remain canonical. - EloSync-Docs: Module development hybrid-sheet standard updated for all modules; changelog.
Leads hybrid list sheets (2026-09-04)
- EloSync-Frontend: Leads list New / shortcut
nand board/table peek open a professionalEntityRecordSheet(create, tabbed view, light edit) so agents stay on the list. Create closes the sheet; Create & open / Open full page use dedicated routes. Deep links and notifications still open full record pages. Playwright:leads.sheetplus updated shortcuts / page object. - EloSync-Backend: Catalog leads 1.3.2 → 1.4.0.
- EloSync-Docs: Module development hybrid-sheet standard; Leads user guide create/peek flows; changelog.
Invoice payments on view, billing email delivery, default T&Cs (2026-09-04)
- EloSync-Backend:
BillingDocumentEmailregistersApplyEmailRuntimeConfigon theemailsqueue (same as notifications);BillingDocumentMailerapplies provider config before queueing. Invoice show eager-loadspayment_allocationswith nested payment. New workspace settinginvoice_default_terms_and_conditions— PDF fallback for invoices/quotations/estimates when document terms are empty. Catalog invoices 1.9.0 → 1.9.1. - EloSync-Frontend: Invoice record Payments section lists posted allocations; Settings → Branding default terms field; new invoices prefill terms from that setting.
- EloSync-Docs: Multi-provider email (queued mailables), tenant settings, invoices user/developer guides, changelog.
Platform-wide related inline creates (2026-09-03)
- EloSync-Frontend: Cross-module create/edit forms offer a gated New beside related pickers (Contact, Company, Opportunity, Lead, Vendor). Dialogs create the record and auto-select it without leaving the page. New appears only when the related module is entitled and the user has
*.create. Shared:Create*Dialog+RelatedEntityPicker(dialogs reset on close to avoid open/fill races; dialogonSubmitusesstopPropagationso the parent form is not submitted). Playwright:related-createsshared session + authz (no create → no New) +invoices/quotations/purchase-orders/contacts.related-creates. URL?contact=/?company=prefills preserved across form reset. - EloSync-Backend: Catalog MINOR bumps —
invoices1.8.1 → 1.9.0,quotations1.6.0 → 1.7.0,estimates1.5.0 → 1.6.0,contracts1.2.1 → 1.3.0,payments1.2.0 → 1.3.0,credit-notes1.2.0 → 1.3.0,purchase-orders1.2.0 → 1.3.0,opportunities1.1.1 → 1.2.0,projects1.1.0 → 1.2.0,activities1.0.0 → 1.1.0,help-desk1.7.0 → 1.8.0,documents1.3.0 → 1.4.0,expenses1.3.0 → 1.4.0,assets1.0.0 → 1.1.0. - EloSync-Docs: Module Development Guide related-create standard; user-guide create steps for all wired modules; production readiness (Go — audit residuals remediated).
Contacts related creates (2026-09-03)
- EloSync-Frontend: Contact create/edit can open a New company dialog (Companies entitled +
companies.create) that creates and auto-selects the company. Contact view adds gated New quotation / New invoice / New payment links that open those modules’ create pages with?contact=(and?company=when linked). Quotation, invoice, and payment create forms prefill from those query params. Playwright:contacts.related-creates. - EloSync-Backend: Catalog contacts 1.1.0 → 1.2.0.
- EloSync-Docs: Contacts user/developer guides and related company/sales notes updated.
Collapsible sidebar categories (2026-09-03)
- EloSync-Frontend: Tenant sidebar sections now match catalog categories. The former Workspace dump is split into CRM, Communication, and Operations. Each section with more than one item is a collapsible accordion; the group for the current page stays open, and extra expanded sections persist per Central/Tenant context. Playwright:
expectSidebarNavLinkVisibleexpands sections before asserting links; tenant dashboard covers CRM collapse/expand. - EloSync-Docs: Shared layout, tenant application, and Projects guides updated for the new groups.
User security settings — 2FA, passkeys, sessions (2026-09-02)
- EloSync-Backend: Per-user Security APIs on tenant + Central guards — browser session list/revoke (
SessionTokenService), Fortify TOTP enable/confirm/disable + login challenge,laravel/passkeysregistration/login with polymorphicpasskeyable. Password change revokes other sessions by default. Pest:tests/Feature/Auth/*. - EloSync-Frontend: Profile Security tab (password, 2FA, passkeys, sessions); login two-step TOTP challenge + Sign in with passkey (
@laravel/passkeys+@simplewebauthn/browser). Playwright: profile security tab coverage. - EloSync-Docs: User + developer authentication guides, API reference, deployment/upgrade notes, and production readiness checklist.
Attendance self check-in/out without RBAC (2026-09-01)
- EloSync-Backend: Self check-in, check-out, and today timer no longer require
attendance.create/attendance.update— only a linked active employee, Attendance module entitlement, and Show check-in / check-out enabled. Reason list readable for self-service pickers. Catalog attendance 1.2.0 → 1.2.1. - EloSync-Frontend: Top header and Attendance list self-service buttons gate on employee link + self-check setting (not create/update permissions).
- EloSync-Docs: API + user/developer attendance guides updated.
Inline tag editing — Leads & Tasks (2026-09-01)
- EloSync-Frontend: Leads and Tasks list/board views support ClickUp-style inline tag editing — click tag badges (or Add tags) to open a popover without opening the record. Tasks auto-save on toggle (including inline tag create when permitted). Leads use Save tags in the popover; Follow Up Later still requires a follow-up due date. Playwright:
leads.inline-tags,tasks.inline-tags. - EloSync-Backend: Catalog leads 1.3.1 → 1.3.2, tasks 1.2.0 → 1.2.1 (UI-only; existing
PUT …/tagsendpoints unchanged). - EloSync-Docs: Leads/Tasks user guides + tenant API notes updated.
Mobile OS push via FCM (2026-09-01)
- EloSync-Backend:
HttpFcmClientsends platform-aware FCM HTTP v1 messages — web keeps data +webpush;android/iostokens getnotification(+android.priority/apns) while still carryingdata.payloadfor deep links. Pest covers web/android/ios shapes. - EloSync-Mobile: Profile Push notifications toggle registers native device tokens on
/fcm-device-tokens(expo-notifications); login/bootstrap sync + logout unregister (opt-in retained). Tap-to-open + inbox row navigation via route resolver. Graceful degrade until Firebase Android/iOS apps + EAS rebuild. - EloSync-Docs: User/developer/deployment notification guides updated for native FCM tokens.
AI workspace search — remediations + Eng Ready (2026-08-31)
- EloSync-Backend: Wave C providers (documents, knowledge-base, activities, meetings); per-provider try/catch (
modules_failed); prompt citation paths expanded. Pest 18/18 (Wave C + provider isolation). - EloSync-Frontend: Lead create no longer defaults
assigned_toto ineligible owners (isEligibleLeadAssigneeCandidate); Wave C citation maps. - EloSync-Mobile / Docs / Website: Wave C citations; audit Eng Ready; marketing roadmap next focus synced (API & Webhooks already shipped).
- Operator remaining: merge
feature/ai-workspace-search, migrate ai → 1.3.0, deploy SPA/Mobile, staging smoke. See production readiness.
AI workspace search — production readiness (2026-08-31)
- EloSync-Docs: AI workspace search production readiness — Go (engineering on
feature/ai-workspace-search); operator next: merge PRs, migrate catalog ai → 1.3.0, deploy SPA/Mobile, staging smoke.
AI workspace search (2026-08-31)
- EloSync-Backend: Ask EloSync tool
search_workspacefans out across entitled Wave A+B+C modules (leads, tasks, projects, opportunities, contacts, companies, invoices, help-desk, estimates, payments, credit-notes, vendors, purchase-orders, expenses, employees, products, documents, knowledge-base, activities, meetings) with per-module view gates. Catalog ai 1.2.0 → 1.3.0. Pest coverage for gating, filters, limits, resilience, and tenant isolation. - EloSync-Frontend / Mobile: Citation deep links for Wave A+B+C record paths; Search workspace starter chip (
ai+ai.use). - EloSync-Docs: AI tools / API / user guide / deployment / roadmap updated for workspace search.
Phased Depth Program — merged to main (2026-08-31)
- Five repos (
feature/phased-depth-program→main): Backend #165, Frontend #159, Docs #234, Mobile #51, Website #42. - EloSync-Docs: Production readiness — Go; operator next:
migrate --force, queue restart, staging smoke.
Phased Depth Program — production readiness (2026-08-31)
- EloSync-Backend / Frontend / Docs / Mobile / Website: Phases 0–3c complete on
feature/phased-depth-program— hygiene, webhook depth + Developers Edit UI, credit-note applied refund (1.2.0), Help Desk @mentions (1.5.0), Kanban board (1.6.0), Communication Template replies (1.7.0). - EloSync-Docs: Phased Depth production readiness — Go after PR merge + migrate-first deploy. Remediated doc drift, mobile CN refund guide, Playwright
local:seed-demoprerequisites, Backendphpunit.xmlmemory for Pest. - Verification: Depth Playwright re-verified green (2026-08-31); tenant settings e2e hardened; Backend
phpunit.xmlmemory for Pest.
Help Desk status Kanban (2026-08-31)
- EloSync-Backend:
GET /help-desk/board— fixedHelpDeskStatusEnumcolumns (mirrors Tasks, not Opportunities stages);HelpDeskTicketService::board(); Pest board coverage. Catalog help-desk 1.5.0 → 1.6.0. - EloSync-Frontend: Help Desk list Board/List toggle (
KanbanBoard); drag callsPOST …/statuswith update/close/reopen gates. - EloSync-Docs: User/dev/API/deployment guides + roadmap deferred list updated for Kanban.
Help Desk Communication Template replies (2026-08-31)
- EloSync-Backend:
HelpDeskPlaceholderProvidercontexthelp-deskregistered onPlaceholderRegistry(soft entitlement — nomodule_dependencies). Ticket contact API refs includeemail/phone. Catalog help-desk → 1.7.0 (Kanban owns 1.6.0 in parallel). Pest coverage for placeholder resolve/render. - EloSync-Frontend: Generalized
WhatsAppTemplatePickerDialogfor any context; Help Desk ticket view soft-gates WhatsApp when Communication Templates is installed +communication-templates.useand the linked contact has a phone. - EloSync-Docs: Help Desk + Communication Templates user/developer/deployment guides, roadmap, and this note.
Help Desk @mentions (2026-08-31)
- EloSync-Backend: Help Desk ticket notes support
@mentions(help_desk_note_mentions,HelpDeskMentionedNotification); email-ingest notes withoutuser_idskip mention sync. Catalog help-desk 1.4.0 → 1.5.0. - EloSync-Frontend: Ticket note composer uses
MentionComposer; notification registry mapshelp_desk.mentioned. - EloSync-Docs: Help Desk guides updated; deferred list no longer claims mentions unshipped.
Tenant API & Webhooks depth (2026-08-31)
- EloSync-Backend / Frontend / Mobile / Docs: Credit Notes 1.1.0 → 1.2.0 — applied refund (
POST /credit-notes/{id}/refund,credit-notes.refund). Statusapplied → refunded(void stays draft/issued only). Reverses invoiceamount_credited, voids linked apply journal when Accounting is entitled, updates invoice cancel guard to require refunds first. SPA + mobile Refund action; Playwright + Pest coverage. - EloSync-Backend: Expanded outbound webhook catalog with
customer_payment.posted,customer_credit_note.applied, and Help Deskticket_created/ticket_status_changed/sla_breached(Help Desk Automation now fans out viaIntegrationEventDispatcher). Pest coverage for catalog, edit, and new event delivery. - EloSync-Frontend: Settings → Developers webhook endpoints support full Edit (name, URL, events) in addition to Enable/Disable. Playwright round-trip for edit.
- EloSync-Docs / Website: Developer/deployment webhook docs + marketing roadmap “Next” updated; event catalog documents the new events.
- EloSync-Docs: Prior hygiene note — rebuilt Product Roadmap + credit-notes/PO PDF copy sync.
Docs hygiene — roadmap rebuild + billing PDF copy (2026-08-31)
EloSync-Docs: Rebuilt corrupted Product Roadmap as clean UTF-8 from module dependencies, changelog, and user-guide deferred lists. Synced Credit Notes and Purchase Orders user guides so PDF/email match overviews (shipped, not deferred). Documented credit-note PDF/email API routes.
FCM-only desktop push remains as previously shipped — no product change in this note.
EloSync-Mobile: Dashboard Quick Actions (permission-gated shortcuts: new lead/task/deal, log activity, attendance, new to-do). Tasks list filters (All, My tasks, Overdue, status chips). More screen groups modules into Work / Sales / Billing / Purchasing / Inventory / Finance / HR / Tools with icons per module (
config/modules.tscategory + icon).EloSync-Docs: Mobile user guide + this changelog note.
FCM-only desktop push + once-per-device enable (2026-08-30)
- EloSync-Backend: Closed/background push is FCM only (
FcmChannel). Removed Minishlink / Laravel VAPID (WebPushChannel,push_subscriptionsAPI,VAPID_*/WEBPUSH_*). Icon/TTL/urgency live underFCM_*. Migration dropspush_subscriptions. - EloSync-Frontend: Enable flow registers an FCM web token (Firebase Messaging +
/sw.js). Logout unregisters the token but keeps the per-browser opt-in flag; next login silently re-registers without the enable dialog. - EloSync-Docs: Deployment / architecture / API / install / Forge updated for FCM-only env keys and once-per-device persistence.
Help Desk SLAs + shared email intake (2026-08-30)
- EloSync-Backend: Help Desk 1.2.0 → 1.3.0 → 1.4.0 — SLA policies with response/resolve clocks (
UtcDateTime/UtcInstant), breach scanner (help-desk:scan-sla-breaches), breach notifications, list filters/stats (sla_breached,sla_at_risk), and Automation triggershelp_desk.ticket_created/ticket_status_changed/sla_breached. Shared IMAP mailboxes (help_desk_mailboxes) with ingest sync (help-desk:sync-mailboxes→ queuehelp-desk-ingest): createsource=emailtickets or append notes whenHD-#####is referenced; soft contact match by sender email. - EloSync-Frontend: Manage SLAs and Manage mailboxes dialogs; SLA badges/filters/KPIs on the list; SLA section on ticket view; Email source badge; notification routes for SLA breaches. Playwright coverage extended for SLA CRUD and mailbox form smoke.
- EloSync-Docs: User/developer/deployment/API guides + Forge queue note + this changelog; deferred lists no longer claim attachments/SLAs/email/Automation triggers as unshipped.
Topbar attendance timer, CRM staff pressure, semantic badges (2026-08-29)
- EloSync-Frontend: Attendance HH:MM timer + Check in/out in the tenant topbar (next to theme toggle). CRM Analytics report shows a Staff workload table (open load, cycle times, pressure score with semantic health badges). Status/active/unread badges use fixed semantic colors (
success/warning/info/destructive) — not tenant primary. - EloSync-Backend: CRM domain report includes
staffrows with explainable pressure score (0–100). - EloSync-Docs: Attendance + Analytics user/dev guides and this note.
Assignee pickers — search filters + non-lead eligibility (2026-08-29)
- EloSync-Frontend: List Assignee filters use searchable
AssigneeFilterSelect. Form/view/filter assignee pickers omit suspended users (current assignee kept so they can be cleared). Non-lead modules (quotations, invoices, tasks, etc.) list all active users — including workspace owners and users flagged Exclude from lead assignment. Leads keepfilterLeadAssigneeOptions(owners / exclude-from-lead omitted). - EloSync-Backend:
User::isEligibleAssignee/scopeEligibleAssignees(not suspended). Non-leadEligible*Assigneerules and newEligibleTaskAssigneeuse it.EligibleLeadAssigneeunchanged. - EloSync-Docs: Module development, tasks, contracts, assets guides + this note.
Shared list pagination — page numbers + page size (2026-08-29)
- EloSync-Frontend: Module
DataTablepagination now shows numbered page buttons (with ellipsis), previous/next chevrons, and a Rows per page control (10/25/50/100).useListQueryStateexposessetPerPage(resets to page 1). Wired across tenant/central list pages. - EloSync-Docs: Changelog note for the shared table UX change.
Attendance check-in / pay slips (2026-08-28)
- EloSync-Backend / Frontend / Docs: Attendance 1.1.0 → 1.2.0 — explicit self check-in/out (login auto check-in off by default), live HH:MM timer from saved check-in, on-site/remote mode, admin-managed late/check-out reasons (Other → free text), settings toggles + remote grace, today/presence APIs. When auto-login is on and late reasons are required, late logins skip auto check-in so staff must submit a reason explicitly.
- EloSync-Backend / Frontend / Docs: Payroll 1.1.0 → 1.2.0 — employees with
payroll.view_owncan list and download their own paid pay-slip PDFs (/my-pay-slips).
Local Vite — BrowserRouter (2026-08-28)
- EloSync-Frontend:
npm run devuses BrowserRouter so post-login navigation to/dashboardworks without a#prefix (Vite SPA fallback). Production builds keep HashRouter (/#/...) for static hosting. Playwright local routes match path mode; setE2E_USE_HASH_ROUTER=1for preview/prod targets. Safe post-login redirects normalize/#/path→/pathto avoid the SPA 404 page.
Tasks — complete from list (2026-08-28)
- EloSync-Frontend: Tasks list row menu — Complete / Reopen (permission-gated with
tasks.complete), matching the record-page actions so tasks can be finished without opening the view. - EloSync-Docs: Tasks user guide updated for list complete/reopen; catalog tasks 1.3.0 → 1.3.1.
- EloSync-Backend: Catalog PATCH bump tasks
1.3.0→1.3.1.
Tenant web — auth, invoices, quotations (2026-08-27)
- EloSync-Frontend: Tenant bearer tokens always persist in
localStorageso new tabs and “open in new tab” on records stay signed in without Keep me signed in for 30 days. That checkbox now only extends server token lifetime (30 days). Legacy tenant tokens insessionStoragemigrate on read. Tenant login label updated accordingly. - EloSync-Frontend: Invoices list — issue-date range filters (
date_from/date_to) in the filter bar. - EloSync-Backend: Invoice list and stats accept
date_from/date_to(filters on issue date, or created date when issue date is unset). - EloSync-Frontend: Quotations list — Contact, Company, Phone, and Email columns; view page shows phone and email on related contact/company cards.
- EloSync-Backend: Quotation and invoice list APIs include
phoneon contact and company refs. - EloSync-Frontend: Module list layout — title + page tour, description, action buttons, then filters; breadcrumbs stay in the topbar only.
EloSync Mobile — module polish wave 3 (2026-08-27)
- EloSync-Mobile: Shared RecordTimelineSection on record view screens — reads
activitiesfrom the show API and displays audit timeline entries (actor, timestamp, event type). Wired across CRM, sales, purchasing, inventory, and help-desk modules that expose timeline on show. - EloSync-Docs: Mobile user guide, developer mobile guide, and changelog updated for timeline polish.
EloSync Mobile — module polish wave 2 (2026-08-27)
- EloSync-Mobile: Extended shared RecordNotesSection to Activities, Products, Assets, Expenses, Quotations, Contracts, Projects, Invoices, Payments, Credit notes, Estimates, Warehouses, and Knowledge base (list + add when update permitted). Refactored Vendors, Purchase orders, and Help desk ticket views to the shared component.
addNoteAPI methods added for each module. - EloSync-Docs: Mobile user guide, developer mobile guide, and changelog updated for polish wave 2.
EloSync Mobile — module polish (2026-08-27)
- EloSync-Mobile: Shared RecordNotesSection on Leads, Opportunities, Contacts, Companies, Tasks (list + add when update permitted); RecordAiCopilotSection on Lead and Opportunity view (summarize, next action, draft follow-up with share). Resellers notes refactored to shared component. Copilot API methods added to
lib/api/ai.ts. - EloSync-Docs: Mobile user guide, developer mobile guide, and changelog updated for polish delivery.
EloSync Mobile — AI module (2026-08-27)
- EloSync-Mobile: EloSync AI assistant — conversation list, new thread, chat with starter prompts, credit balance, structured insights/references, confirm/cancel write actions when permitted. API client
lib/api/ai.ts. Routes underapp/(app)/(tabs)/ai/. Record copilot panels and Settings → AI configuration remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for AI delivery.
EloSync Mobile — Branded module (2026-08-27)
- EloSync-Mobile: Branded custom website address screen — status, DNS instructions (TXT, A, CNAME), propose/verify/remove when permitted, share DNS values, pull-to-refresh. API client
lib/api/branded.ts. Routes underapp/(app)/(tabs)/branded/. Settings → General branded panel parity on mobile v1; logo upload remains web-only. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Branded delivery.
EloSync Mobile — Storage module (2026-08-27)
- EloSync-Mobile: Storage workspace usage screen — used/allowance bytes, percent bar, pack label, pull-to-refresh (permission-gated). API client
lib/api/storage.ts. Routes underapp/(app)/(tabs)/storage/. Marketplace pack purchase and settings panel remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Storage delivery.
EloSync Mobile — Reseller Payouts module (2026-08-27)
- EloSync-Mobile: Reseller Payouts commission ledger list, search, status and party filters, view entry details, approve, mark paid, and void (permission-gated). API client
lib/api/reseller-payouts.ts. Routes underapp/(app)/(tabs)/reseller-payouts/. Stats dashboard KPI strip remains web-only on mobile v1; entries are system-generated (no create/edit). - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Reseller Payouts delivery.
EloSync Mobile — Resellers module (2026-08-27)
- EloSync-Mobile: Resellers list, search, status filter, create, view, edit, delete; assignee on create/edit when permitted, commission rates, internal notes field, note entries list and add note on view (permission-gated). API client
lib/api/resellers.ts. Routes underapp/(app)/(tabs)/resellers/. Stats dashboard, activity timeline, trash/restore, invite login, and dedicated assign endpoint remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Resellers delivery.
EloSync Mobile — Purchase Orders module (2026-08-27)
- EloSync-Mobile: Purchase Orders list, search, status filter, create, view, edit (draft), delete; send, receive (with optional warehouse when Inventory + Warehouses entitled), cancel, convert to expense, notes, and vendor picker (permission-gated). API client
lib/api/purchase-orders.ts. Routes underapp/(app)/(tabs)/purchase-orders/. PDF, email vendor, stats, timeline, trash/restore, multi-line editor, and product picker remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Purchase Orders delivery.
EloSync Mobile — Vendors module (2026-08-27)
- EloSync-Mobile: Vendors list, search, status filter, create, view, edit, delete; assignee on create/edit when permitted, notes list and add note on view (permission-gated). API client
lib/api/vendors.ts. Routes underapp/(app)/(tabs)/vendors/. Stats dashboard, activity timeline, trash/restore, dedicated assign endpoint, and default withholding tax picker remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Vendors delivery.
EloSync Mobile — Departments module (2026-08-27)
- EloSync-Mobile: Departments list, search, status filter, create, view, edit, delete; manager assignment, user/employee member chips, and read-only performance summary when permitted (permission-gated). Expanded
lib/api/departments.ts. Routes underapp/(app)/(tabs)/departments/. Stats dashboard, performance reports, trash/restore, and separate attach/detach endpoints remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Departments delivery.
EloSync Mobile — Payroll module (2026-08-27)
- EloSync-Mobile: Payroll profiles and pay runs — list, search, create, view, edit, delete; pay run status filter, approve, mark paid, and post to journal (permission-gated). API clients
lib/api/payroll-profiles.tsandlib/api/pay-runs.ts. Routes underapp/(app)/(tabs)/payroll/(hub,profiles/*,pay-runs/*). Line gross/adjustment editing, journal account picker, trash/restore, and staff self-scope remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Payroll delivery.
EloSync Mobile — Leave Management module (2026-08-27)
- EloSync-Mobile: Leave Management leave requests list, search, status filter, create, view, edit (draft), delete, submit, approve, reject, cancel, self-service for linked employees, and read-only balance summary on view (permission-gated). API clients
lib/api/leave-requests.ts,lib/api/leave-types.ts,lib/api/leave-balances.ts. Routes underapp/(app)/(tabs)/leave-management/. Leave type CRUD, balance administration, trash/restore, and salary-deduction overrides remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Leave Management delivery.
EloSync Mobile — Attendance module (2026-08-27)
- EloSync-Mobile: Attendance daily records list, search, status filter, create, view, edit, delete, self check-in/check-out, and manager employee picker (permission-gated). API client
lib/api/attendance-records.ts(/attendance-records). Routes underapp/(app)/(tabs)/attendance/. KPI stats, date-range filters, trash/restore, and geolocation remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Attendance delivery.
EloSync Mobile — Employees module (2026-08-27)
- EloSync-Mobile: Employees directory list, search, create, view, edit, and delete (permission-gated). Department multi-select when Departments module is entitled; linked user picker when
users.listis granted. API clientslib/api/employees.tsandlib/api/departments.ts(/employees,/departments). Routes underapp/(app)/(tabs)/employees/. Payroll, attendance, leave, and department management remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Employees delivery.
EloSync Mobile — Accounting module (2026-08-27)
- EloSync-Mobile: Accounting chart of accounts (list, search, create, view, edit, delete) and journal entries (list, search, create, view, edit draft, delete draft, post, void) with balanced multi-line forms (permission-gated). API clients
lib/api/accounts.tsandlib/api/journal-entries.ts(/accounts,/journal-entries). Routes underapp/(app)/(tabs)/accounting/. General ledger, transfers, bank reconciliation, periods, opening balances, and tax types remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Accounting delivery.
EloSync Mobile — Financial Reports module (2026-08-27)
- EloSync-Mobile: Financial Reports trial balance, profit and loss, balance sheet, and aged receivables with date filters (permission-gated). API client
lib/api/financial-reports.ts(/financial-reports/*). Routes underapp/(app)/(tabs)/financial-reports/. CSV export remains web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Financial Reports delivery.
EloSync Mobile — Analytics module (2026-08-27)
- EloSync-Mobile: Analytics KPI overview with preset periods, domain report links (CRM, Sales, Billing, Purchasing, People), metrics, and tabular rows (permission-gated). API client
lib/api/analytics.ts(/analytics/overview,/analytics/reports/{area}). Routes underapp/(app)/(tabs)/analytics/. Charts, CSV export, and custom date ranges remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Analytics delivery.
EloSync Mobile — Automation module (2026-08-27)
- EloSync-Mobile: Automation workflow list, search, view, activate/deactivate, manual run, delete (permission-gated), run history list, and run detail with execution logs. API client
lib/api/automation.ts(/automation/workflows,/automation/runs). Routes underapp/(app)/(tabs)/automation/. Visual builder, create/edit workflows, templates, and catalog browsing remain web-only on mobile v1. - EloSync-Docs: Mobile user guide, developer mobile guide, product roadmap, and changelog updated for Automation delivery.
EloSync Mobile — WhatsApp Cloud module (2026-08-27)
- EloSync-Mobile: WhatsApp Cloud inbox list, search, thread read, mark read, open chat by phone, send text inside the 24h service window, and send approved templates outside the window (permission-gated). API client
lib/api/whatsapp-cloud.ts(/whatsapp/integrations,/whatsapp/conversations, messages, templates). Routes underapp/(app)/(tabs)/whatsapp-cloud/. OAuth connect, media upload/download, lead linking, assignee changes, template sync, and integration settings remain web-only on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Team Chat module (2026-08-27)
- EloSync-Mobile: Team Chat conversation list, search, thread read, join public channels, start DM/group DM, send text messages, and mark read (permission-gated). API clients
lib/api/team-chat.tsandlib/api/users.ts(/conversations,/users). Routes underapp/(app)/(tabs)/team-chat/. Realtime (Reverb), attachments, reactions, pins, threads, channel create/settings, message edit/delete, and global message search deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Help Desk module (2026-08-26)
- EloSync-Mobile: Help Desk list, search, create, view, edit, delete, close, reopen, add text notes, and status change on edit (permission-gated). API clients
lib/api/help-desk.tsandlib/api/help-desk-categories.ts(/help-desk,/help-desk-categories). Routes underapp/(app)/(tabs)/help-desk/; assignee scoping matches web. Attachments, assign, contact/company/KB links, category CRUD, timeline, and trash/restore deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Knowledge Base module (2026-08-26)
- EloSync-Mobile: Knowledge Base list, search, create, view, edit, delete (permission-gated). API clients
lib/api/knowledge-base.tsandlib/api/knowledge-base-categories.ts(/knowledge-base,/knowledge-base-categories). Routes underapp/(app)/(tabs)/knowledge-base/; plain-text body converted to simple HTML for TipTap storage. Rich editor, attachments, notes, timeline, category CRUD, and trash/restore deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Documents module (2026-08-26)
- EloSync-Mobile: Documents list, search, upload, view, edit metadata, delete, and download/share (permission-gated). API clients
lib/api/documents.tsandlib/api/document-categories.ts(/documents,/document-categories). Routes underapp/(app)/(tabs)/documents/; file picker upload viaexpo-document-picker; authenticated download viaexpo-file-system+expo-sharing. Record links, category CRUD, trash/restore, bulk delete, and file replace deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Short Links module (2026-08-26)
- EloSync-Mobile: Short Links list, search, create, view, edit, delete, and share short URL (permission-gated). API client
lib/api/short-links.ts(/short-linksCRUD). Routes underapp/(app)/(tabs)/short-links/; vanity slug on create; basic UTM fields; click analytics and click log deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Assets module (2026-08-26)
- EloSync-Mobile: Assets list, search, create, view, edit, delete (permission-gated). API client
lib/api/assets.ts(/assetsCRUD). Routes underapp/(app)/(tabs)/assets/; status, category, purchase, and warranty fields on create/edit; vendor, employee, assign, notes, and timeline deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Announcements module (2026-08-26)
- EloSync-Mobile: Announcements inbox for all entitled users; managers with
announcements.updateget admin list/search; create, view, edit, delete, and mark-read (permission-gated). API clientlib/api/announcements.ts. Routes underapp/(app)/(tabs)/announcements/; read-tracking list and restore/force-delete deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Email module (2026-08-26)
- EloSync-Mobile: Email mailbox browse and read (permission-gated). API client
lib/api/email.ts(GET /email/accounts, folder messages, message detail, mark read). Routes underapp/(app)/(tabs)/email/; account and folder pickers; inbox/sent/drafts focus on mobile v1; compose, account setup, labels, attachments, templates, and signatures deferred. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Communication Templates module (2026-08-26)
- EloSync-Mobile: Communication Templates list, search, create, view, edit, delete (permission-gated). API client
lib/api/communication-templates.ts(/communication-templatesCRUD +/meta/contextspicker). Routes underapp/(app)/(tabs)/communication-templates/; WhatsApp channel only on mobile v1; placeholder picker, preview, and render/use deferred. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Inventory module (2026-08-26)
- EloSync-Mobile: Inventory stock level list and search, stock adjust (
POST /inventory/stock/adjust), transfer list/create/view/edit (draft)/dispatch/complete/cancel/delete (permission-gated). API clientlib/api/inventory.ts. Routes underapp/(app)/(tabs)/inventory/; product and warehouse pickers when Products/Warehouses are entitled; single-line transfers on mobile v1; movement history deferred. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Warehouses module (2026-08-26)
- EloSync-Mobile: Warehouses list, search, create, view, edit, delete (permission-gated). API client
lib/api/warehouses.ts(/warehousesCRUD). Routes underapp/(app)/(tabs)/warehouses/; active/default toggles on create/edit; notes and timeline deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Products module (2026-08-26)
- EloSync-Mobile: Products list, search, create, view, edit, delete (permission-gated). API clients
lib/api/products.ts(/productsCRUD +next-sku) andlib/api/product-categories.ts(/product-categoriespicker). Routes underapp/(app)/(tabs)/products/; category picker, stock tracking, and pricing fields on create/edit; rich-text description, notes, timeline, and category management deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Expenses module (2026-08-26)
- EloSync-Mobile: Expenses list, search, create, view, edit (draft only), submit, approve, reject, pay (when Accounting is not installed), cancel, delete (permission-gated). API clients
lib/api/expenses.ts(/expensesCRUD + workflow actions) andlib/api/expense-categories.ts(/expense-categoriespicker). Routes underapp/(app)/(tabs)/expenses/; category picker on create/edit; receipt upload, vendor/PO links, accounting account pickers, notes, and timeline deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Estimates module (2026-08-26)
- EloSync-Mobile: Estimates list, search, create, view, edit (draft only), send, accept, reject, convert to invoice, delete (permission-gated). API client
lib/api/estimates.ts(/estimatesCRUD +send/accept/status/convert). Routes underapp/(app)/(tabs)/estimates/;EstimateLinkPickersfor optional contact, company, quotation, and opportunity links; optional line item on create; email and PDF deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Credit Notes module (2026-08-26)
- EloSync-Mobile: Credit Notes list, search, create, view, edit (draft only), issue, apply, void, delete (permission-gated). API client
lib/api/credit-notes.ts(/credit-notesCRUD +issue/apply/void). Routes underapp/(app)/(tabs)/credit-notes/; required unpaid-invoice picker on create; optional line item; email and PDF deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Payments module (2026-08-26)
- EloSync-Mobile: Payments list, search, create, view, edit (draft only), post, void, delete (permission-gated). API client
lib/api/payments.ts(/paymentsCRUD +post/void). Routes underapp/(app)/(tabs)/payments/;PaymentLinkPickersfor optional contact and company; optional single-invoice allocation when Invoices is entitled; email receipt, PDF, deposit account, and withholding deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Invoices module (2026-08-26)
- EloSync-Mobile: Invoices list, search, create, view, edit (draft only), send, void, delete (permission-gated). API client
lib/api/invoices.ts(/invoicesCRUD +send/void). Routes underapp/(app)/(tabs)/invoices/;InvoiceLinkPickersfor optional contact, company, and quotation; recurring series, email, and PDF deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Contracts module (2026-08-26)
- EloSync-Mobile: Contracts list, search, create, view, edit (draft only), status transitions, delete (permission-gated). API client
lib/api/contracts.ts(/contractsCRUD +status). Routes underapp/(app)/(tabs)/contracts/;ContractLinkPickersfor opportunity (required) and optional quotation; convert-to-invoice deferred on mobile v1. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Projects module (2026-08-26)
- EloSync-Mobile: Projects list, search, create, view, edit, status transitions, delete (permission-gated). API client
lib/api/projects.ts(/projectsCRUD +status). Routes underapp/(app)/(tabs)/projects/;ProjectLinkPickersfor optional CRM links. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Meetings module (2026-08-26)
- EloSync-Mobile: Meetings list (60-day range), search, create, view, edit, cancel, complete, delete (permission-gated). API client
lib/api/meetings.ts(/meetingsCRUD +cancel/complete). Routes underapp/(app)/(tabs)/meetings/; Meetings tab; manual-link meetings with optional guest + reminder on mobile. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Calendar module (2026-08-26)
- EloSync-Mobile: Calendar list (60-day range), search, create, view, edit manual events, cancel, delete (permission-gated). API client
lib/api/calendar.ts(/calendar/eventsCRUD +cancel,/calendar/upcoming). Routes underapp/(app)/(tabs)/calendar/; Calendar tab inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Quotations module (2026-08-26)
- EloSync-Mobile: Quotations list, search, create, view, edit, delete, send, accept (permission-gated). API client
lib/api/quotations.ts(/quotationsCRUD +send/accept). Routes underapp/(app)/(tabs)/quotations/; Quotes tab inconfig/modules.ts; opportunity-linked create with optional line item. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — ToDos module (2026-08-26)
- EloSync-Mobile: ToDos list, search, create, view, edit, delete, mark complete (permission-gated). API client
lib/api/todos.ts(/todosCRUD; complete viastatus: completed). Routes underapp/(app)/(tabs)/todos/; ToDos tab inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Activities module (2026-08-26)
- EloSync-Mobile: Activities list, search, create, view, edit, delete, complete (permission-gated). API client
lib/api/activities.ts(/activitiesCRUD +complete). Routes underapp/(app)/(tabs)/activities/; Log tab;RelatedEntityPickersfor contact/company/lead links. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Opportunities module (2026-08-26)
- EloSync-Mobile: Opportunities list, search, create, view, edit, delete (permission-gated). API client
lib/api/opportunities.tswith/opportunitiesCRUD +GET /opportunity-stages. Routes underapp/(app)/(tabs)/opportunities/; Pipeline tab inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Companies module (2026-08-26)
- EloSync-Mobile: Companies list, search, create, view, edit, delete (permission-gated). API client
lib/api/companies.tsmirrors Contacts (/companiesCRUD). Routes underapp/(app)/(tabs)/companies/; registered inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Contacts module (2026-08-26)
- EloSync-Mobile: Contacts list, search, create, view, edit, delete (permission-gated). API client
lib/api/contacts.tsmirrors Leads (/contactsCRUD). Routes underapp/(app)/(tabs)/contacts/; registered inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — Tasks module (2026-08-26)
- EloSync-Mobile: Tasks list, search, create, view, edit, delete, complete/reopen (permission-gated). API client
lib/api/tasks.tsmirrors Leads (/tasksCRUD +complete/reopen). Routes underapp/(app)/(tabs)/tasks/; registered inconfig/modules.ts. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
EloSync Mobile — platform shell + Leads module (2026-08-26)
- EloSync-Mobile: React Query provider, shared screen/list/form components, dynamic module nav (
config/modules.ts), notifications tab, Leads list/search/create/view/edit/delete (permission-gated). Home dashboard links entitled modules. - Docs: Mobile user guide, product roadmap — Mobile, developer mobile guide.
- elosync-website: Roadmap entry for EloSync Mobile (in development).
Laravel Horizon queue dashboard (2026-08-26)
- EloSync-Backend: Laravel Horizon at
/horizonon the central domain, with the same role-only access as Pulse (superadmin, developer, tester). Central SPA users open Horizon viaGET /api/central/v1/horizon/enter(signed session bridge). Production worker config inconfig/horizon.php:supervisor-general(automations,whatsapp-inbound,whatsapp-outbound,webhooks,emails,lead-ingest,imports,default; max 3 processes) andsupervisor-email-sync(email-sync; max 1 process, 300s timeout). Scheduler runshorizon:snapshotevery five minutes. Pest:HorizonAccessTest. - EloSync-Frontend: Central Horizon link (Settings) opens the Horizon dashboard in a new browser tab and returns the Central SPA to the dashboard. Playwright:
e2e/tests/horizon/horizon.spec.ts(npm run test:e2e:horizon). - Ops: Replace Forge
queue:workdaemons with a singlephp artisan horizondaemon; deploy script useshorizon:terminateafter$ACTIVATE_RELEASE()instead ofqueue:restart. Keeppulse:check, Nightwatch, and Reverb as separate processes. - Docs: Laravel Forge — Horizon, Installation — monitoring, Central API.
EloSync Mobile — EAS development builds (2026-08-26)
- EloSync-Mobile:
expo-dev-client,eas.jsonprofiles (development,development-simulator,preview,production), bundle idscom.diligentcreators.elosync, location/secure-store config plugins, npm scripts for EAS builds andstart:dev-client. Replaces App Store Expo Go for SDK 57 device testing. - Docs: Mobile app developer guide.
EloSync Mobile — tenant auth scaffold (2026-08-25)
- EloSync-Mobile: New Expo app (
elosync-mobile) with tenant-only auth: login (email/password/remember, optional workspace branding), forgot password (workspace + email), email verification gate, SecureStore session, dashboard + profile shell. API client targets/api/tenant/v1only. - Docs: Mobile app developer guide.
Ask EloSync — expanded AI provider catalog (2026-08-25)
- EloSync-Backend: Central and tenant AI settings support ten text providers (OpenAI, Anthropic, Gemini, OpenRouter, Groq, Mistral, DeepSeek, xAI, OpenAI-compatible, Ollama) via
App\Support\AiProviderCatalog. Provider-aware defaults inAiConfigResolver, runtime key/URL injection for all Laravel AI drivers, and read-only catalog APIsGET /system-settings/ai-providersandGET /settings/ai-providers. OpenRouter and self-hosted providers accept custom model IDs; Ollama can omit an API key when a base URL is set. Catalog:ai1.1.0 → 1.2.0. Pest:AiProviderCatalogTest, extendedAiConfigResolverTest,AiTestConnectionTest,AiProvidersCatalogTest. - EloSync-Frontend: Settings → AI provider form loads the catalog API (fallback constants), shows base URL for self-hosted providers, and custom model inputs for OpenRouter/Ollama/OpenAI-compatible. Playwright: provider dropdown includes Gemini and OpenRouter.
- Docs: Central settings, Tenant settings — AI, AI Assistant, deployment AI.
Ask EloSync — workspace scope + clickable references (2026-08-25)
- EloSync-Backend: Ask EloSync now refuses obvious off-topic prompts (for example logo design) without calling the provider. Structured responses normalize reference aliases (
numeric_entity_id/relative_url→entity_id/url), extract prose when models dump JSON intoanswer, and coerce string suggested actions into labeled objects. Workspace AI instructions, industry, and preferred language are injected into trusted chat context. System prompt 2026-08-25.1 tightens workspace-only scope and reference field names. Pest:AiStructuredResponseNormalizerTest,AiWorkspaceScopeGuardTest,AiWorkspaceScopeTest. - EloSync-Frontend:
resolveAiReferenceHrefacceptsnumeric_entity_idandrelative_urlso invoice/lead/task citations render as in-app links. - Docs: AI Assistant user guide.
Central Pulse access (2026-08-25)
- EloSync-Backend: Laravel Pulse at
/pulseis limited to central roles superadmin, developer, and tester (role-based, not Spatie permissions). Central SPA users open Pulse viaGET /api/central/v1/pulse/enter, which returns a signed web URL that stores a short-lived session bridge. Pulse uses a dedicated cache store resolver so Laravel 13'scache.serializable_classes = falsedoes not break nestedCollectionquery caches. Pulse dashboard cards disable Livewire lazy loading sowire:polllive updates stay stable. Pulse is enabled by default in production (config/pulse.php). Pest:PulseAccessTest,PulseCacheSerializationTest. - EloSync-Frontend: Central Pulse link (Settings) opens the full Laravel Pulse dashboard in a new browser tab and returns the Central SPA to the dashboard. Playwright:
e2e/tests/pulse/pulse.spec.ts(npm run test:e2e:pulse). - Docs: Installation — monitoring, Central API.
Fix — manual lead create assigns to creator (2026-08-25)
- EloSync-Backend:
LeadService::createnow defaultsassigned_toto the authenticated creator when omitted, matching Contacts, Opportunities, and Projects. Users withoutleads.assignstill cannot assign to someone else on create, but their new lead is assigned to them so assignee-scoped lists include it. Inbound webhook/Meta ingest (no actor) is unchanged. Catalog:leads1.3.0 → 1.3.1. Pest:LeadTestcreator-default coverage. - EloSync-Frontend: Lead create form defaults assignee to the current user when the actor has
leads.assign, and injects the signed-in user into assignee options whenusers.listis unavailable (same pattern as Projects). - Docs: Leads user guide, developer guide, API.
International tax & withholding (2026-08-24)
- EloSync-Backend: Tenant Tax types catalog (
sales_tax,withholdingwith payment in/out directions), starter GL accounts1150/2150, invoice/credit-note tax split to2100,tax_type_idon billing lines, withholding on customer payment post and expense pay (3-line journals), contact/vendor default withholding types. Catalog:accounting1.7.0 → 1.7.2 (PATCH 1.7.2 fixes tax types list whendirection=asc|desc),invoices1.8.1. Pest:TaxTypeTest,PaymentWithholdingTest,ExpenseWithholdingTest, updated invoice accounting tests. - EloSync-Frontend: Tax types list/create/view/edit, billing line tax type picker, payment and expense pay withholding UI, contact/vendor default withholding, workspace tax country/registration in Settings → General. Payment and expense view pages show posted withholding. Playwright:
e2e/tests/accounting/tax-types.spec.ts. - Docs: user guide, developer guide, API, accounting overview, production readiness.
Fix — workspace retention settings save (2026-08-24)
- EloSync-Frontend: Settings → General Trash retention and Settings → Team Chat Message & file retention now persist reliably — save payload compares against loaded API values (not only RHF
dirtyFields), Select changes explicitly mark the form dirty, and the settings query cache updates from the PUT response so a stale refetch cannot revert the UI. - EloSync-Backend: Pest coverage for
team-chat.retention_daysonPUT /settings(alongside existingtrash.retention_daystests).
Short Links UX — branded 404, vanity slugs, copy icons (2026-08-24)
- EloSync-Backend: Branded public 404 page (
resources/views/short-links/unavailable.blade.php) with EloSync marketing CTAs when links are missing, paused, expired, or deleted. Optional custom vanitycodeon create (3–64 chars, reserved-slug validation).short_links.codewidened to 64 chars. Catalog MINOR:short-links1.1.0 → 1.2.0. Pest: branded 404, custom slug, soft-delete redirect tests. - EloSync-Frontend:
CopyableTextcomponent; inline copy icon on list Short URL column and view. Optional Custom slug field on create with live preview. Playwright coverage extended. - Docs: user guide, developer guide, API, production readiness.
Platform polish lanes A, B, C (2026-08-23)
- EloSync-Backend: Expense / Help Desk / KB attachments; document polymorphic links; credit note & purchase order PDF + email; lead import retry guard; projects calendar projection; dashboard widget data;
local:seed-demo --full; AI write tools + Opportunity Copilot API. Catalog MINOR/PATCH bumps across billing, expenses, help-desk, documents, knowledge-base, projects. Merged tomain(PR #148). - EloSync-Frontend: Workspace checklist card, Ask EloSync dashboard section, marketplace starter paths, attachment UI, billing email dialogs, Opportunity Copilot, dashboard widgets. E2e + TypeScript production fixes on
main(PR #143 + follow-up). - EloSync-Docs: API, user/developer guides, changelog; production readiness audit (Go after joint migrate + smoke).
- Verification: Pest polish suite green on Backend
main; Playwright module scripts (documents, expenses, AI, dashboard, help-desk) with cached demo login.
Documents soft record links — A7 (2026-08-23)
- EloSync-Backend:
document_linkspolymorphic pivot,DocumentLinkmodel,DocumentLinkService::syncon create/update,LinkableAsset+LinkableTaskvalidation rules,linkson document API resources. Catalog MINOR:documents1.1.0 → 1.3.0. Pest: link sync, entitlement gating, forbidden link inDocumentTest. - EloSync-Frontend: Related-record pickers on document create/edit (module + permission gated); Related records section on document view. Playwright:
documents.workflow.spec.ts. - Docs: documents overview, developer guide, API.
Platform polish — rich demo, workspace checklist, marketplace starters (2026-08-23)
- EloSync-Backend:
local:seed-demo --fullinstallsconfig/local-demo.php→demo_modules, chainsLocalReportsDemoDataService+LocalDemoCrossModuleStoryService(Companies/Contacts linked to Leads, sales→billing chain, Help Desk ticket linked to KB).--fulldefaults off. - EloSync-Backend: Workspace onboarding checklist —
onboarding_checklisttenant setting,GET/PATCH /settings/onboarding, auto-detect for timezone/branding/first lead, mail-test + marketplace visit timestamps. Pest:WorkspaceOnboardingTest. - EloSync-Frontend: Dismissible
WorkspaceChecklistCardon tenant dashboard (owner/admin). Dashboard marketplace upsell widgets when starter modules are missing; Marketplace Starter paths section (CRM / Billing / Operations slug bundles). - EloSync-Docs: Local demo data
--fullsection; changelog note.
Platform polish — Knowledge Base attachments + AI tools (2026-08-23)
- EloSync-Backend (A5):
knowledge_base_article_attachmentstable, model/policy/resource,KnowledgeBaseArticleService::attachFileon create/update (multipartattachmentfield),GET /knowledge-base/attachments/{uuid}/download, workspace storageusedBytes()integration. Catalog MINOR:knowledge-base1.0.0 → 1.1.0. Pest:KnowledgeBaseAttachmentTest. - EloSync-Frontend (A5): Attachment file input on article create/edit; download links on article view.
- EloSync-Backend (C2): New AI tools — write:
update_lead_status,log_activity(pending confirmation); read:get_help_desk_open_tickets,get_expense_pending_approval. Opportunity Copilot endpoints (/ai/opportunities/{id}/summarize,next-action,draft-follow-up). ExtendedAiWriteConfirmationTest+AiAuthorizationTest. - EloSync-Frontend (C2):
OpportunityAiCopilotSectionon opportunity view; Ask EloSync starter chips on tenant dashboard (module-entitled prompts); sharedai-starter-promptsfor panel + dashboard. - Docs: AI tools developer guide.
Billing docs sync — overview + production readiness (2026-08-23)
- EloSync-Docs: User-guide overviews for Invoices, Estimates, and Quotations now list shipped Download PDF + Email customer capabilities and catalog versions (1.8.0 / 1.5.0 / 1.6.0). Removed stale “e-mail delivery deferred” bullets.
- EloSync-Docs: Invoices production readiness — customer email marked Shipped (1.8.0); API and user guide cross-links.
Expense receipt attachments (2026-08-23)
- EloSync-Backend:
expense_attachmentstable,ExpenseAttachmentmodel/policy/resource,ExpenseService::attachReceipton create/update (multipartreceiptfield),GET /expenses/attachments/{uuid}/download, workspace storageusedBytes()integration, POST twin for multipart update. Catalog MINOR:expenses1.2.0 → 1.3.0. - EloSync-Frontend: Receipt file input on expense create/edit; download links on expense view. Pest:
ExpenseAttachmentTest. - EloSync-Backend: Help Desk ticket attachments —
help_desk_ticket_attachments, attach on create (attachmentfield), download route, storage usage. Catalog MINOR:help-desk1.1.0 → 1.2.0. - EloSync-Frontend: Attachment input on ticket create; download on ticket view. Pest:
HelpDeskAttachmentTest. - Docs: developer guides + API snippets for both modules (user guide deferred list updates).
Branded custom domain — SSL pending status (2026-08-23)
- EloSync-Backend: Custom domains track
ssl_provisioned_atandhosting_status(dns_pending/ssl_pending/active). DNS verify leaves SSL pending until operator marks provisioned (POST /api/central/v1/tenants/{tenant}/branded-domain/mark-ssl-provisionedorphp artisan branded:mark-ssl-provisioned {hostname}). Email/deep links use custom host only when SSL is provisioned. Grandfather migration setsssl_provisioned_atfor existing verified custom domains. - EloSync-Frontend: Settings → Domain shows Pending SSL certificate after DNS verify until hosting is active.
- Docs: Branded user/deployment/API/developer guides; Central API route table.
Short Links module (2026-08-23)
- EloSync-Backend: New free Operations module
short-links(catalog 1.0.0, not default-included). CRUD API, click analytics, public redirectGET /r/{uuid}, async click recording job, Pest coverage. - EloSync-Frontend: List/create/view/edit pages, KPI strip, analytics section, nav gated by module +
short-links.view. Playwrighttest:e2e:short-links. - Docs: user guide, developer guide, API.
- Short Links 1.1.0: Public URLs use 7-character codes on
SHORT_LINK_BASE_URL(e.g.https://go.elosync.com/r/abc1234). Legacy UUID redirects remain supported. Catalog bump 1.0.0 → 1.1.0.
Connected workflow polish — payment receipts + Help Desk ↔ Knowledge Base (2026-08-23)
- EloSync-Backend: Posted payments —
GET /payments/{id}/pdf(receipt PDF) andPOST /payments/{id}/email(payments.send, throttled like invoices).CustomerPaymentPdfService,CustomerPaymentEmailService,Emailedtimeline event. Catalog PATCH:payments1.1.0 → 1.2.0. - EloSync-Frontend: Payment record page — Download receipt + Email receipt (shared billing email dialog); Playwright coverage in
payments.workflow.spec.ts. - EloSync-Backend: Soft Help Desk ↔ Knowledge Base M2M (
help_desk_ticket_knowledge_base_articlepivot,LinkableKnowledgeBaseArticle,knowledge_base_article_idson create/update,PUT /help-desk/{id}/articles,articles_syncedtimeline). Catalog MINOR:help-desk1.0.0 → 1.1.0. - EloSync-Frontend: KB article multi-select on Help Desk create/edit; Related articles on ticket view; Linked tickets on article view when Help Desk is entitled; Playwright in
help-desk.workflow.spec.ts. - Docs: payments + Help Desk + Knowledge Base user/dev/API/deployment guides; roadmap deferred lists updated.
Connected workflow polish — document prefixes + billing email (2026-08-23)
- EloSync-Frontend: Settings → General → Document number prefixes for entitled modules (invoices, payments, credit notes, estimates, purchase orders, expenses, assets, Help Desk). Playwright:
tenant-settings.number-prefixes.spec.ts,billing/connected-workflow-polish.spec.ts. - EloSync-Backend:
POST /invoices|estimates|quotations/{id}/email— email customers with optional PDF attachment (requires document already sent). Activity timelineemailedevent; tenant email logs. Queued mailable stores PDF as base64 so theemailsqueue payload serializes correctly. - Catalog MINOR:
invoices1.8.0,estimates1.5.0,quotations1.6.0. - Docs: user guides, API, developer guides, tenant settings.
Marketing site runtime config (2026-08-23)
- elosync-website: API URL and marketing pixels (
NEXT_PUBLIC_*) resolve from Forge/config.js(window.env) at runtime — same model as EloSync-Frontend. Local dev uses.env.localwithpublic/config.jsstub. CI no longer bakes pixel IDs into the static export. - Deploy: Marketing Forge deploy script writes
config.jsfrom site.env; redeploy only (no CI rebuild) to change pixels or API URL. - Docs: Marketing pixels, Laravel Forge — Marketing.
Entity search labels — name + company (2026-08-23)
- EloSync-Frontend: Shared
src/lib/entity-display.tshelpers show name — company together in module search dropdowns (contacts, leads, opportunities, resellers), billing list Customer columns, invoice pickers, activity related columns, and record view cards — so duplicate person names are distinguishable by company.
Marketing pixels scaffold (2026-08-22)
- EloSync-Frontend:
src/lib/marketing-pixels/— optional env-gated GTM (existing), Meta Pixel, LinkedIn Insight Tag, and X Pixel;initMarketingPixels()+MarketingPageViewfor HashRouter SPA page views; Vitest coverage. - elosync-website:
MarketingScripts/MarketingPageViewwithNEXT_PUBLIC_*vars; client navigations push GTM virtual page views + Meta/X PageView. - Deploy: Forge
config.jskeysVITE_META_PIXEL_ID,VITE_LINKEDIN_PARTNER_ID,VITE_X_PIXEL_ID(alongsideVITE_GTM_ID). No prod IDs required until operators enable each vendor. - Docs: Marketing pixels.
Mermaid diagrams on docs site (2026-08-22)
- Enabled vitepress-plugin-mermaid so fenced Mermaid blocks render as diagrams (not code fences) across Deployment, Developer Guide, and User Guide pages (e.g. Laravel Forge topology chart).
- Dependencies:
mermaid,vitepress-plugin-mermaid. Redeploy Docs frombuild-artifactsafter CI rebuilds.
WhatsApp Cloud media (2026-08-22)
Catalog MINOR: whatsapp-cloud 1.2.0 → 1.3.0.
- Inbound image/document/audio/video: webhook captures Meta media id;
DownloadWhatsAppMediaJobstores privately undertenants/{id}/whatsapp/...and counts toward Storage quota (soft Storage gate). - Outbound media inside the 24h window:
POST /whatsapp/conversations/{id}/media; download viaGET .../attachments/{id}. - SPA inbox: attach control + attachment download links.
- Pest:
WhatsAppMediaTest. - Production readiness: WhatsApp Cloud production readiness — Go (inbound MIME/quota gates, attachment IDOR Pest, CatalogSeeder versions, Automation WA trigger e2e).
WhatsApp Automation triggers (2026-08-22)
Catalog MINOR: automation 1.0.0 → 1.1.0, whatsapp-cloud 1.1.0 → 1.2.0.
- Trigger
whatsapp.message_received(wired; requireswhatsapp-cloud) viaWhatsAppMessageReceived→IntegrationEventDispatcher. - Action
send_whatsapp_template(wired; requireswhatsapp-cloud) — resolves conversation byconversation_idorlead_id. - Pest:
WhatsAppAutomationTest.
WhatsApp Lead Source Driver (2026-08-22)
Catalog MINOR: whatsapp-cloud 1.0.0 → 1.1.0.
- Opt-in connection settings:
auto_create_leads(default off) +default_lead_source(defaultWhatsApp). - Inbound messages for unlinked conversations create/link Leads via
WhatsAppCloudLeadSourceDriver(source_reference = whatsapp_cloud,external_id = customer_wa_id) when Leads is entitled. - API:
PATCH /whatsapp/integrations; SPA settings toggle on the WhatsApp page when Leads is installed. - Pest: auto-create / duplicate link / Leads-not-entitled / settings update cases in
WhatsAppCloudModuleTest.
Custom Central path + failed-login alerts (2026-08-22)
- Per-install WHMCS-style Central SPA prefix:
VITE_CENTRAL_PATH_PREFIX(SPAconfig.js) + matching APICENTRAL_PATH_PREFIXfor password-reset / verify deep links. Default remainscentral. API stays/api/central/v1. - Failed Central login (and lockout) emails go to the targeted admin and platform
support_email(throttled for ordinary failures; lockouts always notify).
Feedback detail page + Feature Board parked (2026-08-22)
- Tenant reporters get a dedicated feedback detail page (
/#/feedback/{uuid}) with public comments, attachments, and a public status/module activity timeline (never internal notes or triage priority). - My submissions in Give Feedback links to the detail page; status/public-reply email CTA deep-links to the same page.
- Feature Board (shared sanitized ideation across tenants) documented as Parked on the product roadmap — distinct from Give Feedback / Help Desk / Central triage; do not open raw cross-tenant feedback lists.
- Docs: Give Feedback user guide, Central Feedback System, Product Roadmap, this changelog.
Notifications list and filters (2026-08-22)
- Bell dropdown shows unread only (up to 20); marking read removes the row so the next unread can fill in.
- Full page
/#/notificationswith status (all / unread / read) and date-from / date-to filters; Mark all read. - API
GET /notificationsacceptsstatus,date_from,date_to; list responses include standard paginationmeta. - Pest:
NotificationIndexFilterTest. Docs: tenant notifications API + Tenant Application user guide.
Accounting production readiness (2026-08-22)
Catalog PATCH: accounting 1.6.0 → 1.6.1, financial-reports 1.1.0 → 1.1.1.
- Invoice send/void: atomic with accrual journal post/void; period lock checked before status change; unpaid invoices without a JE can be repaired via send.
- Year-end close: zeros revenue/expense into Retained Earnings
3100(multi-line), idempotent, period lock in the same transaction. - Aged receivables: historical open balance as of date (payments/credits dated on or before
as_of). - Bank reconciliation: book balance as of statement date; excludes previously cleared lines; complete requires cleared = statement.
- GL CSV export: uncapped filtered stream; period unlock requires
accounting.void; opening balances / bank rec SPA gated bypost/create.
Bank reconciliation and aged receivables (2026-08-22)
Catalog MINOR: accounting 1.5.0 → 1.6.0, financial-reports 1.0.1 → 1.1.0.
- Bank reconciliations API: start against cash/bank, clear lines, complete.
- Aged receivables report:
GET financial-reports/aged-receivables(+ SPA report kind).
Fiscal periods and year-end close (2026-08-22)
Catalog MINOR: accounting 1.4.0 → 1.5.0.
- Setting
fiscal_year_start_month(1–12) under General. - Accounting periods CRUD + lock/unlock; journal post/void blocked in locked periods.
- Year-end close posts net income into Retained Earnings
3100and locks the FY period. - Balance Sheet Net Income uses fiscal YTD.
Chart hierarchy, headers, opening trial balance (2026-08-22)
Catalog MINOR: accounting 1.3.0 → 1.4.0.
- Accounts:
is_header(non-postable), parent account on create/edit UI; starter CoA adds Tax Payable2100and Retained Earnings3100; existing workspaces get missing system accounts viaensureMissingSystemAccounts. - Opening balances:
POST /accounts/opening-balancesposts a balanced multi-line journal; SPA/accounts/opening-balances. - Header accounts rejected on journal lines.
Accrual invoice and credit-note journals (2026-08-22)
Catalog MINOR: accounting 1.2.1 → 1.3.0. Soft optional deps: invoices → accounting, credit-notes → accounting.
- Invoice send: when Accounting entitled, posts Dr AR
1100/ Cr Sales Revenue4000for invoice total; storesjournal_entry_id; cancel voids the JE. - Credit note apply: posts Dr Revenue / Cr AR for credit total; stores
journal_entry_id(applied remains irreversible). - Payment post still Dr cash / Cr AR (settles receivable booked on send).
- No historical backfill for invoices issued before entitlement.
- Pest:
CustomerInvoiceAccountingTest,CustomerCreditNoteAccountingTest.
Accountant format presentation + CSV export (2026-08-22)
Catalog PATCH: accounting 1.2.0 → 1.2.1, financial-reports 1.0.0 → 1.0.1.
- Journals: classic Account / Debit / Credit / Memo grid on create/edit/view; list Amount column; currency formatting with blank zero Dr/Cr cells.
- General Ledger: currency columns, journal deep links, Export CSV.
- Financial Reports: currency formatting, classic two-pane Balance Sheet with Assets = L+E check, report chrome; Export CSV for TB / P&L / BS.
- API:
GET general-ledger/export,GET financial-reports/{trial-balance|profit-and-loss|balance-sheet}/export(streamed CSV). - Pest: FinancialReportTest export cases.
Account set balance / opening adjust (2026-08-21)
Catalog MINOR: accounting 1.1.0 → 1.2.0.
- Cash/bank Set balance: enter target + date; system posts delta journal (
ADJ-) viaCashMovementJournalService; default offset Owner Equity3000. - First-class
account_balance_adjustments(voidable history); never edits a stored balance column. - API:
POST /accounts/{account}/balance-adjustments, list/show/void under/account-balance-adjustments. - SPA: account view Set balance dialog + Balance adjustments list/void.
- Pest:
AccountBalanceAdjustmentTest. - Docs: Accounting user / overview / API / developer guides; this changelog.
Cash & bank money movements (2026-08-21)
Catalog MINOR bumps: accounting 1.0.0 → 1.1.0, payments 1.0.1 → 1.1.0, expenses 1.1.0 → 1.2.0. Soft optional deps: payments → accounting, expenses → accounting.
- Accounts:
is_cash_bank(asset only; starter Cash1000flagged), current balance on list/show, filter?is_cash_bank=1. - Shared
CashMovementJournalService— create+post / void two-line journals. - Payments:
deposit_account_id+ auto JE on post (Dr deposit / Cr AR); void voids JE. SPA Deposit to picker when Accounting installed. - Expenses: on pay,
paid_from_account_id(required with Accounting) + optionalexpense_account_id(default6000); JE Dr expense / Cr paid-from. SPA Mark as paid dialog. - Account transfers (
TRF-): Finance → Transfers; create posts Dr to / Cr from; void reverses. API/account-transfers. - Pest:
CashBankAccountTest,AccountTransferTest,CustomerPaymentAccountingTest,ExpenseAccountingTest. Playwright accounting suites include Transfers smoke. - Docs: Accounting / Payments / Expenses user + API + developer guides; this changelog.
AI tools depth + Ask EloSync starters (2026-08-21)
Catalog MINOR: ai 1.0.1 → 1.1.0.
- Read tools: Projects (
search_projects,get_project,get_overdue_projects), Opportunities (search_opportunities,get_pipeline_summary), Invoices (get_overdue_invoices,get_invoice_balance_summary); tool rows include numericid+uuid. ProjectServicelist supportsoverdue=true(planned/active/on_hold withends_onbefore workspace-local today).- Ask EloSync empty-state starter chips (module and view-permission gated); citation hrefs allowlisted via
isSafeRedirectPathand suppressed for unentitled modules. - Pest:
tests/Feature/Tenant/Ai/AiReadToolsTest.php(positiveavailableFor+ project module install); Vitestsrc/lib/ai-reference-href.test.ts; Playwright starter chip click. - Production readiness: AI Assistant — Go (R5/L1–L3 closed).
- Docs: AI tools, User guide, Tenant AI API.
Tasks assignee picker ignores lead-exclude flag (2026-08-21)
- Task create/edit/view assignee pickers no longer hide users flagged Exclude from lead assignment (or workspace owners). That flag remains leads-only.
- SPA:
filterTaskAssigneeOptions(suspended only); Vitestsrc/lib/task-assignees.test.ts. - Docs: Tasks user guide, Tasks developer guide, API.
Tenant API & Webhooks (2026-08-21)
Platform Settings → Developers (permission settings.manage_developers):
- Integration API tokens: Sanctum PATs (
es_…, shown once) for/api/tenant/v1; create / list / rotate / revoke. - Outbound event webhooks: subscribe to CRM/Sales/Meeting/Invoice events; signed envelope; delivery ledger; Send test; SSRF-safe POSTs on queue
webhooks. - Shared
SignedOutboundHttpClient; Automation webhook action reuses it (body-only HMAC kept for BC). - Pest:
tests/Feature/Tenant/Developers/*,tests/Unit/Http/SignedOutboundHttpClientTest.php. - Playwright:
e2e/tests/settings/tenant-settings.developers.spec.ts(full one-login workflow;npm run test:e2e:developers). - Docs: User guide, Developer guide, API, Production readiness — Go (migrate-first).
AI Test connection empty model fallback (2026-08-21)
- Central/Tenant Test AI no longer sends OpenAI an empty model when Default model is blank; resolver falls back to
gpt-4o-mini/gpt-4o. - SPA test payloads omit blank provider/model fields.
- Central (and tenant BYOK) Settings → AI: Default / Fast / Advanced models are searchable dropdowns scoped to the selected provider; switching provider resets incompatible models to suggested defaults.
AI Test connection agent API fix (2026-08-21)
- Fixed Central/Tenant Test AI connection calling non-existent
Ai::text()(laravel/ai v0.10 uses agents). Now usesAiConnectionTestAgent::prompt(). - Pest:
tests/Feature/AiTestConnectionTest.php.
AI Assistant production readiness 1.0.1 (2026-08-21)
Catalog PATCH: ai 1.0.0 → 1.0.1.
- Credit integrity: wallet
lockForUpdateon burn/grant/rollover; pre-provider credit ceiling + agentmaxTokenscap;ensurePeriod()on chat, Lead Copilot, and credits summary. - RBAC: Settings AI update/test require
ai.manage; SPA AI tab gated by module +ai.manage. - Rate limit:
throttle:ai(30/min) on message send and Lead Copilot routes. - Production readiness: AI Assistant — Go.
AI Assistant platform foundation (2026-08-21)
Catalog: billable ai module v1.0.0 plus prepaid packs ai-credits-1k, ai-credits-5k, ai-credits-20k (depend on ai).
- Tenant API: conversations, messages, credit wallet, Lead Copilot, pending write confirmation (
ai.use,ai.confirm,ai.manage). - Platform billing: dual-balance wallet (included monthly + prepaid packs), prorated activation grant, rollover command
ai:rollover-monthly-credits, HTTP 402 when credits exhausted. - BYOK + Central provider resolution via
AiConfigResolver; API keys masked in settings responses. - Pest:
tests/Feature/Tenant/Ai/*,tests/Unit/AI/AiConfigResolverTest.php. - Playwright:
npm run test:e2e:ai— one demo login session covering AI entitlement, Settings → AI validation/save, Ask EloSync send (graceful provider errors), and Lead Copilot summarize. - Docs: AI platform, AI tools, AI credits, Tenant AI API, User guide, AI deployment.
Form 422 validation visibility (all tenant modules) (2026-08-20)
- Server validation errors (HTTP 422) now always toast the first API message and map onto react-hook-form fields via shared
applyServerValidationErrors(tenant CRUD forms plus auth, settings, email, users, and related dialogs). - Assignee pickers filter ineligible users (workspace owners / excluded assignees) and show
assigned_tofield errors; quotation/estimate opportunity auto-fill only copies eligible assignees (fixes silent create failures). - Vitest covers the shared helper; Playwright dismisses multi-unread announcement inboxes so create actions stay clickable on the shared demo workspace.
Branded PDF long line-body pagination (2026-08-20)
Catalog PATCH bumps (old → new): quotations 1.5.1 → 1.5.2, estimates 1.4.1 → 1.4.2, invoices 1.7.1 → 1.7.2.
- Dompdf no longer clips long line-item body HTML off-page. Each pricing row stays in a short table; line body is a block-level
.line-bodybelow so multi-page scope text paginates. - Same layout fix on quotation, estimate, and invoice PDF Blade templates.
- Pest: long quotation line body asserts more than two
/Type /Pageobjects.
Branded PDF long-notes pagination (2026-08-20)
Catalog PATCH bumps (old → new): quotations 1.5.0 → 1.5.1, estimates 1.4.0 → 1.4.1, invoices 1.7.0 → 1.7.1.
- Dompdf no longer clips long Notes / Terms & Conditions to ~2 pages. Totals stay in a short right-aligned block; memo HTML is rendered as full-width blocks below so multi-page content paginates.
- Same layout fix on quotation, estimate, and invoice PDF Blade templates.
- Pest: long quotation notes assert end-marker text is present and the PDF has more than two
/Type /Pageobjects.
Dedicated record pages UX — production Go (2026-08-20)
- Production readiness: Dedicated record pages UX — Go (form validation visibility + Playwright hardeners remediated; migrate-first).
- Required-field errors now surface on floating inputs for Employees, Payroll profiles, and Reseller commission rates.
- Playwright: shared-demo authz skips, settings load retry, email empty-state / opt-in IMAP, team-chat settings save/delete hardeners, journal search after create.
Floating labels, search clear, semantic status badges (2026-08-20)
- Form text fields use floating labels (
FloatingInput/FloatingTextarea) on tenant create/edit pages. - Module list search (
Ctrl/⌘+F) no longer shows two clear (X) icons — native browser clear is hidden; only the app clear button remains. - Status badges use semantic colors (paid/posted/accepted → green, unpaid/partial/pending → amber, sent → sky, cancelled/void → red) instead of the tenant brand primary for every “positive” status.
Local demo login + shared Playwright tenant (2026-08-20)
- Local Vite prefills
/central/loginwithsuperadmin@saas.com/passwordand/loginwithdemo@demo.com/password(dev builds only). php artisan migrate:fresh --seed(APP_ENV=local) also runslocal:seed-demoand creates the demo-crm workspace owned bydemo@demo.com.- Playwright tenant suites sign in to that shared demo workspace via
E2E_DEMO_*(see Frontend.env.e2e.example). - Docs: Installation, Local Demo Data.
Dedicated record pages UX (all tenant CRUD modules) (2026-08-20)
Catalog MINOR bumps (old → new): leads 1.2.1 → 1.3.0, tasks 1.2.0 → 1.3.0, invoices 1.6.1 → 1.7.0, payments 1.0.1 → 1.1.0, quotations 1.4.1 → 1.5.0, estimates 1.3.3 → 1.4.0, contracts 1.2.1 → 1.3.0, products 1.2.0 → 1.3.0, expenses 1.1.0 → 1.2.0, documents 1.1.0 → 1.2.0, opportunities 1.1.1 → 1.2.0, todos 1.1.1 → 1.2.0, attendance 1.0.1 → 1.1.0, and 1.0.0 → 1.1.0 for remaining migrated modules (companies, contacts, credit-notes, purchase-orders, projects, meetings, activities, announcements, knowledge-base, help-desk, assets, warehouses, inventory, vendors, employees, departments, leave-management, payroll, accounting, resellers, reseller-payouts).
- Tenant CRUD uses list + dedicated create/view/edit pages with stacked cards (
RecordPage/RecordSection/FormSubmitSplit). Dialogs remain only for secondary flows (confirm, import, tags/categories). - Create/edit forms use separate submit buttons (no dropdown): Create (return to list) and Create & View; edit uses Save, Save & View, and Save & return to list. Invoices add Create & Send / Save & Send; payments add Post (save then post).
- Old query deep links (
/invoices?invoice=12) redirect to/invoices/12. List filters such as/payments?invoice=and/credit-notes?invoice=are unchanged. - Notification hrefs and backend task/lead route resolvers emit path URLs (
/tasks/:id,/leads/:id). - Blueprint: mirror Leads pages — Module Development, Shared Layout.
Dedicated record pages: Products, Vendors, HR modules (2026-08-20)
- Products, Vendors, Employees, Departments, Leave requests, and Attendance now use dedicated create, view, and edit pages (same pattern as Expenses) instead of form dialogs and detail sheets. Product categories, leave types, and leave review stay as dialogs.
- Vendor assignment notifications deep-link to
/vendors/:id. Query-param list redirects (?product=,?vendor=,?employee=,?department=,?request=,?record=) still land on the record page. - Docs: Products, Vendors, Employees, Departments, Leave Management, Attendance.
Dedicated record pages for operations modules (2026-08-20)
- Announcements, Knowledge Base, Help Desk, Documents, Assets, and Warehouses now use dedicated create, view, and edit pages (same pattern as Expenses) instead of form dialogs and detail sheets. Category dialogs stay as secondary flows.
- Notification deep links go to
/announcements/:id,/help-desk/:id, and/assets/:id. Query-param list redirects (?announcement=,?ticket=,?article=,?document=,?asset=,?warehouse=) still land on the record page. - Docs: Announcements, Knowledge Base, Help Desk, Documents, Assets, Warehouses.
Sales document convert integrity hardening (2026-08-19)
Catalog versions: quotations 1.4.1, contracts 1.2.1, estimates 1.3.3, invoices 1.6.1.
- Quote/estimate convert uses row locks (
lockForUpdate) so concurrent converts cannot double-bill. - Unique nullable
customer_invoices.estimate_idenforces one-shot estimate convert at the database. - Estimate convert soft-checks Invoices entitlement (same pattern as quotes/contracts).
- Soft-deleted invoices still block one-shot convert; API errors tell operators to restore or permanently delete.
- Contract progress billing: second and later invoices require
acknowledge_repeat_billing=true(SPA confirm sends it). - Go-live: Sales document convert production readiness — Go.
Invoices: auto-fill next recurring date from frequency (2026-08-19)
- Turning on Recurring invoice (or changing weekly / monthly / quarterly / semi-annually / yearly) fills Next invoice date one period after the issue date (or today). The date stays editable.
- Docs: Invoices user guide.
Sales documents: convert quotations and contracts to invoices (2026-08-18)
Catalog versions: quotations 1.4.0, contracts 1.2.0, invoices 1.6.0, estimates 1.3.2.
- Quotations: one-shot Convert to invoice (sent or accepted) when Invoices is installed. Copies lines, links
quotation_id, auto-accepts if sent, then hides convert. Soft Invoices entitlement (like PO → expense) — not a Marketplace hard dependency. - Contracts: repeatable Create invoice from Active contracts. Copies linked quotation lines, or a single line from contract
value. Setscustomer_invoices.contract_id. Warns if the linked quotation already has an invoice; the API still allows more contract invoices. - Estimates: convert stays one-shot. Blocked with 422 if the linked quotation is already invoiced (quote convert, prior estimate convert, or a contract invoice that copied
quotation_id). - Permissions:
quotations.convertandcontracts.convert(admin/manager).customer_invoices.quotation_idis not unique. - Docs: Quotations, Contracts, Estimates, Invoices; matching API + developer guides; Module Dependencies.
Contracts: opportunity auto-fill and rich HTML memos (2026-08-18)
Catalog version: contracts 1.1.0.
- Creating a contract: selecting an Opportunity auto-fills title (when empty), party name (from contact or company), value, currency, and assignee when those are set on the opportunity.
- Linked quotation: auto-selected only when that opportunity has exactly one quotation; with multiple quotations, leave unset for manual choice.
- New description field plus existing notes use TipTap rich HTML (headings, lists, bold/italic/underline), sanitized on save and display. Timeline comments stay plain text.
- Docs: Contracts user guide, Contracts API, Contracts developer guide. Go-live: Contracts 1.1.0 production readiness — Go (migrate-first).
Products: auto-generated SKU (2026-08-18)
Catalog version: products 1.2.0.
- Creating a product auto-fills the next SKU (
SKU-00001style). You can override it or click Generate SKU to refresh. - API:
skuis optional on create; omit/blank to auto-assign.GET /products/next-skureturns the preview. Optional tenant settingproducts_sku_prefix. - Docs: Products user guide, Products API, Products developer guide.
Billing product line picker — production ready (2026-08-18)
Catalog versions: products 1.1.1, quotations 1.3.1, estimates 1.3.1, invoices 1.5.1.
- Hardened
LinkableProduct: requiresproducts.view(or superadmin), rejects soft-deleted and inactive products. DocumentHtmlSanitizerstrips inlinestyleand neutralizesdata:/javascript:hrefs.- Billing line picker uses server-side product search with
status=active(SPA). - Pest coverage for estimate/invoice entitlement reject, convert + recurring
product_id, sanitizer cases. - Playwright: clear product without wiping edited name; picker hidden without Products.
- Canonical audit: Billing product line picker production readiness — Go (migrate-first).
Billing product line picker — production readiness draft (2026-08-18)
- Canonical go-live audit draft (superseded by Go note above).
database.mdquotation / estimate / invoice line schemas updated forname/body/ optionalproduct_id.
Billing documents: product line picker (2026-08-18)
Catalog versions: quotations 1.3.0, estimates 1.3.0, invoices 1.5.0.
- Optional product on quotation, estimate, and invoice lines (when Products is installed).
- Selecting a product in the form auto-fills name, rich details (from product description), and unit price; you can still edit those fields afterward.
- Clearing the product link does not wipe edited line text. The API stores the client-sent values plus optional
product_id(no server-side re-copy from the product catalog on save). - Docs: Quotations, Estimates, Invoices; matching API + developer guides.
Products: rich HTML description (2026-08-18)
Catalog version: products 1.1.0.
- Product description uses TipTap rich HTML (headings, lists, bold/italic/underline), sanitized on save and display.
- Docs: Products user guide, Products API, Products developer guide.
Billing documents: HTML line details & terms (2026-08-18)
Catalog versions: quotations 1.2.0, estimates 1.2.0, invoices 1.4.0.
- Line body uses the same TipTap rich HTML editor as document notes (headings, lists, bold/italic/underline).
- New document field Terms & conditions (HTML) on quotations, estimates, and invoices — shown on detail overview and branded PDFs under Notes.
- Server sanitizes notes, terms, and line bodies on save; PDFs render sanitized HTML for line details and terms.
- Docs: Quotations, Estimates, Invoices; API + developer guides.
Estimate create: opportunity auto-fill and quotation pick (2026-08-18)
- Creating an estimate: selecting an Opportunity auto-fills Contact, Company, and Assignee when set on the opportunity.
- Linked quotation: auto-selected only when that opportunity has exactly one quotation; with multiple quotations, leave unset for manual choice.
- Docs: Estimates user guide.
Quotation create: auto-fill from opportunity (2026-08-18)
- Creating a quotation: selecting an Opportunity auto-fills Contact, Company, and Assignee when those fields are set on the opportunity (still editable afterward).
- Docs: Quotations user guide.
Billing documents: line name/body, shared discounts, rich notes, PDFs (2026-08-18)
Catalog versions: quotations 1.1.0, estimates 1.1.0, invoices 1.3.0.
- Line items use short name (required) plus optional long body under the row (UI + PDF). Former
descriptioncolumn renamed. - Shared document
line_discount_type(none|percent|fixed) applies to every line; each line has onlydiscount_value.discount_totalis the sum of line discounts (no separate document-level discount amount). Tax after discounts;total = subtotal − discount_total + tax_total. - Rich HTML notes (headings, lists, bold/italic/underline) on document memos via TipTap; sanitized on display and PDF.
- Downloadable branded PDFs for quotations and estimates (same branding as invoices).
- Invoice PDF: discount rows, payments received table for posted allocations, PARTIAL status chip when partially paid.
- UI: line grid Name | Qty | Unit price | Discount | Tax | Total + body; shared totals panel (subtotal / discount / tax / total; invoices also paid / credits / balance). Invoice list/detail shows Partial badge when unpaid with
amount_paid > 0andbalance_due > 0(DB status staysunpaid). - Docs: Quotations user guide, Estimates user guide, Invoices user guide; Quotations API, Estimates API, Invoices API; Quotations developer guide, Estimates developer guide, Invoices developer guide.
Payments allocation picker UX (2026-08-17)
Catalog version: payments 1.0.1 (PATCH — allocation picker labels + auto-fill).
- Create/edit payment: invoice allocation options show contact · company · invoice number — balance due.
- Search allocations by contact name, company name, or invoice number.
- Selecting an invoice auto-fills Contact, Company, and Assignee from that invoice when those fields exist (still editable afterward).
- Docs: Payments user guide, Payments developer guide.
Module notes and activity newest-first (2026-08-17)
- Show payloads for Leads, Tasks, and other modules with notes/comments and domain activity history return those collections newest-first (
created_at, thenid). - Aligns detail-sheet tabs with existing
GET …/timelineordering (and Reseller, which already usedlatest()). - Feedback ticket comments remain oldest-first (conversation thread).
- Playwright: Leads/Tasks detail flows assert newest note/comment first; Lead Import e2e enables the free Storage module before CSV upload.
- Audit residuals remediated: broader Pest coverage (Contact/Company/Opportunity + all-model relation dataset), API show contract documented newest-first, composite indexes on
*_notes/ lead assignment histories for DESC order (no hard embed limit). - Production readiness: Newest-first notes & activity — Go (L1–L3 remediated).
Founding Beta invites (2026-08-17)
- Central Beta Applications can accept an applicant and send, copy, or resend a time-limited workspace registration invite.
- Accepted applicants register with
invite_tokenwhile public registration remains disabled; tokens are hashed, single-use (viaactivated_at), expiry-checked, and tied to the application email. After activation the hash is retained so the same link shows “already activated”. - The public beta page can self-resend an eligible invite without revealing whether an application exists.
- General settings add
founding_beta_enabled,founding_beta_apply_url, andfounding_beta_invite_ttl_days; public bootstrap exposes these non-secret values for registration-closed CTA behavior. - Playwright:
npm run test:e2e:beta-applications— one Central admin session covering settings validation, registration-closed CTA, invite issue, invite register validation/activation, and expired + Central resend. - Production readiness: Founding Beta invite — Go (H1–H3 / M2 / M4 / L1 / L5 remediated).
Tenant Impersonation history and Audit Logs (2026-08-17)
Central tenant details now surfaces real history instead of empty placeholders:
- Impersonation tab:
GET /tenants/{tenant}/impersonation-sessions(impersonation.list) — reason, admin, start/end, duration; Active / Ended / Expired status fromis_active/is_expired. - Audit Logs tab:
GET /tenants/{tenant}/audit-logs(tenants.read) — platformactivity_logrows for the workspace; listpropertiesallowlisted (reason, session ids, duration, tenant/actor/ip) withbefore/afterblobs omitted from API responses. - Frontend: Central resume after end (
resumeTokeninsessionStorage+skipSessionExpiryon end); Impersonation and Audit Logs tabs permission-gated with restricted empty states; ErrorState + retry on failed fetches. - Backend:
ImpersonationSession::isActive()/isExpired()honourexpires_at; MySQL virtual column + index onactivity_log.properties->tenant_idfor tenant audit list filters. - Production-readiness residuals M1 / L1 / L2 / L3 remediated — Tenant Audit & Impersonation History — Go.
Central defaults: SalesOS → EloSync (2026-08-17)
- Central
SystemSettingsSeederapp_name/company_namedefaults are EloSync (were stillSalesOS). - Idempotent migration renames leftover
SaleOS/SalesOSvalues only; custom Central branding is unchanged. config('app.name')fallback isEloSyncwhenAPP_NAMEis unset.
Press-kit brand defaults (2026-08-17)
Platform shell, favicon, web push chrome, and non-Branded email headers use the marketing press-kit App Store light icon (/brand/elosync-app-icon-light.png) when no Central/tenant branding upload is set. Custom logo/favicon uploads still override. WEBPUSH_ICON / WEBPUSH_BADGE / BRAND_DEFAULT_ICON default to that path relative to FRONTEND_URL.
Documents module 1.1.0 (2026-08-17)
Catalog version: documents 1.1.0 (MINOR — bulk soft delete + bulk force delete + uploader ownership).
POST /documents/bulk-delete(documents.delete) andPOST /documents/bulk-force-delete(documents.force.delete); max 100 ids; returnsprocessed/failed.- Force delete (single + bulk) requires the document to already be soft-deleted (Assets parity).
- Ownership: delete / force delete only for the uploader (
created_by) or workspace owner (superadmin); permission alone is not enough for another user’s file. - SPA: row selection on Active / Deleted-only lists with bulk Delete and Delete permanently toolbar actions (selection limited to deletable rows).
- Docs: user / API / developer / deploy notes; production readiness remains Go.
Documents module 1.0.1 (2026-08-17)
Catalog version: documents 1.0.1 (PATCH — production-readiness remediations).
- Restore re-checks Storage quota; multipart file replace via
POST /documents/{id}. - Soft-deleted documents join workspace trash retention (
TrashPurgeRegistry+trash:purge-expired);forceDeletingremoves disk objects (API + purge). - Platform audit:
document_restored,document_force_deleted(plus existing create/update/delete). - Canonical go-live: Documents production readiness — Go.
Documents module 1.0.0 (2026-08-16)
Catalog version: documents 1.0.0 (new free Operations Marketplace opt-in; is_default_included = false, is_billable = false; hard dependency on Storage).
- Flat internal document library: upload, categorize, download, soft delete / restore / force delete.
- Categories are flat (no nested folders); soft/force delete blocked while documents still reference the category.
- Uploads via
FileUploadService;WorkspaceStorageService::usedBytes()includes activedocuments.size_bytes(soft-deleted excluded). - Permissions:
documents.view|create|update|delete|restore|force.delete— admin defaults exclude force.delete; manager = view/create/update; staff = view. - To make billable later: Central → Modules → Documents → set
is_billable+ prices (existing Update Module API; no new Central UI). - Deferred: soft record links, nested folders, versioning, preview, dashboard widget, Automation triggers.
- Docs: overview, user, developer, API, deployment, production readiness, roadmap
Product roadmap — Future Expansion tiers (2026-08-16)
Documentation + marketing alignment (no new Marketplace SKU).
- Product Roadmap Future Expansion rewritten into Near-term (Documents → API & Webhooks → on-demand Customer Portal / Recruitment; WhatsApp post-MVP polish), Parked (marketing campaigns, portals/finance depth, AI planning, report builder), and Out of active scope (Manufacturing, QA, POS, E-Commerce) unless tenant demand forces them.
- Founding Beta roadmap relationship and Getting Started index updated for Business Operating System framing (not an open-ended ERP ladder).
- Marketing site roadmap / marketplace placeholders aligned: next focus Documents; POS and email-campaign cards removed from “planned” marketplace teasers.
Assets module 1.0.0 (2026-08-16)
Catalog version: assets 1.0.0 (new free Operations Marketplace opt-in; is_default_included = false, is_billable = false; no hard module dependencies).
- Equipment / fixed-asset register: auto-number (
AST-), status, category, identity + purchase/warranty fields, free-text location, assignment, notes/timeline. - Soft optional Vendor / Employee links (
LinkableVendor,LinkableEmployee). - Assignee eligibility matches CRM lead pool (owners excluded for explicit assign; create still defaults to creator).
- Permission grant migrate hardened against concurrent
role_has_permissionsunique races. - Deferred: depreciation/Accounting, Products/Inventory/Warehouse FKs, Help Desk maintenance, attachments/barcodes, dashboard widget, Automation triggers.
- Docs: overview, user, developer, API, deployment, roadmap
WhatsApp Cloud 1.0.0 production readiness — Conditional Go (2026-08-16)
- Audit: staff connection-status access, needs_reauth notify, outbound Queued-only send, OAuth HashRouter callback, headed e2e 6/6.
- Conditional Go pending CI + staging smoke / Forge WhatsApp queues / Meta App setup.
- Canonical page: WhatsApp Cloud production readiness.
WhatsApp Cloud module 1.0.0 (2026-08-16)
Catalog version: whatsapp-cloud 1.0.0 (new billable CRM module, $29 / $290).
- Meta WhatsApp Cloud API: connect WABA/phone, shared inbox, text send/receive, status webhooks, Meta template sync + outside-24h enforcement.
- Soft optional Lead link + timeline activity; Communication Templates
wa.meremains fallback. - Deferred: media, Automation triggers, Lead Source WhatsApp Driver.
- Docs: overview, user, developer, API, deployment, production readiness, roadmap
Reports 1.4.0 production readiness — Go (2026-08-16)
- Closed audit openers: companion PRs Backend #113 · Frontend #109 · Docs #134 · Website #28; Backend Laravel Tests + Code Quality Gate success; local catalog migrated to 1.4.0.
- Pest Analytics 23/23; headed Playwright 14/14; Frontend / Docs / Website Quality Gates success.
- Staging migrate + human smoke (manager Payroll / staff omit) remain ops pre-flight.
- Canonical page: Analytics production readiness.
Reports 1.4.0 production readiness — Conditional Go (2026-08-16)
- Audit of
feature/analytics-payroll-people-1-4-0companions (superseded by Go above after F19/F20 closed). - Canonical page: Analytics production readiness.
Reports People Payroll soft source 1.4.0 (Analytics) (2026-08-16)
Catalog version: analytics 1.3.1 → 1.4.0. Display name remains Reports.
- People domain soft source
payroll: pay runs overlapping the period, paid net (sum of linenetfor paid runs), payroll profile count, status mix rows (draft/approved/paid). - Soft gate: Payroll entitled +
payroll.view(stricter thananalytics.viewalone; staff roles omit compensation by default; no self-scope). - SPA: People copy / nav
anyModules/ donut chart label for Payroll; CSV includes payroll rows when entitled. - Docs: overview, user guide, developer, API, deployment, roadmap
Reports 1.3.1 production blockers closed — Go (2026-08-16)
- Closed audit blockers before merge: People leave/attendance self-scope (F16), catalog bump SemVer migration order (F17), People period whereDate filters (F18), website PR Quality Gate (F15).
- Pest Analytics 21/20+ (includes staff self-scope). Staging migrate through 1.3.1 + smoke remain.
- Canonical page: Analytics production readiness.
Reports 1.3.1 / People 1.3.0 production readiness — Go (2026-08-16)
- Audit of
feature/analytics-people-reports-e0a6companions (Backend #112 · Frontend #108 · Docs #133 · Website #27). - Go: local Pest Analytics green; headed Playwright 14/14; Docs / Frontend Quality Gates green; Backend Code Quality Gate + Laravel Tests green (dispatched). Staging migrate 1.3.1 + smoke remain.
- Canonical page: Analytics production readiness.
Reports chart types 1.3.1 (Analytics) (2026-08-15)
Catalog version: analytics 1.3.0 → 1.3.1. Display name remains Reports.
- Hub and domain reports pick chart types by context: pie/donut for status mix, horizontal bars for currency, area/line for longer series, vertical bars for simple compares.
- Card badges label chart intent (Share / Mix / Compare / Trend / Series / Bars). Empty periods show a clear placeholder.
- No API contract change; SPA-only Recharts polish on existing overview + domain payloads.
- Docs: overview, developer, API
Reports People / HR domain 1.3.0 (Analytics) (2026-08-15)
Catalog version: analytics 1.2.0 → 1.3.0. Display name remains Reports.
- New domain area
people: soft sources Employees, Leave Management, Attendance (entitlement +{module}.view). - KPI cards + per-module charts + breakdown table + CSV export (same contract as CRM/Sales/Billing/Purchasing).
- Period-aware hired/terminated, approved leave overlapping the period, and attendance records in range.
- SPA
/analytics/people, hub link, sidebar child under Reports. - Deferred still: Payroll in People report (stricter authz), report builder, saved/scheduled reports, email analytics.
- Docs: overview, user guide, developer, API, deployment, roadmap
Reports 1.2.0 production readiness — Go (2026-08-15)
- Audit findings F6–F7, F9–F12 addressed (refetch/a11y polish, tour length 35, website catalog 1.2.0, Backend CI dispatched, LocalSeed excluded).
- Status Go for staging smoke → production after companion CI green.
- Canonical page: Analytics production readiness.
Reports: plan People / HR domain (docs) (2026-08-15)
Docs-only future reference (no catalog bump):
- Next Reports domain area: People / HR — soft sources Employees, Leave, Attendance first; Payroll later with stricter authz.
- Keep Department reports and Financial Reports separate from Analytics.
- Likely catalog bump when implemented: analytics 1.2.0 → 1.3.0.
- Guides: overview, user guide, developer, roadmap
Reports 1.2.0 production readiness re-audit (2026-08-15)
- Re-audited companion PRs Backend #111 · Frontend #107 · Docs #132 · Website #26.
- Conditional Go: Pest Analytics 17/17 local; Docs CI green; Frontend Quality Gate blocked on module-tour length (35); website timeline must show catalog 1.2.0; Playwright not re-run this audit.
- Canonical page: Analytics production readiness.
Reports charts 1.2.0 (Analytics) (2026-08-15)
Catalog version: analytics 1.1.0 → 1.2.0. Display name remains Reports.
- Hub overview: one bar chart per entitled module (friendly module titles).
- Domain reports (CRM / Sales / Billing / Purchasing): separate charts per source module (e.g. Leads, Invoices); value chart when amounts exist.
- Charts use existing Recharts + theme
--chart-*tokens; no API contract change. - Deferred still: People / HR domain report, report builder, saved/scheduled reports, email analytics.
- Docs: overview, user guide, developer, API, deployment
Reports suite 1.1.0 (Analytics) (2026-08-15)
Catalog version: analytics 1.0.0 → 1.1.0. Display name Reports (slug remains analytics).
- Executive dashboard retained (
GET /analytics/overview). - Domain reports: CRM, Sales, Billing, Purchasing — KPI summary + breakdown table + CSV export (
GET /analytics/reports/{area},…/export). - Soft sources per area; empty report when none entitled / viewable.
- SPA Overview → Reports hub + domain pages; Playwright covers CRM CSV.
- Deferred still: report builder, saved/scheduled reports, charts, email analytics.
- Docs: overview, user guide, developer, API, deployment
Analytics module 1.0.0 (2026-08-15)
Free Operations Marketplace module (analytics catalog 1.0.0) — Business Intelligence & Analytics MVP.
- Executive overview API
GET /analytics/overviewwith shared period filters (DashboardPeriod); soft KPI sections for leads, opportunities, tasks, invoices, help-desk, and projects (omitted without entitlement + view permission). - Permission
analytics.view(admin / manager / staff). No hardmodule_dependencies. - SPA
/analyticsunder Overview with period controls and StatCard sections; Playwrighttest:e2e:analytics(modules / authz / workflow). - Distinct from Financial Reports (accounting TB / P&L / BS).
- Deferred: report builder, saved/scheduled reports, CSV export, email analytics.
- Docs: overview, user guide, developer, API, deployment, production readiness
Backend CI: pause auto runs + faster Pest (2026-08-15)
laravel.ymlandquality-gate.ymlautopush/pull_requesttriggers paused — both areworkflow_dispatchonly (Actions → Run workflow) to cut GitHub Actions billing. Re-enable push/PR triggers when required checks should resume.- When run manually, Pest uses
--parallel --processes=4on oneubuntu-latestrunner (:memory:SQLite; timeout 15 minutes).
Invoices status model 1.2.0 (2026-08-15)
Catalog version: invoices 1.1.1 → 1.2.0.
- Invoice statuses are now
draft/unpaid/paid/cancelled(replacingsent/partial/void). Send moves draft → unpaid; Cancel (API stillPOST …/void) moves draft|unpaid → cancelled. Partial payments stay unpaid until the balance clears to paid. - Data migration remaps existing rows:
sent/partial→unpaid,void→cancelled. - SPA filters, KPIs, badges, and Playwright expectations updated; permission slug
invoices.voidunchanged. - Docs: user guide, overview, developer, API, deployment
Invoices branded PDF + settings 1.1.1 (2026-08-15)
Catalog version: invoices 1.1.0 → 1.1.1.
- Invoice PDF redesigned to a professional layout: logo/header, BILL TO / details, colored line table, notes + totals, balance due bar, optional payment information footer — accent color from workspace button color.
- New workspace settings (Settings → Branding): company tagline, address, phone, website, default payment terms, default notes, bank name/account/IBAN/SWIFT. Missing fields stay blank; bank section hides when empty.
- PDF cache key includes a settings fingerprint so branding edits invalidate cached PDFs without waiting for invoice updates.
- Docs: user guide invoices, tenant settings, developer invoices, tenant settings, deployment invoices
Invoices recurring series + PDF 1.1.0 (2026-08-15)
Catalog version: invoices 1.0.0 → 1.1.0.
- Create/edit drafts with Recurring invoice, frequency, and a required Next invoice date (e.g. invoice on 15 Aug, next draft on 1 Sep; later invoices follow the frequency from that date). Sending the first invoice starts the series;
invoices:generate-recurring(daily, workspace timezone) creates the next draft with copied lines. - Stop recurring on the original invoice ends future generation without changing paid history. Optional checkbox voids the latest unpaid auto-generated draft/sent invoice (same ledger rules as Void).
- Download PDF from the invoice sheet and row menu (
GET /invoices/{id}/pdf,invoices.view). Email delivery is still deferred. - Pest
CustomerInvoiceRecurrenceTest(catch-up cap, command failure exit, PDF cache, timezone, stop/void, soft-delete skip). Playwright one-session headed workflow (validation, CRUD, Overview memo, PDF sheet + row menu, send/void, generate + stop with optional void, shortcuts, trash). Requiresdompdf/dompdf. - Production hardening: generator
chunkById+ per-tenant time budget; catch-up cap default 52; command FAILURE if any tenant series fails; PDF cache +WarmCustomerInvoicePdfJobon the default queue;throttle:invoices-pdf. Soft-deleted occurrences are treated as already issued for that date. - Docs: user guide, overview, developer, API, deployment, production readiness
Help Desk module v1.0.0 (2026-08-14)
Catalog version: help-desk 1.0.0 (new Operations SKU).
- Internal workspace ticketing MVP: tickets with status (
open/in_progress/waiting/resolved/closed), priority, tenant-managed categories, assignment, notes/timeline, optional soft Contact/Company links, KPIs,due_at, and dashboard widgethelp_desk_my_open - Free Marketplace opt-in under category
operations— no hard module dependencies; distinct from Central Give Feedback - Permissions:
help-desk.view|create|update|delete|restore|force.delete|assign|close|reopen(admin all exceptforce.delete; manager view/create/update/assign/close/reopen; staff view/create/update/close/reopen) - Tenant setting
help_desk_number_prefix(defaultHD-); overdue uses workspace timezone convention - Deferred: SLAs, email ingest/multi-channel, customer portal, Knowledge Base, attachments,
@mentions, Automation triggers, Communication Template context, Kanban - Docs: Help Desk overview, user guide, developer, API, database, deployment, Upgrade
Projects 1.0.0 + Tasks 1.2.0 (2026-08-14)
Catalog versions: projects 1.0.0 (new), tasks 1.1.2 → 1.2.0.
- Free Operations Marketplace opt-in Projects (
slug: projects): title, status board (planned→active/cancelled;active→on_hold/completed/cancelled;on_hold→active/cancelled), assignee + members, soft Contact/Company/Opportunity links, notes/timeline, stats/board, dashboard widgetsactive_projects/overdue_projects. - Visibility without
projects.assign: assignee OR member OR creator. Datesstarts_on/ends_on; overdue uses workspace timezone “today”. - Soft Task
project_id(nullable FK,LinkableProject) when Projects is entitled. - Explicitly deferred: Gantt, milestones, task dependencies, workload heatmaps, Calendar projection, Automation
create_project, tags,PRJ-numbers. - Docs: Projects overview, user, developer, API, deployment, Tasks, module dependencies, roadmap
Knowledge Base module 1.0.0 (2026-08-14)
Free Operations Marketplace module (knowledge-base: not default-included, not billable, catalog 1.0.0) for internal team articles, flat categories, and searchable FAQs. No public/customer portal.
- Articles: title, slug, excerpt, TipTap HTML body, optional category, status
draft/published/archived,published_at(UtcDateTime) - Permissions:
knowledge-base.view|create|update|delete|restore|force.delete— admin defaults exclude force.delete; manager = view/create/update; staff = view - View-only users see published only; editors with update see drafts / archived / trash
- Categories: flat; delete blocked while articles use them
- Notes + domain timeline + Spatie
LogsActivity+ PlatformAuditService; no hard module dependencies - Tenant API under
/api/tenant/v1/knowledge-base*and/knowledge-base-categories*; SPAsrc/pages/knowledge-base/; tourIdknowledge-base; Playwrighttest:e2e:knowledge-base - Deferred: public URLs, Help Desk links, attachments/image upload, nested categories, dashboard widget, Automation triggers, publish fan-out
- Docs: overview, user guide, developer, API, deployment
Storage module 1.0.0 (2026-08-13)
Catalog versions: storage / storage-10 / storage-50 / storage-100 / storage-500 / storage-1000 all 1.0.0.
- Free Marketplace Storage unlocks 1 GB total workspace content storage (chat attachments, feedback screenshots, lead imports). Branding logos/favicons and profile avatars stay on the VPS and do not count.
- Billable capacity packs set total allowance: 10 / 50 / 100 / 500 / 1000 GB ($4/$12/$20/$75/$120 monthly; yearly ~10× $40/$120/$200/$750/$1200). Packs require free Storage; only one pack may be active (cancel then buy to change size).
WorkspaceStorageServicesoft-blocks uploads withSTORAGE_REQUIRED/STORAGE_QUOTA_EXCEEDED; pack install conflicts returnSTORAGE_PACK_CONFLICT.- Team Chat install companion-installs free Storage; grandfather migrations cover existing workspaces. Settings → Storage usage panel; Marketplace pack conflict copy; upload toasts for quota errors.
- Prefer a dedicated Wasabi/S3 bucket for EloSync content. Pest
tests/Feature/Storage/WorkspaceStorageTest.php; Playwrighttest:e2e:storage. - Docs: Storage overview, user guide, developer, API, deployment, production readiness audit, object storage.
Expenses categories 1.1.0 (2026-08-13)
Catalog version: expenses 1.0.0 → 1.1.0.
- Tenant-managed expense categories (
expense_categories) replace the hardcodedtravel|office|software|utilities|otherenum. CRUD reusesexpenses.*(same pattern as Product Categories). - Expenses store
category_idwith embedded{ id, name, slug }on the API; list filter is?category_id=. Create/PO convert default to seeded Other. This is a first-party SPA contract change (MINOR) — migrate Backend before deploying the SPA. - Starter categories (Travel, Office, Software, Utilities, Other) lazy-seed on first list/create/convert. Starter slugs stay stable if renamed. Other cannot be deleted. Listing does not restore a deleted Travel/Office/Software/Utilities row; only missing Other is restored. Delete is also blocked while expenses still use the category.
- Spatie activity log name
expense-categoriesfor category CRUD (lazy seed is quiet). - SPA: Manage categories on Expenses (
expenses.createorupdateordelete); in-dialog create/edit/delete are gated separately. Form/filter/detail use the lookup. Playwright covers creating a custom category and using it on a new expense. - Docs: Expenses user guide, developer, API, database, deployment, Upgrade
Fix — overdue due dates vs workspace timezone (2026-08-13)
Catalog versions: tasks 1.1.1 → 1.1.2, todos 1.1.0 → 1.1.1, leads 1.2.0 → 1.2.1.
- Tasks, ToDos, and lead follow-ups no longer show as Overdue when the due time is still upcoming in Settings → General timezone (e.g. Asia/Karachi). SQL was comparing UTC-stored
due_atto workspacenow(). - Due today / this week KPIs and dashboard widgets use workspace calendar day bounds converted to UTC (
App\Support\UtcInstant). - Task / ToDo / activity / lead follow-up forms convert
datetime-localthroughappLocalInputToIso/isoToAppLocalInputinstead of sending naive strings or slicing UTC ISO. - Docs: Workspace timezone, Tasks, ToDos
Automation module v1.0.0 (2026-08-10)
Billable marketplace Automation SKU (automation): cross-module trigger → condition → action workflows with builder UI, templates, runs/logs, queue automations, and schedule dispatcher. Wired triggers for Leads, Tasks, Opportunities, Meetings, and Customer Invoices. NotificationSourceEnum::Workflow reserved for automation notifications; mentions/DMs use Mention / DirectMessage.
Hardeners: create always persists inactive then activate() (unwired triggers cannot bypass); outbound webhook SSRF + connect timeout; schedule 90s due window + 2-minute dedup + per-workflow cache lock; ExecuteAutomationRunJob::failed() + per-run cache lock + 60s timeout; delay_seconds capped at 24h; AUTOMATION_WEBHOOK_SECRET in .env.example. Docs hubs, Forge/automations queue, Meta Lead Ads event note, Playwright create → activate → run, production readiness. Marketing catalog lists Automation as a paid add-on ($29/$290) alongside Branded.
Lead webhook Copy JSON (2026-08-10)
- Leads → Integrations → Webhooks: Copy JSON next to Copy URL (also on the one-time secrets panel after create/rotate)
- Clipboard payload is the example POST body for website / Zapier wiring, with that endpoint’s default
source; extras map undercustom_fields - Docs: Leads user guide, Custom Lead Webhook
Email reading pane layout + multi-select bulk actions (2026-08-09)
Catalog version: email 1.2.0 → 1.3.0.
- Reading pane: Right panel (default, taller viewport, drag-resizable list) or Full panel; preference stored in the browser
- Multi-select in each folder/label list with bulk Mark read/unread, Move to, Labels (add/remove), and Delete (batch capped at 25 for IMAP timeout safety)
- API:
POST /email/messages/bulk(owned messages only; up to 25 per request) - Ops: run
php artisan migrate --forcefor the catalog bump (no new tables); keep the standingemail-syncqueue worker for mailbox connect/sync (bulk actions run in the HTTP request) - Docs: Email user guide, Email developer guide, Tenant Email API, Email deployment
Give Feedback — email reporters on Central updates (2026-08-09)
- When Central changes a feedback ticket’s status, or posts a public response, the submitting workspace user receives a platform-branded email (
FeedbackReporterUpdated) with the ticket number, title, and update details - Status emails are delayed ~90 seconds and suppressed if triage moved on (rapid updates only notify for the final status); public replies send immediately
- Mail includes a View my submissions CTA (
/#/dashboard?feedback=submissions) that opens Give Feedback on My submissions - Priority/module-only triage and internal notes do not email the reporter; missing/soft-deleted reporters are skipped silently
- Docs: Central Feedback System, Give Feedback
CRM digest email visual redesign (2026-08-09)
- Daily CRM (personal + team), task due digest, and department performance digest emails use shared table-based HTML templates: navy header with digest label (
DAILY SUMMARY/TASK DIGEST/DEPT DIGEST), tinted metric cards, and card-styled body sections (user CRM cards, task lists, department stats). - Views live under Backend
resources/views/emails/crm/; notifications pass data viaCrmDigestMailView+BrandedMail::apply(). - Docs: Daily CRM summary, notifications API digest notes.
Daily CRM summary — owner team mail, leads exclusion, mail chrome (2026-08-09)
- Owners (
superadmin) always receive the team (all-users) daily CRM summary; non-owners can still opt in via Receive all-users daily summary. - Personal digests omit the Leads block when the user has Exclude from lead assignment; leads alone do not trigger a personal send.
- Mail chrome: Branded module → tenant logo/name; otherwise platform (central EloSync) logo/name via
BrandedMail. Confirm Central Branding logo is set in production. - First-morning note: expect one team digest per Owner when any member has CRM activity — watch the
emailsqueue. - Docs: daily-crm-summary, branded guides, notifications API, tasks / RBAC user notes.
- Frontend Users dialog help text clarifies Owners already receive the team rollup.
Give Feedback — required searchable Module + tour tip (2026-08-09)
- Give Feedback: Module is required via a searchable picker of installed modules (prefilled from the current page when possible); choose Other and enter a required What area? when the report is not module-related
- Tenant API:
module_slugis required on create (max:100) - Module product tours append a shared final step pointing at the avatar menu → Give Feedback
- Docs: Give Feedback, Module Tours, Shared Layout
Fix — Team Chat in-pane scroll (2026-08-09)
Catalog version 1.3.1 → 1.3.2.
- Team Chat fills the remaining viewport height; the message list (and conversation sidebar / thread panel) scroll inside the chat chrome instead of growing the page/browser scrollbar
- Message auto-scroll and deep-link highlight scroll within the chat
ScrollArea, so long history no longer shrinks the browser scrollbar - Docs: Team Chat user guide
Fix — Email delete, HTML view, workspace trash retention (2026-08-09)
- Email: deleting a message already gone from IMAP removes the local EloSync copy; connection/auth failures keep the local copy and error
- Email: reading pane shows sanitized HTML in a sandboxed iframe (popups allowed for links); IMAP body extract walks nested multiparts + charset decode; show re-fetches only when
body_htmlis null - Settings → General: Trash retention (
trash.retention_days— Forever / 30 / 90 / 365, API-validated) with dailytrash:purge-expiredacross SoftDeletes modules (TrashPurgeRegistry, excludes EmailAccount)
Fix — @mention composer caret after pick (2026-08-09)
- Tasks comments / Leads notes / Team Chat: after picking an
@suggestion you can keep typing and Backspace/Delete removes the mention chip - Root causes:
display:flexon the contentEditable (Chrome caret stuck next to atomic chips), no Backspace handler forcontentEditable=falsechips, and chip@Nametext re-triggering autocomplete - Docs: developer guide Mentions UI notes for Leads / Tasks
Attendance status badge colors (2026-08-09)
Catalog version: attendance 1.0.0 → 1.0.1.
- Status badges in the Attendance table and detail sheet use fixed outline colors: Present (green), Late (red), Absent (slate), Half day (amber), Remote (blue)
- Docs: Attendance user guide
Pipeline board & table stage colors (2026-08-08)
Catalog versions: leads 1.1.1 → 1.2.0, opportunities 1.0.0 → 1.1.0, tasks 1.0.0 → 1.1.0.
- Leads / Opportunities board: column headers and cards tint from each stage’s stored color; Stage badges in the table and detail drawer use the same soft outline colors
- Tasks board: fixed color per status (To Do, In Progress, Waiting, Completed, Cancelled); Status badges in list and detail match
- Dashboard Pipeline Board widget prefers
stage.colorwhen present (falls back to the previous index palette) - Docs: user guides for Leads, Opportunities, Tasks, and tenant dashboard overview
Per-module colored tags — Tasks, ToDos, Opportunities (2026-08-08)
Catalog versions: tasks 1.1.0 → 1.1.1, opportunities 1.1.0 → 1.1.1, todos 1.0.0 → 1.1.0.
- Tags (Tasks / ToDos / Opportunities): per-module colored tags (name + color). Create and assign inline from the create/edit dialog (ClickUp-style chips). Filter list/board by tag. Separate catalogs — Tasks tags ≠ ToDos tags ≠ Opportunities tags. Leads keep their existing disposition tags system unchanged.
- Tag catalogs are create-only in this MVP (no rename / delete / reorder UI or routes). Detail drawers show tag badges; assignment changes go through the create/edit dialog (or
PUT …/tagsfor API clients). - Docs: user guides, API tag endpoints, developer/database/deployment tables + audit keys
Central feedback system + Founding Beta intake (2026-08-08)
- Backend: tenant + Central feedback APIs, public beta-application intake, Central permissions (
feedback.*,beta-applications.*), critical-bug mail to System Settingssupport_email - SPA: Give Feedback dialog (auto page/browser context + My submissions), Central Platform → Feedback inbox, Platform → Beta Applications triage
- Docs: Central Feedback System marked Implemented; Founding Beta status table updated; user guide Give Feedback
- Marketing site companion (separate repo): Founding Beta homepage funnel,
/beta/,/pricing/, module pages
Founding Beta program docs + feedback architecture (2026-08-08)
- Document Founding Beta: objectives, access model, tester expectations, and transition to public launch
- Document Central Feedback System architecture (tenant submit / Central manage) — originally Planned; now Implemented (see delivery note above)
- Product Roadmap: Business Operating System positioning note + Founding Beta cross-links
- Marketing site companion work (separate repo): Founding Beta homepage funnel,
/beta/,/pricing/, module pages
Fix — Lead convert RBAC + meta cleanup (v1.1.1) (2026-08-08)
Catalog version 1.1.0 → 1.1.1.
- Stub contact backfill that links an existing convert Opportunity now requires
opportunities.update(same as Opportunities API) - Clear stale
conversion_meta.company_id/company_uuidwhen the company cannot be resolved - Cache-lock company create by tenant + normalized name to reduce concurrent duplicates; document residual race without a unique name constraint
- Pest: stub opportunity backfill + update authz; stale company_id cleanup
- Playwright: convert with company + optional opportunity
- Docs: known limitation + this note
Leads v1.1.0 — convert creates Company + optional Opportunity (2026-08-08)
Catalog version 1.0.0 → 1.1.0.
- Convert a lead now creates/links a Company when Companies is installed and the lead has a company name (case-insensitive reuse;
companies.createonly when creating) - Optional Also create an opportunity on convert when Opportunities is installed (
create_opportunity+opportunity.name; links lead/contact/company; preserves lead assignee) - Contact convert behavior unchanged when Contacts is entitled;
conversion_metamay includecompany_id/opportunity_id - SPA lead drawer: company hint, opportunity checkbox + fields, post-convert View company / View opportunity links
- Pest coverage for company reuse, optional opportunity, validation, and create-permission gates
- Docs: user / developer / API leads (+ contacts convert notes, opportunities overview) + this note
Fix — Team Chat receive latency on 2 vCPU hosts (2026-08-08)
Catalog version 1.3.0 → 1.3.1.
- Broadcast
MessageSentafter the DB commit and before audit/mention side effects so peers see messages as soon as Reverb delivers - Defer audit + mention/DM notification fan-out until after the HTTP response (
afterResponse) - Conversation list: one batched unread query; do not hydrate every
#generalmember on each list request (DM members still load for titles) - SPA: inbound message cache updates use
startTransition; message rows are memoized
Team Chat v1.3.0 — channel creator settings (2026-08-08)
Catalog version 1.2.0 → 1.3.0.
- Channel creators can change visibility (public ↔ private), rename, and edit description
- Creators can permanently delete a channel and all history (messages, reactions, pins, attachments) after typing the exact channel name
- Workspace #general cannot be deleted
- SPA: Channel settings control in the conversation header (creator only)
- Pest coverage for creator authz, confirmation mismatch, #general protection, and slug reuse after delete
Fix — Team Chat Enter-to-send + snappier UI (2026-08-08)
- Team Chat composer: Enter sends, Shift+Enter inserts a new line (replaces Ctrl/⌘+Enter)
- Mentions picker still uses Enter to select; lead notes / task comments keep Enter = newline
- Reduced conversation-list refetch storms: send, mark-read, and realtime message events patch the sidebar cache locally instead of invalidating the full list every time
- Debounced mark-read; conversation detail uses list placeholder data while loading for faster channel switches
Team Chat module (2026-08-08)
Free Collaboration Marketplace module (team-chat: not default-included, not billable) for realtime workspace messaging — channels, DMs, mentions, threads, and file sharing.
Catalog versions: registered 1.0.0, then bumped 1.0.0 → 1.1.0 (threads / reactions / pins / typing), 1.1.0 → 1.2.0 (attachments / search / retention). Shipped through catalog 1.2.0; see 1.3.0 above for creator channel settings.
- Backend: conversations API under
/api/tenant/v1/conversations(module:team-chat+ Spatie permissions), #general auto-provision on install (+ join new users when eligible), Reverb private channeltenant.{tid}.conversation.{id}alongside the existing user notification channel, S3 attachments, message search,team-chat.retention_days+team-chat:purge-expired - Frontend: SPA route
/team-chat, unread + notification bell, deep links from mention/DM notifications, authenticated attachment downloads, shortcutsn/mod+f - Pest
tests/Feature/Tenant/TeamChat/TeamChatTest.php(licensing, provision, channels/DMs, mentions, broadcast auth, tenant isolation, authz/privacy, attachments/purge, mention prune) + Playwrighttest:e2e:team-chat(headed multi-user opt-in viatest:e2e:team-chat:headed) - Docs: user guide, Team Chat API,
chat_*schema in the database guide, tenant settings retention key, module development Reverb + always-on purge schedule note, entitlements/licensing catalog + this note
Fix — offline banner for real-world disconnects (2026-08-08)
- SPA sticky offline banner is driven by browser
offline/onlineand Axios transport failures (no HTTP response), not onlynavigator.onLine - Network-error toasts are suppressed while the banner owns connectivity UX (no stacked “Network error. Check your connection.” spam)
- While offline, SPA probes Laravel
/upon the API origin and marks restored when reachable; successful API responses also clear offline - Restoring connectivity invalidates React Query so dashboards refetch
- Marketing site: same-origin HEAD probe + visibility/online checks so the banner appears when WAN is down even if Wi‑Fi stays “online”
Email v1.2.0 — EloSync-only labels (2026-08-08)
Catalog version 1.1.0 → 1.2.0.
- Per-mailbox labels (name + color) stored only in EloSync — not synced to IMAP/Gmail
- Sidebar: IMAP folders, then a Labels heading with manage (+) and label filter list
- Apply/remove labels from the reading pane; messages can have many labels while staying in one folder
- Filter message list by label; disconnect removes labels with the mailbox
- Pest: labels create / update / show / duplicate reject / apply / clear / filter / delete / cross-user forbid
- Playwright: labels create / edit / assign / remove / filter / delete (after live mailbox connect)
- Docs: user / developer / API guides + this note
Docs — module catalog SemVer policy (2026-08-08)
Clarified modules.version bumping for all modules:
- PATCH (
1.0.1) for fixes; MINOR (1.1.0) for additive features; MAJOR (2.0.0) for large backward-compatible milestones - Modules ship without breaking changes; bumps use
DefaultModuleRegistrar::bumpVersiondata migrations - Code + catalog version are platform-wide; a bump does not auto-install or re-enable modules for workspaces that never entitled them
- Guides: Module Development Guide — catalog versioning, Deployment — module development
Email v1.1.0 — shared templates + multi-mailbox UX (2026-08-08)
Catalog version 1.0.0 → 1.1.0.
- Templates:
is_shared(default shared for new templates; existing rows stay private). Teammates can apply shared templates; creator or workspace owner can edit/delete any template. - Compose template list uses
for_compose=1(own + shared only). Picker labels show name · creator · Shared/Private (uuidremains the identity). - Multi-mailbox SPA: account switcher, Add mailbox, set default / disconnect; compose From select among connected accounts.
- Signatures remain personal. Gmail/Outlook still IMAP/SMTP + app passwords (no OAuth).
- Pest coverage for sharing authz + non-default compose From; Playwright asserts shared checkbox default and From/mailbox controls when a mailbox is connected.
- Docs: user / developer / API guides + this note.
Fix — @mention composer shows name only (2026-08-07)
- Lead notes / Task comments: after picking someone with
@, the composer shows a name chip (@Aamir Raza) instead of the raw@[Name](user:id)token - Stored body (and API payload) still uses
@[Display Name](user:ID)so mention notifications keep working - Table/board Latest note previews already render
@Namefor saved notes
Fix — Latest note @mentions show name (2026-08-07)
- Table and board Latest note previews (and hover tooltips) render
@Nameinstead of the stored@[Name](user:id)token - Applies wherever the shared latest-note component is used (Tasks, Leads, and other modules with notes)
Announcements module (2026-08-07)
Free Communication Marketplace module (announcements: not default-included, not billable) for workspace announcements with read tracking.
- Post-login unread dialog for every signed-in user when the module is installed (no
announcements.viewpermission) - Dashboard announcements section after the welcome greeting (unread only; hidden when empty or all read)
- Admin CRUD with
create/update/delete/restore/force.delete; readers list viaview_reads(first/last read + IP) - Publish fans out in-app notification
type: announcement - Pest
AnnouncementTest(incl. validation + non-UTC expiry) + Playwrighttest:e2e:announcements - Docs: user / developer / deployment / API + database dictionary
- Production hardening: unread-only dashboard Mark as read, queued publish fan-out, UTC expiry scope, workspace-timezone form helpers
Connectivity banner — offline / restored (2026-08-07)
- SPA and marketing site show a sticky top banner when connectivity is lost (“You are currently offline”) and a short green “connection was restored” notice when it comes back
- SPA: also triggered by Axios transport failures; health probe via API
/up; network toasts suppressed in favor of the banner (see 2026-08-08 fix)
Email — connect SMTP IP note (2026-08-07)
- Connect mailbox dialog and user guide note that the mail provider must allow SMTP from the application server IP (587/465) for Test and send
Email — security harden + docs honesty (2026-08-06)
- Reading pane sanitizes HTML bodies with DOMPurify before render
- CRM link API authorizes
viewon the target Lead/Contact/Company/Opportunity - Docs demote attachment download/storage and CRM linking UI (API/schema only in v1)
Email — per-mailbox sync interval (2026-08-06)
- Each connected mailbox has Auto-sync interval (default 5 minutes; 5 / 10 / 15 / 30 / 60)
- Scheduler runs
email:syncevery minute and only queues jobs for due mailboxes; Sync button still forces an immediate sync - Forge: dedicated
email-syncqueue daemon documented separately fromemails,default - Docs: user guide, API, deployment, Laravel Forge
Email — rich text compose (2026-08-06)
- Compose, Reply, Forward, Templates, and Signatures use a TipTap rich text editor (bold, italic, underline, lists, link, text color)
- Reply/Forward quote the original message as rendered HTML instead of showing source in a raw textarea
- Docs: user guide + developer guide editor notes
Email — reply, move, trash (2026-08-06)
- Reading pane: Reply, Forward, and Move to folder actions
- Delete moves the message to Trash (IMAP) when a trash folder exists; delete again permanently removes it
- Compose sends
in_reply_to/thread_keyfor replies - Docs: user guide + tenant API behavior notes
Email module — personal IMAP/SMTP (2026-08-06)
Free Communication Marketplace module for a personal mailbox inside EloSync (Inbox/folders, compose/drafts/sent, templates with , signatures). CRM link API and attachment schema are present; SPA linking UI and attachment file sync are deferred.
- Catalog: slug
email,is_default_included=false,is_billable=false, prices$0(not auto-installed) - Connect via IMAP/SMTP credentials with Gmail/Outlook app password presets — no OAuth in v1
- Sync/send jobs on dedicated
email-syncqueue; scheduleremail:syncevery five minutes; requires PHPext-imap - Fully separate from Settings → Mail /
EmailConfigResolver(platform transactional mail) and from Communication Templates (WhatsApp plain-text) - Personal isolation enforced in policies; Spa
/email+ Playwrighttest:e2e:email - Docs: user, developer, API, and deployment guides
Phase 1 — Resellers + Reseller Payouts (2026-08-06)
Free Sales Marketplace modules for partner accounts and a two-tier commission ledger on fully Paid customer invoices.
- Resellers (
resellers): partner directory withcommission_rate/owner_commission_rate, assignee scoping, soft delete, same-workspace invite-login (protectedresellerrole only), Vendor-parity threaded notes (note_entries/reseller_notes) and domain activity timeline (reseller_activities). Hard dependency on Payments. Catalog:is_default_included=false,is_billable=false, categorysales,sort_order=70. - Reseller Payouts (
reseller-payouts):reseller_commission_entriesaccrued onCustomerInvoiceBecamePaid(not Partial); formularesellerCut = T×R%,ownerCut = (T−resellerCut)×O%; approve → pay / void. Pay → void payment → pay again revives void ledger rows to accrued with refreshed snapshots. Hard dependency on Resellers. Catalog: same free Sales flags,sort_order=80. - Invoice link: nullable
customer_invoices.reseller_id(+LinkableReseller). - SPA: Resellers list + detail sheet tabs Overview | Notes | Activity | Access; Reseller Payouts ledger workflow.
- Deferred: cross-workspace identity (each tenant manages its own reseller logins; no Central multi-tenant reseller person).
- Docs: user/developer/deployment/API guides + module-dependencies + database dictionary.
- Finance ops: voiding a posted payment voids all commission rows for that invoice (including already
paid); re-paying revives them toaccrued.
Fix — Leads Ops merge-readiness audit (2026-08-06)
Pre-merge fixes from security/bug audit on feature/leads-operations:
- Persist Receive website leads on user update (
UpdateTenantUserRequest) - Snapshot commission rate when
assigned_tochanges viaPUT /leads/{id}(not only/assign) - Import Update mode no longer self-matches as a same-day duplicate; Skip/create still notify; preview write-gated
- Department weekly digest week bounds use each tenant’s workspace timezone after
applyRuntimeConfig - SPA: refresh
/meafter department save sois_department_managerunlocks equal import / reports without reload; edit lead omits forcedlead_type: directfor legacy null rows - Docs corrections: eligible assignees vs website flag, digest schedule timezone,
dashboard.view, current-stage metrics,lead_commission_rate/department_weeklyin database dictionary
Fix — Leads Playwright coverage for Ops gates (2026-08-05)
Headed test:e2e:leads updates for Lead Ops:
- Create-lead helpers target the Company textbox exactly so the Company Lead system tag does not collide
- Import keep-duplicate flow restored (was referencing an undefined locator)
- Equal-distribute e2e asserts the radio is disabled for non–department-manager owners, and runs a manager path with Departments + a staff member in the managed department
- Integrations waits for webhook endpoints to finish loading before create
Requires a local queue worker on imports (and usually emails) so import jobs leave queued.
Fix — lead inactivity setting save (2026-08-05)
Settings → Leads Inactivity alert (working days) did not persist overrides and did not surface validation errors. The form field used a dotted React Hook Form name (leads.inactivity_working_days), which nested into dirtyFields / errors and skipped the update payload. Renamed the form field to leads_inactivity_working_days and map to API key leads.inactivity_working_days on save.
Lead inactivity alerts (2026-08-05)
Assigned open leads (not in Won/Lost stages) trigger daily inactivity alerts when no meaningful activity occurs for the configured number of Mon–Sat working days (workspace timezone; Sundays excluded). Default threshold: 3 (leads.inactivity_working_days). Meaningful types: notes, follow-ups, stage/status changes, CRM activities, tag changes. Assignment/import/create alone do not reset the timer.
- Assignee receives
lead.inactive; department managers of the assignee receivelead.inactive_escalation(workspace owners when no managers). - Backend:
LeadInactivityService,leads:notify-inactive(daily schedule),NotificationIdempotency, PestLeadInactiveNotificationTest - Frontend: Settings → Leads tab; notification registry entries
- Docs: Leads user + developer guides
Department performance reports + weekly digest (2026-08-05)
Workspace owners and department managers get an in-app Department reports page (/reports/departments) with leads open/won/lost and tasks open/completed by department for a selected period. Metrics join assignees via department_user membership (plus linked employees and manager). A weekly scheduled command sends database + email digest notifications to owners and managers.
- Backend:
DepartmentPerformanceReportService,GET /reports/department-performance,reports:send-department-digest,DepartmentPerformanceDigestNotification, Pest authz + metrics + digest tests - Frontend: HR nav item (owner or
is_department_manager), period filters, summary cards + table - Docs: tenant reports API, Departments user + developer guides
Same-day duplicate leads — notify + Duplicate tag (2026-08-05)
When email or phone matches another lead created the same workspace calendar day (Settings → General timezone), EloSync applies the protected Duplicate system tag and sends lead.duplicate_detected notifications to the existing lead’s assignee, creator, and the actor (importer or manual creator). Import duplicate mode (Skip / Update / Keep) is unchanged; tagging and notifications still run for same-day matches. Inbound ingest skips still tag and notify when the match is same-day.
- Backend:
LeadDuplicateService::findSameDayByContactFields,LeadSameDayDuplicateService,LeadDuplicateDetectedevent + notification, hooks inLeadService,ImportManager,LeadIngestionService, PestLeadSameDayDuplicateTest(non-UTC day boundary) - Frontend: notification registry entry
lead.duplicate_detected - Docs: Leads user + developer guides
Website lead recipient pool (2026-08-05)
Custom webhook endpoints can assign to website recipients after ingest. Users opt in with Receive website leads (receive_website_leads). Eligible assignees in that pool receive equal distribution; empty pool leaves the lead unassigned and writes a platform audit event. Meta Lead Ads is not part of this pool.
- Backend: migrations,
LeadBulkAssignmentService::eligibleAssigneesForWebsite, post-create assign inLeadIngestionService,NotificationSourceEnum::Webhook, PestLeadCustomWebhookTest - Frontend: user flag toggle; webhook endpoint toggle under Leads → Integrations
- Docs: Users API, Custom Lead Webhook, Leads developer guide
Lead type Direct / Company (2026-08-05)
Leads now have a first-class lead type (direct | company), required when creating or updating via the API and lead form. Legacy rows may still have a null type until edited.
System tags Direct Lead / Company Lead are seeded and kept mutually exclusive with the stored type
Import mapping adds optional Lead Type column (defaults to
directwhen unmapped)Table, board cards, and detail drawer show lead type
Backend:
LeadTypeEnum,leads.lead_typemigration, tag sync inLeadService/LeadTagService, PestLeadTypeTestFrontend: lead form select, import field list, list/detail/board display
Docs: Leads user + developer guides, tenant leads API
Lead commission rate snapshot (2026-08-05)
Workspace users can have an optional Default lead commission % (users.lead_commission_rate). When a lead is assigned or reassigned via POST /leads/{id}/assign, the assignee’s rate is copied to leads.commission_rate; unassign clears it. Display and export only — no payout calculations.
- Backend: migrations,
TenantUserService,LeadService::assign, API resources, CSV/XLSX export column, PestLeadCommissionRateTest - Frontend: Users create/edit field; read-only commission on lead list (when assigned) and detail drawer
- Docs: Users API, Leads developer + user guides
Import equal distribute scoped to managed departments (2026-08-05)
Lead import Automatically distribute is limited to department managers. Eligible assignees are drawn only from members of active departments where the importer is manager_id. Non-managers cannot select the mode (API 422 + SPA disable). Bulk equal distribute is unchanged (org-wide). Login /me include is_department_manager.
Lead High Priority dashboard chip (2026-08-05)
Staff with leads.update can toggle High Priority from the lead detail drawer. The Dashboard High Priority widget lists open-pipeline leads with priority High only.
Behavior notes
Clearing the chip restores the previous priority when the chip was used to mark High in that browser tab (for example Urgent → High → Urgent). Otherwise clear falls back to Medium.
Breaking (dashboard): Urgent open leads no longer appear on the High Priority widget. Use Leads filters for Urgent, or mark the lead High to surface it on the dashboard card.
Backend:
high_prioritywidget filter + Pest coverageFrontend: Overview chip with priority restore; Playwright pipeline flow asserts chip, dashboard card, and Urgent restore
Docs: Leads / tenant application user guide, Dashboard API
Docs production build unblock (2026-08-05)
Docs CI publish to build-artifacts was failing the artifact secret scan on a placeholder FCM PEM example in Push notifications. The example no longer uses PEM armor headers, and the scanner only flags PEM headers that are followed by real base64 key material.
Lead tags + Contacts On/Off Boarded (2026-08-05)
Multi-select disposition tags on Leads (full catalog CRUD + sort order), seeded defaults (Not Contacted, Contacted, No Response with per-tag auto follow-up days, Invalid Number, Not Interested, Not Applied, Follow Up Later with forced follow-up), badges on board/table/detail/forms, and filter by tag. Tags never change pipeline stage or lead status.
Contacts gain a lifecycle field separate from soft-delete: On Boarded Clients / Off Boarded Clients (KPIs, filters, form, detail). Lead convert defaults new contacts to on boarded. Trash wording (Active only) is unchanged.
- Backend:
lead_tags+ pivot,PUT /leads/{id}/tags, tag reorder/CRUD,contacts.lifecycle_status, Pest coverage - Frontend: Manage tags dialog, tag picker + force follow-up UX, contacts lifecycle UI; Playwright
leads.tags.spec.ts - Docs: Leads/Contacts user + API + developer guides
Tenant user impersonation (2026-08-05)
Same-workspace Login as user for Owners (permission users.impersonate): Users row menu → reason dialog → amber banner → End impersonation restores the actor session. Targets cannot be self or workspace Owner; nested impersonation is blocked. Central platform impersonation is unchanged.
Hardening: one active session per actor (prior target PAT revoked), revoke PAT on session delete, reject soft-deleted targets, persist impersonation metadata in sessionStorage for refresh/banner restore.
Platform CI note: Laravel 13 #[AsCommand(name: 'db:seed')] made stancl/tenancy’s Seed command clobber core db:seed (missing --tenants → Pest suite red). Backend re-registers framework SeedCommand after package boot.
- Backend:
user_impersonation_sessions,POST /users/{user}/impersonate,POST /user-impersonation/{id}/end, additive permission sync - Frontend: auth-store modes (
central/tenant-user), Users dialog, AppLayout end routing; Playwrightusers.impersonate.spec.ts(tenant project) - Docs: Tenant Users API, Authentication, Tenant RBAC
Rotating dashboard inspire taglines (2026-08-05)
Central and tenant dashboards show a short curated inspirational line under the welcome greeting. A new line is picked on each visit (session-aware so the same line is not repeated back-to-back).
Board drag-and-drop auto-saves (2026-08-03)
Kanban drag-and-drop on Leads, Opportunities, Tasks, and ToDos now saves the stage/status immediately (same as Calendar reschedule). Dropping a card no longer opens the detail drawer; click still opens it for edits. Cards without update permission remain view-only (non-draggable).
Role permissions filtered by installed modules (2026-08-03)
Tenant Roles edit and the Permissions matrix only show core administration permissions plus groups for modules the workspace currently owns. Assigning a permission for an uninstalled module is rejected. Existing grants for a module that was later removed stay in the database (so reinstall can restore prior access) but stay hidden from the UI until the module is installed again.
Module product tours (Phase 5) (2026-08-03)
Every tenant module list page includes a short product tour (driver.js) explaining how the module works.
- PageHeader help icon (
tourId) — always re-runnable; first visit auto-prompts once vialocalStorage - Shared helper + per-module step configs under
EloSync-Frontend/src/tours/(Leads is the blueprint) - Wired for nav modules: CRM, sales, billing, purchasing, inventory, accounting, financial reports, and HR (employees, departments, leave, attendance, payroll)
- Docs: Module Tours; brief note in Shared Layout
Native FCM channel + device tokens (Phase 4b) (2026-08-03)
Additive Firebase Cloud Messaging HTTP v1 delivery for closed/background devices, using the same PlatformNotificationPayload mapper as VAPID Web Push. No parallel notification framework; Firebase remains optional for local/dev.
- Backend:
FcmChannel,fcm_device_tokensregister/unregister API, env-based service-account credentials (FCM_*), graceful skip when unconfigured - Shared wake trait: existing
SendsWebPushNotification/withWebPushChannel()also enqueues FCM (including mention notifications) - Frontend: registers an FCM web token when complete
VITE_FIREBASE_*config is present; VAPID path unchanged - Docs: deployment secrets + Forge checklist, architecture channels table, tenant API surface
Web Push hardened (VAPID Phase 4a) (2026-08-03)
Standards browser Web Push is production-hardened for all existing notification types (including mentions).
- Ops: deploy docs require
VAPID_SUBJECT/VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEYand a supervisedemails,defaultqueue worker (Web Push rides the same notification jobs) - Reliability: expired endpoints (
404/410) pruned; SW registers withupdateViaCache: 'none', activates immediately, and re-syncs onpushsubscriptionchange; Profile / Notification Center prompt when re-subscribe is needed - Click → HashRouter deep link via
PlatformNotificationPayloadMapperurl - Channel skips cleanly when VAPID is unconfigured (database + Reverb unaffected)
@mentions on Lead/Task notes (2026-08-03)
Mention teammates in Lead notes and Task comments with @ autocomplete. Stable body syntax @[Display Name](user:ID) persists mention rows (lead_note_mentions / task_note_mentions) and notifies the mentioned user (skip self; idempotent per note+user).
- Types:
lead.mentioned(database + broadcast + web push) /task.mentioned(database + web push) - Optional mail gated by Settings → Notifications toggles
lead_mentioned/task_mentioned(default off) - SPA registry routes to the lead/task detail sheet; composer shared on lead Notes and task Comments tabs
Leave self-service + salary deduction on approve (2026-08-03)
Staff (and managers) submit leave only for their linked active employee; admin/superadmin may create on behalf of others. Managers still approve others’ pending requests. Reject requires review_notes. Approve accepts optional deduct_salary (default !leaveType.is_paid); overriding the default requires notes. Payroll PayPeriodCalculator uses deduct_salary (legacy null rows fall back to !is_paid). Default staff role gains leave-management.create + leave-management.update (additive sync).
Leave catalog authz (2026-08-03)
Staff leave-management.create / update remain for leave requests only. Leave types and balances writes require admin/owner. Staff balance lists are scoped to self; managers may view for review.
Attendance self-service production hardening (2026-08-03)
- Staff cannot change attendance
status(manager/admin only); self marks force Present - Login and
/meincludeemployee_idfor self-service UX - Managers cannot approve their own leave requests
- Self check-in/out uses workspace timezone
Attendance self-service (2026-08-03)
Staff linked to an active employee can mark their own attendance and check out; managers/admins can mark anyone and correct times/notes. Login auto check-in is unchanged. Staff lists are scoped to their own records. Default staff role gains attendance.create + attendance.update (additive sync for existing workspaces).
Profile avatar upload (2026-08-03)
Users can upload a profile picture on Profile (Central and Tenant). The photo replaces initials in the topbar user menu and sidebar account chip.
POST/DELETE /api/{central|tenant}/v1/me/avatar(multipartfile: jpg/png/webp, max 2 MB)- Login and
/mepayloads include nullable absoluteavatar_url(API origin/storage/...) - Avatars always store on the
publicdisk (FILESYSTEM_AVATAR_DISK, defaultpublic) — never S3, even whenFILESYSTEM_DISK=s3 storage:migrate-to-s3skips/avatars/keys so profile photos stay on local public storage- Persist
storage/app/publicacross zero-downtime deploys (Forge shared storage) so avatars survive releases - Tenant
users.avatar_pathcolumn added; central already hadavatar_path - SPA resolves relative asset URLs against the API host so Vite/dev origins still show the photo
Compact UI density + keyboard shortcuts (2026-08-03)
Density
- Admin shell tightened toward shadcn / Laravel starter density: Inter UI font, narrower sidebar (
w-60), shorter topbar (h-14), smaller page titles, denser nav rows, tighter content padding and control heights.
Keyboard shortcuts
| Shortcut | Scope | Action |
|---|---|---|
Ctrl/⌘B | App shell | Collapse / expand sidebar (mobile: open/close drawer) |
Ctrl/⌘K | App shell | Command palette (navigate pages) — unchanged |
N | Active module list | Open New create dialog when permitted |
Ctrl/⌘F | Active module list | Focus that module’s search field (not browser Find / not ⌘K) |
Note: Chromium reserves Ctrl/⌘N for a new browser window — web apps cannot override it. Create uses bare N when focus is not in an input.
Shortcuts are ignored while typing in inputs. Module pages register via useModuleShortcuts (mirror Leads). UI hints use ShortcutHint on New buttons and list search fields.
Departments module (2026-08-03)
Marketplace HR module (departments, not default-included) for organizing users and employees:
- One User manager per department; one user may manage many departments
- Many-to-many membership for users and employees
- Admins see all departments; managers see managed departments + performance; staff see memberships
- Performance dashboard aggregates Leads/Tasks for linked users only (unlinked employees stay on the roster)
- Employee forms use multi-select
department_idswhen Departments is installed (legacy string column retained) - Manager/member pickers include the signed-in user (Users list API omits self) so a solo owner can manage a department
- Security: tenant-scoped Exists for manager/members; org-wide-only manager assignment;
manage_membersrequired to sync memberships on create/update - Playwright:
npm run test:e2e:departments
Docs: User Guide, Developer Guide, API, Deployment.
Create employee from existing user (2026-08-03)
Workspaces that installed Employees after creating users can now convert a login into a directory record:
- Users row action Create employee record (requires Employees module +
employees.create) POST /api/tenant/v1/users/{user}/create-employee(user-row lock; inactive when the user is suspended)- User list/show/update include nullable
employee_id - Race-safe active link via
employees.active_user_id+ unique(tenant_id, active_user_id); soft delete frees the link for re-provision - Salary remains on Payroll profiles (unchanged)
Tenant email notification toggles (2026-08-03)
Workspace admins can enable or disable event emails under Settings → Notifications. Defaults are all off to reduce SMTP cost during MVP testing. In-app and web push channels are unchanged. Daily task digests, daily CRM summaries, and auth emails (password reset / verification) always send.
Toggles: task assigned, task completed/reopened, task mentioned, follow-up created, follow-up due/overdue, lead mentioned, meeting events, other module assignments.
Workspace timezone convention + daily reminder gating (2026-08-03)
Code
- Task digest and daily CRM summary emails gated by Daily Reminder Time compare against the workspace timezone explicitly (
now($timezone)), not the server/UTC process clock. - Tenant
applyRuntimeConfig()applies timezone before mail overlay and no longer drops workspace timezone when mail provider config fails.
Docs convention
- Canonical Workspace timezone convention: one Settings → General timezone (e.g.
Asia/Karachi) drives Daily Reminder Time, task dues, lead follow-ups, meetings/calendar, and attendance office hours / login check-in. No per-module timezone. - Binding for all current and future modules: Module Development Standard principle + Definition of Done, Module Development Guide (Date and time), Module Architecture, and Platform Freeze Configuration row.
- User Guide Settings, Attendance, Tasks, Leads, Meetings, and Calendar pages cross-link that rule.
HR user sync, login attendance, and payroll deductions (2026-08-02)
Users ↔ Employees
- Creating a workspace user can provision a linked Employee when the Employees module is installed (
create_employee, default on). Suspend sets the linked employee to inactive; name/email updates sync to the employee record.
Attendance
- Tenant Settings → Attendance (when the module is installed): office start/end, grace minutes, work week days.
- Successful tenant login auto check-in for linked active employees (first login of the day; status
presentor newlate). - Attendance status enum adds
late. - Login check-in stores IP and optional GPS coordinates (
check_in_ip/check_in_latitude/check_in_longitude); shown on the attendance detail sheet.
Payroll
- Pay runs deduct unpaid leave and unexcused absences from base salary using the working-day calendar; line breakdown fields:
working_days,unpaid_leave_days,absent_days,days_present.
Verification
- Pest: attendance settings, login check-in, user→employee provision, pay-period calculator. Docs and UI updated for settings, attendance, payroll, and users.
Phase 7 HR — Employees, Leave Management, Attendance, Payroll (2026-08-01)
Architecture
- Shipped four free Marketplace SKUs under category
hr(sort70): Employees (employees), Leave Management (leave-management, sort20), Attendance (attendance, sort30), and Payroll (payroll, sort40). All are opt-in and non-billable. - Leave Management, Attendance, and Payroll hard-depend on Employees (
module_dependencies). Payroll optionally depends on Accounting for pay-run journal posting. - Roadmap capabilities (directory, leave types/balances/requests, daily attendance, profiles, pay runs) live inside these four SKUs — not separate Marketplace modules.
Domain
- Employees directory with employment type/status, optional workspace user link, soft delete, and stats KPIs.
- Leave types, per-year balances, and leave requests with
draft → pending → approved|rejected(cancel from draft/pending); approve applies days to balances underlockForUpdate(). - Attendance records unique per employee/date with check-in/out and presence status (
present/absent/half_day/remote). - Payroll profiles (one per employee) and pay runs that auto-generate lines from active profiles; lifecycle
draft → approved → paid; optional soft post to a draft Accounting journal (expense debit / liability credit).
Security & production readiness (2026-08-02)
- Full security audit:
/deployment/hr-phase7-security-audit· ops readiness:/deployment/hr-phase7-production-readiness. - Leave: balance upsert RBAC, days capped to date range, insufficient-balance reject on approve, approved requests not soft- or force-deletable,
remainingalways derived, transition locks. - Payroll: pay-run approve/pay/post
lockForUpdate, line grossmin:0, journal accounts must be expense/liability; default staff no longer haspayroll.view. - Employees: unique
user_idlink; Attendance:check_out >= check_in. - Re-verification: companion PR CI green (Backend #72 QG+Pest, Frontend #66, Docs #75); headed Playwright Employees/Leave/Attendance/Payroll 6/6 each; GO for opt-in staging → production after staff-role re-sync.
Frontend & verification
- HR nav group with Employees / Leave / Attendance / Payroll UI; API clients, query keys, and permissions wired for module pages.
- Playwright:
test:e2e:employees·leave-management·attendance·payroll(modules + authz). Pest undertests/Feature/Tenant/Employee,Leave,Attendance, andPayroll.
Docs
- User / developer / deployment / API guides for all four SKUs; roadmap, module-dependencies, and
database.mdHR tables updated; Phase 7 HR security audit + production readiness pages.
Phase 6 Finance — Accounting + Financial Reports (2026-08-01)
Architecture
- Shipped two free Marketplace SKUs under category
finance(sort60): Accounting (accounting, sort10) and Financial Reports (financial-reports, sort20). Both are opt-in and non-billable. - Roadmap capabilities Accounts / Journals / General Ledger live inside Accounting; Trial Balance / P&L / Balance Sheet live in Financial Reports.
- Financial Reports hard-depends on Accounting (
module_dependencies).
Domain
- Chart of accounts (
accounts) with starter system CoA on first list; journal headers/lines with balanced debit/credit validation; lifecycledraft → post → void(void excluded from GL/reports). - General ledger inquiry aggregates posted lines (no balance cache), paginated (default 100 / max 500) with period opening/closing balances. Manual double-entry only — no auto-post from Billing/Purchasing/Inventory; single currency.
- Production hardeners: journal
post/voiduselockForUpdate()transactions; system accountcodelocked in API; report/GL query dates FormRequest-validated; CoA seed race-safe.
Frontend & verification
- Finance nav: Accounts, Journals, General Ledger, Financial Reports; dual module/permission gates.
- Accounts trash/restore/force-delete UX; journal void confirm + optional reason; GL pagination.
- Playwright (per module, headed-ready): shared-session human suites (
accounting.modules/financial-reports.modules), authz/security (*.authz), smoke (*.workflow); combinedtest:e2e:finance/test:e2e:finance:headed. - Pest: balance/post/void, cross-tenant isolation, double-post/void-draft, GL pagination, FR module gate + date validation.
Docs
- User / developer / deployment / API guides for both SKUs; roadmap, module-dependencies, and
database.mdfinance tables updated.
Marketplace filters and dependency enable (2026-08-01)
- Tenant Marketplace adds filter chips for Installed, Available, Paid, and Free (combinable: install status × pricing).
- Module detail drawer shows required-dependency fee and an Install / Subscribe action when a hard dependency is missing. Paid dependencies open that module’s subscribe flow (billing cycle + checkout); free dependencies install in place.
- Tenant (and Central) marketplace show payloads enrich
required_modules/optional_modules/missing_required_moduleswithis_billable, prices, and install flags.
Tenant currency defaults + Products as services (2026-08-01)
Currency UX
- Money forms (invoices, estimates, quotations, opportunities, contracts, payments, expenses, purchase orders) and Products/Vendors currency fields now use the shared searchable currency list from Settings (
currencyOptions), not a five-code hard-coded dropdown or free-text code. - Create dialogs default currency to the tenant workspace currency from Settings (fallback
USD). Edit keeps the record’s existing currency. Credit notes still inherit currency from the selected invoice; their bare create fallback also uses the tenant currency.
Products as services
- The Products catalog is documented and labeled for both goods and services. Services use the same catalog with Track stock turned off; Inventory continues to manage only stock-tracked items. No new product/service type column.
Phase 5 Inventory (2026-08-01)
Architecture
- Shipped three free Marketplace SKUs under the new
inventorycategory (category sort50): Products (10), Warehouses (20), and Inventory (30). All are opt-in, non-billable, and not default-included; catalog module count increases from 20 to 23. - Inventory has a required hard dependency on Products. Warehouses is a soft Inventory integration: its UI CRUD remains module-gated while
ensureDefaultWarehouse()supplies active default codeMAINto operations that omit a location. - Products provides categories, stock-tracking flags, notes/activity, and nullable
product_idintegration on Purchase Order lines. Warehouses provides locations, notes/activity, and guards against deleting the sole default warehouse.
Stock and purchasing
- Added per-product/per-warehouse
stock_levels, immutablestock_movements, and draftablestock_transferswith line items.StockServiceis the sole stock mutation boundary, using transactions andlockForUpdate()to maintain a non-negative balance and movement ledger. - Stock adjustment types are
in,out, andadjust; transfers followdraft → in_transit → completed|cancelledand post paired stock movements only on completion. - Purchase Order lines now optionally link a Product. A received Purchase Order posts stock-in once for linked products with
track_stock=truewhen Products and Inventory are entitled;partially_receivedremains acknowledgement-only. Receipt may selectwarehouse_idor use the default. - PO receive integrity:
POST …/statuswithreceived/partially_receivedroutes throughreceive()(same stock path as/receive); status + stock post share one transaction withlockForUpdate()on the PO so concurrent receives cannot double-post and a failed stock post does not leave the PO stuck as received.
Frontend, verification, and docs
- Added Products, Warehouses, and Inventory tenant pages using the existing AppLayout, entitlement/permission-gated navigation, services, types, query keys, forms, detail sheets, and shared states.
- Added Pest coverage under
tests/Feature/Tenant/Product,Warehouse, andInventory, plus Playwright commandstest:e2e:products,test:e2e:warehouses,test:e2e:inventory, and shared-sessiontest:e2e:inventory-phase/test:e2e:inventory-phase:headed(Products → Warehouses → Stock → PO receive, with form validation and human-mistake paths under one login). - Added User, Developer, Deployment, and Tenant API guides; updated module dependencies, database dictionary, Purchase Order receiving documentation, roadmap, changelog, and documentation sidebars.
Out of scope
- Serial/lot tracking, inventory valuation, and COGS are not included in Phase 5.
Expenses module + Purchase Order convert (2026-08-01)
Architecture
- Third and final Phase 4 (Purchasing) module (backend model
Expense) — a standalone operational-expense record: single amount (no line items), category, optional links to a Vendor and/or Purchase Order. Flat Laravel,module:expenses+ Spatie RBAC, mirrors the simplified Estimate/Payment notes/timeline/assignee-scope/status-machine pattern. - No hard
module_dependencies— Expenses installs standalone.vendor_idandpurchase_order_idare both nullable and validated only at the point of use (LinkableVendor, newLinkablePurchaseOrderrule) when the corresponding module (vendors/purchase-orders) is entitled — a soft dependency, unlike Purchase Orders' hard dependency on Vendors. Free Marketplace opt-in — catalog categorypurchasing,is_default_included=false/is_billable=false,sort_order=30(after Purchase Orders'20). - Status workflow
draft → submitted → approved|rejected,approved → paid,draft|submitted → cancelledviaExpenseStatusEnum::allowedTransitions();rejected/paid/cancelledare terminal. Onlydraftexpenses can have their fields edited — workflow actions (submit/approve/reject/pay/cancel) remain available regardless. - Convert a Purchase Order to an Expense (
POST /purchase-orders/{id}/convert) — the deferred Phase 4 Milestone 2 feature, now shipped as part of Milestone 3. One-way, one-time: creates a draftExpensefrom asent/partially_received/receivedpurchase order (title,amount←total,tax_amount←tax_total,currency,vendor_id,assigned_to,notescopied;categoryalwaysother). Re-running the conversion is blocked by an existingExpenserow for thatpurchase_order_id(including soft-deleted). Gated by a new soft, call-time entitlement check for the Expenses module insidePurchaseOrderService::convertToExpense()— not amodule_dependenciesrow — so Purchase Orders keeps working with Expenses uninstalled; only the convert endpoint 422s until Expenses is installed. New permissionpurchase-orders.convert.
Backend
- Tables:
expenses,expense_notes,expense_activities - Auto-numbered (
EXP-00001, prefix from newexpenses_number_prefixtenant setting, retried on duplicate via the sharedRetriesOnDuplicateNumbertrait) - Enums:
ExpenseCategoryEnum(travel,office,software,utilities,other),ExpenseStatusEnum(draft,submitted,approved,rejected,paid,cancelled),ExpenseActivityTypeEnum(Created,Updated,Assigned,StatusChanged,NoteAdded,Deleted,Restored) - Permissions:
expenses.view|create|update|delete|restore|force.delete|assign|submit|approve|reject|pay|cancel - Model (
Expense,BelongsToTenant, soft deletes, UUID,LogsActivity) + factories, controller, form requests, API resources, policy (assignee-scoped view/update/submit/cancel; approve/reject/pay are not assignee-scoped), service (ExpenseService: numbering, status machine, notes, timeline, assign), events,AppServiceProvider-registered event subscriber (audit + assignment notification) - New
LinkablePurchaseOrderrule (mirrorsLinkableVendor) — validatespurchase_order_idbelongs to the tenant and the Purchase Orders module is entitled, only when a value is supplied PurchaseOrderService::convertToExpense()mirrorsEstimateService::convert()'s draft-creation pattern but swaps the hard-dependency check for a softEntitlementService::hasModule()check; newPurchaseOrderActivityTypeEnum::Convertedtimeline entry andPurchaseOrderConvertedevent- Catalog registration (no
module_dependenciesrow) viaDefaultModuleRegistrarmigration (migrate-only); permissions + default role map additive migrations for bothexpenses.*andpurchase-orders.convert expenses_number_prefixadded toTenantSettingDefinitionsandUpdateTenantSettingsRequestvalidation- Pest:
tests/Feature/Tenant/Expense/ExpenseTest.php(CRUD, soft FK validation against module entitlement, status workflow, assignee scoping, module gate), Purchase Order convert coverage added toPurchaseOrderTest.php(entitled vs blocked, one-time conversion, status guard) - Module counts bumped from 19 → 20 across
MarketplaceCatalogTest,TenantProvisioningTest,TenantAuthTestfixtures
Frontend
src/pages/expenses/— list page (KPIs: total, mine, draft/submitted/approved/rejected/paid, approved & paid value; status/category/vendor/PO/assignee filters, DataTable), create/edit form dialog (category select, amount, tax amount, currency, expense date, notes, optional vendor/purchase orderSearchableSelectpickers rendered only when the corresponding module is entitled and viewable), detail sheet (overview/notes/timeline tabs; submit/approve/reject/pay/cancel/assign/notes/edit (draft only))- Added to the existing tenant sidebar Purchasing group, after Purchase Orders
expenseService,QUERY_KEYS.expenses,PERMISSIONS.expenses- Purchase order detail sheet: new Convert to expense action (permission
purchase-orders.convert, visible only when the PO status is convertible, the Expenses module is entitled, and it hasn't already been converted) with a confirm dialog; shows a link to the resulting expense under "Related records" once converted - Notification registry:
expense.assigned→/expenses?expense={id} - Playwright:
e2e/tests/expenses/expenses.workflow.spec.ts(npm run test:e2e:expenses) — enablesvendors+purchase-orders+expenses, covers KPI smoke, create/submit/approve/pay workflow, and end-to-end PO → Expense conversion
Docs
- expenses-overview.md / expenses.md (+ developer / production)
- api/tenant-v1-expenses.md; api/tenant-v1-purchase-orders.md updated with the convert endpoint
- purchase-orders-overview.md / purchase-orders.md updated — convert-to-expense moved from deferred to shipped
- Module Dependencies updated — Expenses' soft dependencies on Vendors/Purchase Orders, and Purchase Orders' soft use of Expenses for convert, both marked shipped
- Product Roadmap — Expenses marked shipped; Phase 4 — Purchasing goal Achieved
Deferred
- Receipt attachments, reimbursements, GL posting, multi-line expenses (all explicitly out of scope for this MVP)
Purchase Orders module (2026-08-01)
Architecture
- Second Phase 4 (Purchasing) module — Milestone 2, header + line-item procurement documents a tenant issues to its own vendors (backend model
PurchaseOrder). Flat Laravel,module:purchase-orders+ Spatie RBAC, mirrors the Estimates notes/timeline/assignee-scope/lines/status-machine pattern, swapping Estimates' optional related-record pickers for a single requiredvendor_id. - Hard dependency on Vendors — same pattern as Estimates → Invoices; Marketplace blocks installing Purchase Orders until a workspace already has Vendors entitled (every purchase order requires a vendor). Free Marketplace opt-in — catalog category
purchasing,is_default_included=false/is_billable=false,sort_order=20(after Vendors'10). - Status workflow
draft → sent → partially_received|received|cancelled(alsosent → cancelled,partially_received → received|cancelled);received/cancelledare terminal. Receiving is acknowledgement only — no Inventory stock posting (no Inventory module exists on this platform). Convert-to-expense is deferred to Phase 4 Milestone 3.
Backend
- Tables:
purchase_orders,purchase_order_lines,purchase_order_notes,purchase_order_activities - Auto-numbered (
PO-00001, prefix frompurchase_orders_number_prefixtenant setting, retried on duplicate via the sharedRetriesOnDuplicateNumbertrait) - Enums:
PurchaseOrderStatusEnum(draft,sent,partially_received,received,cancelled),PurchaseOrderActivityTypeEnum(Created,Updated,Assigned,StatusChanged,NoteAdded,Deleted,Restored) - Permissions:
purchase-orders.view|create|update|delete|restore|force.delete|assign|send|receive|cancel - Model + factories, controller, form requests, API resources, policy (assignee-scoped view/update/send/receive/cancel), service (
PurchaseOrderService,syncLines/recalculateTotalslike Estimates;receive()only acceptspartially_received/received), events,AppServiceProvider-registered event subscriber (audit + assignment notification) LinkableVendorrule —vendor_idis required, tenant-scoped, and validates the Vendors module is entitled- Catalog registration +
module_dependenciesrow onvendorsviaDefaultModuleRegistrarmigrations (migrate-only); permissions + default role map additive migrations purchase_orders_number_prefixadded toUpdateTenantSettingsRequestvalidation (was previously stripped, causingupdateManyto receivenull)- Pest:
tests/Feature/Tenant/PurchaseOrder/PurchaseOrderTest.php,tests/Feature/Central/Module/PurchaseOrdersModuleDependencyTest.php
Frontend
src/pages/purchase-orders/— list page (KPIs: total, mine, draft, sent, partially received, received; status/vendor/assignee filters, DataTable), create/edit form dialog (required vendorSearchableSelect, currency, order date, expected date, notes, line items editor with live subtotal/tax/total preview), detail sheet (overview/lines/notes/timeline tabs; send/mark partially received/mark received/cancel/assign/notes/edit (draft only))- Added to the existing tenant sidebar Purchasing group, after Vendors
purchaseOrderService,QUERY_KEYS.purchaseOrders,PERMISSIONS.purchaseOrders- Notification registry:
purchase-order.assigned→/purchase-orders?purchase_order={id} - Playwright:
e2e/tests/purchase-orders/purchase-orders.workflow.spec.ts(npm run test:e2e:purchase-orders) — enables bothvendorsandpurchase-ordersmodules, creates a vendor, creates a purchase order, verifies status transitions (draft → sent → partially received → received), checks the timeline, then deletes it
Docs
- purchase-orders-overview.md / purchase-orders.md (+ developer / production)
- api/tenant-v1-purchase-orders.md
- Module Dependencies updated — Purchase Orders → Vendors marked shipped
- Product Roadmap — Purchase Orders marked shipped in Phase 4; Expenses remains Planned
Deferred
- Convert a purchase order to an Expense (Phase 4 Milestone 3), Inventory stock posting on receipt, per-line partial receiving, purchase order PDFs / e-mail delivery to vendors, dashboard widgets for Purchase Orders
Vendors module (2026-08-01)
Architecture
- First Phase 4 (Purchasing) module and Milestone 1 of that phase — a workspace directory of suppliers (backend model
Vendor, first-class entity, no relationship to Contacts unlike Companies). Flat Laravel,module:vendors+ Spatie RBAC, mirrors the Companies notes/timeline/assignee-scope pattern. - Introduces a new Marketplace category:
purchasing(Purchasing),category_sort_order=40. Free Marketplace opt-in —is_default_included=false/is_billable=false,sort_order=10. - No
industry,source,source_meta, orcontactsrelationship (unlike Companies). Addstax_id,payment_terms,currency(max 3 chars), and astatusenum (active/inactive, defaultactive).
Backend
- Tables:
vendors,vendor_notes,vendor_activities - Enums:
VendorStatusEnum(active,inactive),VendorActivityTypeEnum(Created,Updated,Assigned,NoteAdded,Deleted,Restored) - Permissions:
vendors.view|create|update|delete|restore|force.delete|assign - Model + factories, controller, form requests, API resources, policy, service (search includes
tax_id; stats:total_vendors,my_vendors,unassigned,active,inactive,scope), events,AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration (new
purchasingcategory +vendorsmodule) viaDefaultModuleRegistrarmigration (migrate-only); permissions + default role map additive migrations - Pest:
tests/Feature/Tenant/Vendor/VendorTest.php(mirrorsCompanyTest, includes module gate coverage)
Frontend
src/pages/vendors/— list page (KPIs: total, my vendors, unassigned, active, inactive; status + assignee filters, DataTable), create/edit form dialog (name, email, phone, website, address, tax ID, payment terms, currency, status, assignee), detail sheet (overview/notes/activity tabs)- New tenant sidebar Purchasing group (after Billing), with Vendors (Truck icon)
vendorService,QUERY_KEYS.vendors,PERMISSIONS.vendors- Notification registry:
vendor.assigned→/vendors?vendor={id} - Playwright:
e2e/tests/vendors/vendors.workflow.spec.ts(npm run test:e2e:vendors) — enables thevendorsmodule, creates a vendor, verifies KPI cards, notes, and activity timeline, then deletes it
Docs
- vendors-overview.md / vendors.md (+ developer / production)
- api/tenant-v1-vendors.md
- Module Dependencies updated — Purchase Orders → Vendors (required, design) and Expenses → Vendors (optional, design) documented ahead of those modules shipping
- Product Roadmap — Vendors marked shipped in Phase 4, Purchase Orders and Expenses expanded with designed MVP capability bullets
Deferred
- Purchase Orders (Phase 4 Milestone 2), Expenses (Phase 4 Milestone 3), vendor scorecards/performance tracking, vendor portal, vendor import/export, dashboard widgets, communication template placeholders
Phase 3 Billing production hardening (2026-08-01)
Security / ledger integrity
- Payment
post()now requires each allocated invoice to be active Sent/Partial, rejects allocations abovebalance_due, and rejects currency mismatches; soft-deleted invoices cannot be paid. - Invoice void limited to Draft/Sent with zero
amount_paid/amount_credited(Partial→Void removed). - Credit note
apply()requires Sent/Partial invoice and credit total ≤balance_due. - Unique
(tenant_id, number)indexes on customer invoices/payments/credit notes and estimates, with create-time duplicate-number retry.
UX / CI
- Payments list supports
?invoice=deep-link (mirrors Credit Notes); invoice detail links through. - Docs Quality Gate: raised Vite
chunkSizeWarningLimitand excluded Rollup chunk-size advisories from WARNING grep. - Pest expectations updated for
todosas a third default-included module (unblocks Backend CI).
Estimates module (2026-07-31)
Architecture
- Fourth and final Phase 3 (Billing) module — pre-sale cost estimates a tenant issues to its own customers (backend model
Estimate, noCustomerprefix — distinct naming from Invoices/Payments/Credit Notes since there's no equivalent Central concept to collide with). Flat Laravel,module:estimates+ Spatie RBAC, mirrors the Credit Notes/Invoices notes/timeline/assignee-scope/lines pattern, plus a Quotations-shaped status machine. - Hard dependency on Invoices — same pattern as Payments/Credit Notes; Marketplace blocks installing Estimates until a workspace already has Invoices entitled (converting an estimate always creates a
CustomerInvoice). Free Marketplace opt-in — catalog categorybilling,is_default_included=false/is_billable=false,sort_order=40(after Credit Notes'30). - Estimates introduces a convert-to-invoice action (
POST /estimates/{id}/convert) — the first Phase 3 action that creates a row in a different module's table. Adds thecustomer_invoices.estimate_idforeign key (the nullable column already existed from the Invoices migration; this module adds the constraint onceestimatesexists). - Optional soft links to Contacts, Companies, Opportunities, and Quotations — each validated only when that module is entitled, no hard dependency rows for these.
Backend
- Tables:
estimates,estimate_lines,estimate_notes,estimate_activities - Auto-numbered (
EST-00001, prefix fromestimates_number_prefixtenant setting) - Status workflow
draft → sent → accepted|rejected|expired(EstimateStatusEnum, identical shape toQuotationStatusEnum); disallowed transitions return a 422 validation error convertToInvoice()copies the estimate's lines into a new draftCustomerInvoiceviaCustomerInvoiceService::create(), links it back viaestimate_id, marks the estimateacceptedif not already, and rejects a second conversion attempt- Permissions:
estimates.view|create|update|delete|restore|force.delete|assign|send|accept|convert - Model + factories, controller, form requests, API resources, policy, service, events (incl.
EstimateConverted),AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration +
module_dependenciesrow viaDefaultModuleRegistrarmigrations (migrate-only); permissions + default role map additive migrations - Pest:
tests/Feature/Tenant/Estimate/EstimateTest.php,tests/Feature/Central/Module/EstimatesModuleDependencyTest.php - Fixed a pre-existing MySQL "identifier name too long" bug (index names exceeding the 64-character limit) in the
customer_invoice_activities,customer_payment_activities, andcustomer_credit_note_activitiesmigrations, plus thecustomer_payment_allocationsunique constraint and thecustomer_credit_note_notes/customer_credit_note_linesindexes — discovered while migrating this batch on real MySQL; all now use explicit shortened index names
Frontend
src/pages/estimates/— list page (KPIs incl. Accepted value, Converted badge, filters, DataTable), create/edit form dialog (contact/company pickers, opportunity picker with quotation picker filtered by the selected opportunity, line items editor), detail sheet (overview/lines/notes/timeline tabs; send/accept/reject/convert to invoice/assign/notes)- Added to the existing tenant sidebar Billing group, after Credit Notes
estimateService,QUERY_KEYS.estimates,PERMISSIONS.estimates- Notification registry:
estimate.assigned→/estimates?estimate={id} - Playwright:
e2e/tests/estimates/estimates.workflow.spec.ts(npm run test:e2e:estimates) — enables bothinvoicesandestimatesmodules, creates an estimate, sends and accepts it, converts it to an invoice, confirms the conversion dialog, and verifies the resulting invoice + estimate timeline via the API
Docs
- estimates-overview.md / estimates.md (+ developer / production)
- api/tenant-v1-estimates.md
- Module Dependencies updated — Estimates → Invoices marked shipped
- Product Roadmap — Estimates marked shipped in Phase 3, completing Phase 3 — Billing (Invoices, Payments, Credit Notes, Estimates all shipped)
Deferred
- Estimate PDF export / e-mail delivery, reversing a conversion (one-way, one-time), multi-currency conversion, approval workflow beyond the status enum
Credit Notes module (2026-07-31)
Architecture
- Third Phase 3 (Billing) module — credit notes a tenant issues against its own customer invoices (backend model
CustomerCreditNote, distinct from Central's own platform-billingcredit_notesledger). Flat Laravel,module:credit-notes+ Spatie RBAC, mirrors the Payments/Invoices notes/timeline/assignee-scope pattern. - Hard dependency on Invoices — same pattern as Payments; Marketplace blocks installing Credit Notes until a workspace already has Invoices entitled. Free Marketplace opt-in — catalog category
billing,is_default_included=false/is_billable=false,sort_order=30(after Payments'20). - Credit Notes introduces a first-class lines child table (
customer_credit_note_lines), like Invoices —subtotal/tax_total/totalcomputed server-side from lines. - Issuing a draft credit note locks its content; applying an issued credit note adds its
totalto the linked invoice'samount_creditedand recalculatesbalance_dueviaCustomerInvoice::recalculateBalanceFromAmounts()— unlike Payments, this does not drive the invoicestatus. Voiding is only reachable fromdraft/issued(before any invoice balance has been touched).
Backend
- Tables:
customer_credit_notes,customer_credit_note_lines,customer_credit_note_notes,customer_credit_note_activities - Auto-numbered (
CN-00001, prefix fromcredit_notes_number_prefixtenant setting) - Status workflow
draft → issued → applied, withvoidfromdraft/issued(CustomerCreditNoteStatusEnum); disallowed transitions return a 422 validation error - Permissions:
credit-notes.view|create|update|delete|restore|force.delete|assign|issue|apply|void - Model + factories, controller, form requests, API resources, policy, service, events,
AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration +
module_dependenciesrow viaDefaultModuleRegistrarmigrations (migrate-only); permissions + default role map additive migrations - Pest:
tests/Feature/Tenant/CustomerCreditNote/CustomerCreditNoteTest.php,tests/Feature/Central/Module/CreditNotesModuleDependencyTest.php
Frontend
src/pages/credit-notes/— list page (KPIs incl. Applied total, filters, DataTable), create/edit form dialog (invoice picker driving default currency/contact/company, line items editor), detail sheet (overview/lines/notes/timeline; issue/apply/void/assign/notes)- Added to the existing tenant sidebar Billing group, after Payments — kept separate from Central Billing nav
customerCreditNoteService,QUERY_KEYS.customerCreditNotes,PERMISSIONS.customerCreditNotes- Invoice detail sheet: new "Credit notes" link to
/credit-notes?invoice={id}when Credit Notes is entitled +credit-notes.viewis granted - Notification registry:
customer_credit_note.assigned→/credit-notes?credit-note={id} - Playwright:
e2e/tests/credit-notes/credit-notes.workflow.spec.ts(npm run test:e2e:credit-notes) — enables bothinvoicesandcredit-notesmodules, creates and sends an invoice, creates a credit note against it, issues and applies it, and verifies the invoice'samount_credited/balance_duevia the API
Docs
- credit-notes-overview.md / credit-notes.md (+ developer / production)
- api/tenant-v1-credit-notes.md
- Module Dependencies updated — Credit Notes → Invoices marked shipped
- Product Roadmap — Credit Notes marked shipped in Phase 3; Estimates remains Planned
- Invoices/Payments docs updated to reflect that
amount_credited/balance_dueare now driven by Credit Notes rather than "still planned"
Deferred
- Estimates, refunding an already-applied credit note, credit note PDF export / e-mail delivery, standalone credit notes not tied to an invoice, multi-currency conversion
Payments module (2026-07-31)
Architecture
- Second Phase 3 (Billing) module — customer payments a tenant receives from its own customers (backend model
CustomerPayment, distinct from Central platform-billing Payments ledger). Flat Laravel,module:payments+ Spatie RBAC, mirrors the Invoices notes/timeline/assignee-scope pattern. - Hard dependency on Invoices — the first Phase 3 module to declare a required
module_dependenciesrow; Marketplace blocks installing Payments until a workspace already has Invoices entitled. Free Marketplace opt-in — catalog categorybilling,is_default_included=false/is_billable=false,sort_order=20(after Invoices'10). - Payments introduces a first-class allocations child table (
customer_payment_allocations) linking a payment to one or more invoices; allocations are stored on draft payments but only applied to invoice balances once posted. - Posting/voiding a payment drives the previously read-only Invoice balance fields:
amount_paid/balance_dueand thesent → partial|paidstatus transition (viaCustomerInvoice::recalculateBalanceFromAmounts()), closing the gap called out in the Invoices changelog entry above.
Backend
- Tables:
customer_payments,customer_payment_allocations,customer_payment_notes,customer_payment_activities - Auto-numbered (
PAY-00001, prefix frompayments_number_prefixtenant setting) - Status workflow
draft → posted → void(CustomerPaymentStatusEnum); disallowed transitions return a 422 validation error - Permissions:
payments.view|create|update|delete|restore|force.delete|assign|post|void - Model + factories, controller, form requests, API resources, policy, service, events,
AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration +
module_dependenciesrow viaDefaultModuleRegistrarmigrations (migrate-only); permissions + default role map additive migrations - Pest:
tests/Feature/Tenant/CustomerPayment/CustomerPaymentTest.php,tests/Feature/Central/Module/PaymentsModuleDependencyTest.php
Frontend
src/pages/payments/— list page (KPIs incl. Posted total, filters, DataTable), create/edit form dialog (amount/method/paid-at/reference, contact/company/assignee pickers, allocations editor against invoices), detail sheet (overview/allocations/notes/timeline tabs; post/void/assign/notes)- Added to the existing tenant sidebar Billing group, after Invoices — kept separate from Central Billing nav
customerPaymentService,QUERY_KEYS.customerPayments,PERMISSIONS.customerPayments— named distinctly from the pre-existing CentralpaymentService/PERMISSIONS.payments(platform subscription billing)- Invoice detail sheet: new "Related payments" link to
/paymentswhen Payments is entitled +payments.viewis granted - Notification registry:
customer_payment.assigned→/payments?payment={id} - Playwright:
e2e/tests/payments/payments.workflow.spec.ts(npm run test:e2e:payments) — enables bothinvoicesandpaymentsmodules, creates an invoice, records a payment with an allocation, posts it, verifies the invoice balance/status updates, voids it, and verifies the reversal
Docs
- payments-overview.md / payments.md (+ developer / production)
- api/tenant-v1-payments.md
- Module Dependencies updated — Payments → Invoices marked shipped (was backend-only)
- Product Roadmap — Payments marked shipped in Phase 3; Credit Notes/Estimates remain Planned
- Invoices docs updated to reflect that
amount_paid/balance_dueare now driven by Payments rather than "reserved for a future module"
Deferred
- Credit Notes (
amount_credited), Estimates, partial refunds of a posted payment, online payment-gateway capture, multi-currency conversion
Invoices module (2026-07-31)
Architecture
- First Phase 3 (Billing) module — customer invoices a tenant sends to its own customers (backend model
CustomerInvoice, distinct from Central platform-billingInvoice). Flat Laravel,module:invoices+ Spatie RBAC, mirrors the Quotations notes/timeline/assignee-scope pattern. - No hard dependency — unlike Quotations/Contracts (which require Opportunities), Invoices has no
module_dependenciesrow and installs standalone. Free Marketplace opt-in — catalog categorybilling(category_sort_order=30),is_default_included=false/is_billable=false,sort_order=10. - Soft optional links:
contact_id/company_idonly surfaced/validated when Contacts/Companies is entitled;quotation_idis a plain tenant-scoped existence check (not gated by aLinkableQuotation-style entitlement rule). - Balance fields (
amount_paid,amount_credited,balance_due) are reserved for the still-planned Payments/Credit Notes modules — read-only via this API today.
Backend
- Tables:
customer_invoices,customer_invoice_lines,customer_invoice_notes,customer_invoice_activities - Line items (
description,quantity,unit_price,tax_rate) fully replaced on create/update;subtotal/tax_total/total/balance_duecomputed server-side - Auto-numbered (
INV-00001, prefix frominvoices_number_prefixtenant setting) - Status workflow
draft → sent → partial|paid → void(CustomerInvoiceStatusEnum); disallowed transitions return a 422 validation error - Permissions:
invoices.view|create|update|delete|restore|force.delete|assign|send|void - Model + factories, controller, form requests, API resources, policy, service, events,
AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration via
DefaultModuleRegistrarmigration (migrate-only); permissions + default role map additive migrations - Pest:
tests/Feature/Tenant/CustomerInvoice/CustomerInvoiceTest.php
Frontend
src/pages/invoices/— list page (KPIs incl. Overdue, filters, DataTable), create/edit form dialog (line items editor, contact/company/assignee pickers), detail sheet (overview/lines/notes/timeline tabs; send/void/assign/notes — no accept)- New tenant sidebar group Billing (after Sales) — kept separate from Central Billing nav
customerInvoiceService,QUERY_KEYS.customerInvoices,PERMISSIONS.customerInvoices— named distinctly from the pre-existing CentralinvoiceService/PERMISSIONS.invoices(platform subscription billing)- Notification registry:
customer_invoice.assigned→/invoices?invoice={id} - Playwright:
e2e/tests/invoices/invoices.workflow.spec.ts(npm run test:e2e:invoices) — enable module, create, search, send, void, timeline
Docs
- invoices-overview.md / invoices.md (+ developer / production)
- api/tenant-v1-invoices.md
- Module Dependencies updated (Invoices → Contacts/Companies/Quotations optional; Payments → Invoices required, backend-only for now)
- Product Roadmap — Invoices marked shipped in Phase 3; Payments/Credit Notes/Estimates remain Planned
Deferred
- Payments (posting
amount_paid, autopartial/paidtransitions), Credit Notes (amount_credited), Estimates, invoice PDF export / e-mail delivery, multi-currency conversion
Lead import first note (2026-07-31)
Bulk lead import supports an optional Note column. When mapped and non-empty, import creates a first note on new leads (and appends a note when updating duplicates). Empty note cells are skipped. Template, wizard field list, Pest, and Playwright import coverage updated.
Lead import numeric phone cast (2026-07-31)
Bulk lead import no longer fails with “phone must be a string” when Excel/CSV parsers return phone cells as numbers. LeadImportMapper casts string fields before validation.
Overdue filter for Tasks & ToDos (2026-07-31)
List and board APIs accept overdue=true for open items with due_at in the past. Tasks and ToDos UIs expose an Overdue checkbox; Tasks also toggles the filter from the Overdue KPI card.
ToDos module (2026-07-31)
Architecture
- New personal checklist module (
todos), separate from Tasks. Default-included, non-billable CRM catalog row (sort_order21, iconlist-todo). - Creator-scoped visibility via
ScopesToCreator— users see only their own to-dos; workspace owner (superadmin) can view all. - Creator-only update/delete (owner may view others’ items but cannot mutate them).
Backend
- Table
todos; model, policy, service, board + CRUD API undermodule:todos+todos.*. - Soft-delete route binding (
withTrashed) and whitelist for listsort/direction. - Local demo:
TodosSeeder+ dataset counts inconfig/local-demo.php. - Permissions:
view·create·update·delete(admin/manager/staff). - Pest:
tests/Feature/Tenant/Todo/TodoTest.php(CRUD, creator/admin scope, owner view-only, sort safety, soft-delete show, module gate, isolation).
Frontend
- Board (default) + list; form dialog + detail sheet; nav gated by
module:todos+todos.view. - Board drag disabled for non-creators (owner view-only cards).
- Playwright:
npm run test:e2e:todos(workflow + multi-user visibility).
Docs / Website
- User / developer / production / API guides; database + roadmap + this note.
- Marketing
MODULESentry and included-module copy updated for ToDos (including FAQ cancel copy).
Marketing catalog currency uses modules.currency (2026-07-31)
Public GET /api/central/v1/public/modules now labels paid prices with each module’s catalog currency (modules.currency, typically USD as in Central Modules). It no longer uses system_settings.currency (workspace default), which can be PKR/EUR/etc. and mislabeled catalog USD amounts.
Marketing site public catalog + stats API (2026-07-31)
- Central public endpoints for the EloSync website homepage:
GET /api/central/v1/public/stats— live Active Workspaces, Modules Installed, Catalog Modules (+ uptime / platform currency)GET /api/central/v1/public/modules— marketplace cards with Available / In Progress / Planned and Included / Free / Paid tags; paid prices always use the central application currency
- Catalog modules gain
availability(available|in_progress|planned) alongside existing commercialstatus/is_billableflags - EloSync-Website Trust + Module Marketplace sections consume these APIs (
NEXT_PUBLIC_API_URL); CORS supportsMARKETING_URL - Pest:
tests/Feature/Central/Public/PublicPlatformTest.php
Rebrand SaleOS → EloSync (2026-07-31)
Product name, docs site, and operator examples are now EloSync (docs.elosync.com, *.elosync.com).
Breaking: Custom lead webhook HMAC / API-key headers are renamed — X-SaleOS-Signature / X-SaleOS-Timestamp / X-SaleOS-Key → X-EloSync-Signature / X-EloSync-Timestamp / X-EloSync-Key. Update integrators (Zapier, Make, form plugins). Branded-domain DNS defaults are _elosync-verification / elosync-verify-… (override with BRANDED_TXT_PREFIX if needed).
Marketplace badges, dependents copy, catalog versioning (2026-07-31)
- Tenant Marketplace cards/detail: badges show Installed / Available / Billable / Pending (no longer “Included” for free opt-in modules); category tag above module name; long titles no longer overflow the card.
- Detail sheet separates Dependencies (modules this one needs) from Dependents (installed modules that block remove).
- List API includes
already_installed,purchase_pending, andversionper row. DefaultModuleRegistraraccepts create-timeversionandbumpVersion($slug, $version)for idempotent catalog bumps; docs require bumpingmodules.versionwhen a module is meaningfully updated.
Marketplace display currency conversion (2026-07-31)
Tenant Marketplace list/detail now convert catalog module prices from the catalog currency (typically USD) into the workspace currency for display only. Checkout still charges the mapped Stripe/gateway Price in the catalog currency. FX rates come from a cached third-party mid-market feed (CurrencyConversionService / CURRENCY_FX_*). When rates are unavailable, the UI falls back to catalog currency amounts.
Sales production readiness (2026-07-31)
LinkableQuotationkeeps SoftDeletes (only dropsTenantScope) so soft-deleted quotations cannot be linked.QuotationPolicy::send/acceptmirror view/update assignee scoping.- Contracts: content updates are draft-only (
Contract::isEditable()); assignment uses dedicatedassign()(mirrors Quotations). - SPA: Quotation and Contract list/detail Edit actions are draft-only.
- Pest: staff assignee scope for Quotations/Contracts; soft-deleted quotation link rejected; non-draft contract edit rejected; send/accept assignee isolation.
- Docs: API index, entitlements catalog, database schema, tenant free-install list, and Playwright Sales suites synced.
Sales audit remediations (2026-07-31)
- Quotations: content/
linesupdates are draft-only (Quotation::isEditable()); assignment stays available viaPOST …/assign. - Quotations:
POST …/statusrequiresquotations.sendforsentandquotations.acceptforaccepted(other transitions still usequotations.update). - Contracts:
LinkableQuotationrequires the quotation’s opportunity to match the contract’s opportunity. - SPA: Edit quotation action is draft-only; Playwright RequireAccess coverage extended to Sales routes.
- Docs: API + developer guides updated for the above.
Quotations & Contracts modules (2026-07-31)
Architecture
- Two lean Sales modules on top of Opportunities, mirroring its assignee-scope + notes + timeline patterns. Flat Laravel,
module:quotations/module:contracts+ Spatie RBAC. Free Marketplace opt-in — catalog categorysales,is_default_included=false/is_billable=false;quotationssort_order=50,contractssort_order=60. - Hard dependency: both
quotationsandcontractsrequire Opportunities — Marketplace install blocks until Opportunities is entitled (module_dependenciesmigration, mirrors Meetings → Calendar). - Soft optional dependency: Contracts may link
quotation_idonly when Quotations is also entitled — validated byLinkableQuotation(mirrorsLinkableCompanyForOpportunity).
Backend — Quotations
- Tables:
quotations,quotation_lines,quotation_notes,quotation_activities - Line items (
description,quantity,unit_price,tax_rate) fully replaced on create/update;subtotal/tax_total/totalcomputed server-side - Status workflow
draft → sent → accepted|rejected|expired(QuotationStatusEnum); disallowed transitions — including re-entering the same status — return a 422 validation error - Permissions:
quotations.view|create|update|delete|restore|force.delete|assign|send|accept - Pest:
tests/Feature/Tenant/Quotation/QuotationTest.php,tests/Feature/Central/Module/QuotationsModuleDependencyTest.php
Backend — Contracts
- Tables:
contracts,contract_notes,contract_activities(no line items — a contract is a single agreement record) - Status workflow
draft → active → expired|terminated(ContractStatusEnum); same same-status/invalid-transition guard as Quotations - Permissions:
contracts.view|create|update|delete|restore|force.delete|assign - Pest:
tests/Feature/Tenant/Contract/ContractTest.php,tests/Feature/Central/Module/ContractsModuleDependencyTest.php
Both modules
- Model + factory, controller, form requests, API resources, policy, service, events,
AppServiceProvider-registered event subscriber (audit + assignment notification) - Catalog registration via
DefaultModuleRegistrarmigration (migrate-only); permissions + default role map additive migrations
Docs
- quotations-overview.md / quotations.md (+ developer / production)
- contracts-overview.md / contracts.md (+ developer / production)
- api/tenant-v1-quotations.md, api/tenant-v1-contracts.md
- Module Dependencies updated (Quotations/Contracts → Opportunities now shipped; Contracts → Quotations optional)
Deferred
- Quotation PDF export / e-signature, contract renewal reminders, multi-currency conversion, Opportunity → Quotation → Contract conversion wizard
Opportunities module (2026-07-30)
Architecture
- Sales deals module with pipeline stages + Kanban board inside Opportunities (Sales Pipeline is not a separate Marketplace SKU). Mirrors Leads board patterns and Activities soft related links. Flat Laravel,
module:opportunities+ Spatie RBAC. Free Marketplace opt-in — catalog categorysales,is_default_included=false/is_billable=false/sort_order=40; tenants enable it manually (only Leads + Tasks auto-install).
Backend
- Tables:
opportunity_stages,opportunities,opportunity_notes,opportunity_activities - Seeded stages: Prospecting → Qualification → Proposal → Negotiation → Won / Lost
- Soft optional FKs:
contact_id/company_id/lead_id(entitlement-validated when set) - Board / stats / stage change / assign / notes / restore / force delete
- Permissions:
opportunities.view|create|update|delete|restore|force.delete|assign - Catalog registration via
DefaultModuleRegistrarmigration (migrate-only) - Pest:
tests/Feature/Tenant/Opportunity/OpportunityTest.php
Frontend
- SPA mirrors Leads (board default + table, form dialog, detail drawer) — ship with Opportunities nav gated by module + permission
Docs
- opportunities-overview.md (+ user / developer / production)
- api/tenant-v1-opportunities.md
Deferred
- Separate Sales Pipeline SKU, custom stage admin UI, Lead → Opportunity conversion wizard, realtime board sync, export / import
Quotations & Contracts SPA (2026-07-31)
Tenant Application pages for Quotations and Contracts (list, form with line items for quotes, detail sheets with status actions), Sales nav group, Playwright test:e2e:quotations / test:e2e:contracts, and notification deep-links for assignment.
Marketing site Forge CI (2026-07-30)
EloSync marketing repo (EloSync-Website) publishes a static Next.js export to build-artifacts on each push to main (same pattern as SPA/Docs). Forge deploys that branch with activate-only script — no Node on the server. See Laravel Forge §4 Marketing.
Marketing catalog pricing sync (2026-07-30)
EloSync marketing site and docs now mirror Central catalog commercial flags: Included free (Leads, Tasks), Free to install (other CRM modules), Paid Branded at $29/mo · $290/yr. Entitlements catalog table corrected (opt-in CRM modules are not default-included).
Tenant Marketplace remove modules (2026-07-30)
Workspace owners (and anyone with marketplace.purchase) can remove opt-in modules from Marketplace — Install / Subscribe to add, Remove / Cancel subscription to drop access. Core default-included modules (Leads, Tasks) stay non-removable; hard dependents must be removed first (e.g. Meetings before Calendar). Re-installing a previously cancelled module reactivates the same subscription row.
Fix (same day): Cancel eligibility follows catalog is_default_included (not historical source=included). Data migration marks optional CRM modules opt-in and rewrites their included subscriptions to purchased so Remove appears for workspaces that had them pre-opt-in.
- API:
POST /api/tenant/v1/marketplace/modules/{module}/cancel; detail addscan_cancel,blocking_dependents,subscription_source - UI: Marketplace module detail sheet Remove / Cancel subscription + confirmation
- Docs: tenant-v1-marketplace, entitlements
Default module policy (2026-07-30)
New workspaces auto-install Leads and Tasks only. Contacts, Companies, Activities, Opportunities, Calendar, Meetings, and Communication Templates stay free (is_billable=false) but opt-in (is_default_included=false) via Marketplace. Existing workspace subscriptions are not removed.
Activities module (2026-07-30)
Architecture
- CRM engagements module; mirrors Contacts/Companies (flat Laravel,
module:activities+ Spatie RBAC). Free Marketplace opt-in — catalogis_default_included=false/is_billable=false/sort_order=28; tenants enable it manually (only Leads + Tasks auto-install).
Backend
- Tables:
activities,activity_notes,activity_activities - Types:
call|email|note|follow_up|other; complete viaPOST /activities/{id}/complete - Related FKs:
contact_id/company_id/lead_id(at least one required; soft module entitlement) - Mirrors
crm_activity_logged/crm_activity_completedonto related Contact/Company/Lead timelines - Permissions:
activities.view|create|update|delete|restore|force.delete|assign|complete(staff defaults includeview+complete, mirroring Tasks) - Pest:
tests/Feature/Tenant/Activity/ActivityTest.php(16 tests)
Frontend
- Activities list / form dialog / detail drawer (Overview, Notes, Activity tabs); nav after Meetings
- Tenant dashboard: Recent Activities widget + Log Activity quick action
- Playwright:
npm run test:e2e:activities(e2e/tests/activities/)
Docs
- activities-overview.md (+ user / developer / production)
- api/tenant-v1-activities.md
Deferred
- Calendar projection, Meetings types, unified system-timeline aggregator, Communication Template placeholders
Companies module (2026-07-30)
Architecture
- CRM organizations module; mirrors Contacts/Leads/Tasks (flat Laravel,
module:companies+ Spatie RBAC). Free Marketplace opt-in — catalogis_default_included=false/is_billable=false/sort_order=12; tenants enable it manually (only Leads + Tasks auto-install).
Backend
- Tables:
companies,company_notes,company_activities;contacts.company_idFK CompanyService, events/subscriber (audit + assignment mail), tenant API routes- Permissions:
companies.view|create|update|delete|restore|force.delete|assign - Contact writes sync legacy
companystring from linked Company name whencompany_idis set; resources exposelinked_company - Communication Templates placeholder provider for Companies
- Pest:
tests/Feature/Tenant/Company/CompanyTest.php
Frontend
- Companies list / form dialog / detail drawer (Overview, Notes, Activity tabs); nav gated by module + permission, positioned between Leads and Contacts
- Tenant dashboard: Recent Companies widget + Create Company quick action (gated by
module:companies+companies.view/companies.create) - Contact form company picker when Companies is entitled; list/detail prefer linked company name over legacy string
- Playwright:
npm run test:e2e:companies(e2e/tests/companies/)
Docs
- companies-overview.md (+ user / developer / production)
- api/tenant-v1-companies.md
Deferred
- Lead convert-to-Company, legacy company-string backfill job, Meta invent Companies
Contacts module (2026-07-30)
Architecture
- Third product module; mirrors Leads/Tasks (flat Laravel,
module:contacts+ Spatie RBAC). Free Marketplace opt-in — catalogis_default_included=false/is_billable=false; tenants enable it manually (only Leads + Tasks auto-install).
Backend
- Tables:
contacts,contact_notes,contact_activities;leads.contact_idFK ContactService, events/subscriber (audit + assignment mail), tenant API routes- Permissions:
contacts.view|create|update|delete|restore|force.delete|assign LeadService::convert()creates/links a real Contact whencontactsis entitled (conversion_meta.stub = false); otherwise conversion stays the earlier status-only placeholder- Convert hardening: transactional + row lock; requires
contacts.create; preserves lead assignee;LeadPolicy::convertmatches assignee scope; stub converts can complete once Contacts is installed; assignee eligibility mirrors Leads - Communication Templates placeholder provider for Contacts
- Pest:
tests/Feature/Tenant/Contact/ContactTest.php(+ convert authz/backfill cases in LeadTest)
Frontend
- Contacts list / form dialog / detail drawer (Overview, Notes, Activity tabs); nav gated by module + permission, positioned between Leads and Tasks
- Tenant dashboard: Recent Contacts widget + Create Contact quick action (gated by
module:contacts+contacts.view/contacts.create); dashboard cache invalidated after contact save / lead convert - Lead detail drawer shows a View contact link after a lead converts to a real contact
- Playwright:
npm run test:e2e:contacts(e2e/tests/contacts/)
Docs
- contacts-overview.md (+ user / developer / production)
- api/tenant-v1-contacts.md
Meeting completion (2026-07-29)
Meetings can be marked completed (detail action) or auto-completed after ends_at via meetings:auto-complete (every 5 minutes). Status is now scheduled | completed | cancelled. Completing cancels pending reminders, keeps the Calendar historical event as scheduled, and does not delete remote Zoom/Google links. List filter Past is replaced by Completed. API: POST /meetings/{id}/complete (meetings.update). Migration adds completed_at and backfills already-ended scheduled meetings.
Lead & task restore / force delete (2026-07-28)
Leads and Tasks support trash filtering plus Restore and Delete permanently. Soft delete is unchanged. Restore is granted to workspace admin (and owner); force delete is owner-only by default (leads.force.delete / tasks.force.delete) and can be assigned on Roles. API: POST …/restore, DELETE …/force.
Latest note & follow-up previews on Leads/Tasks lists (2026-07-28)
Lead and task table and board views now show the latest note (and, for leads, the next pending follow-up). Hover the truncated preview to read the full note or follow-up details. List/board API payloads include latest_note and (leads) next_follow_up.
Meetings create form crash after visiting Settings (2026-07-28)
Fixed an intermittent SPA “Something went wrong” error when opening New Meeting after Settings (or any page that filled the shared tenant-settings React Query cache). The meeting form unwrapped that cache to a bare array and called .find, while Settings stored the API envelope — so .find ran on an object. Meetings now uses a shared useTenantSettingsQuery helper with the same envelope shape (and a defensive normalizer).
Meeting list vs edit timezone mismatch (2026-07-28)
Meetings (and Calendar list columns) now format start/end times with an explicit timezone: workspace Settings when loaded, otherwise the meeting/event timezone saved on the record. This fixes the case where the edit form showed the correct Asia/Karachi wall clock (e.g. 4:00 PM) while the list still showed the UTC clock (e.g. 11:00 AM) after a Central settings fallback. Tenant settings bootstrap also refuses to adopt Central’s timezone for an authenticated workspace shell.
Workspace timezone meeting/calendar times (2026-07-28)
Scheduled datetimes (meetings, calendar, task due dates, lead follow-ups) no longer shift when the workspace timezone is not UTC. Eloquent stores those fields as UTC instants (UtcDateTime cast); the SPA continues to display and edit them in the workspace timezone from Settings. Meeting create/edit uses the workspace timezone (read-only on the form) instead of a separate picker that did not drive conversion. Meetings/Calendar list UIs now re-render when the workspace timezone loads or changes so times do not stick on UTC after Settings bootstrap.
Web Push Profile false "not supported" (2026-07-27)
Profile Desktop notifications no longer sticks on “This browser does not support Web Push” when notification permission is already granted but no service worker is registered. Status lookup uses getRegistration() instead of hanging on serviceWorker.ready.
Desktop notification prompt — dismiss once forever (2026-07-27)
The post-login “Enable desktop notifications?” dialog no longer reappears on every login after “Not now”. Dismissal is stored in localStorage for that browser; users can still enable later from Profile or the Notification Center.
Meetings host filter crash after visiting Leads/Tasks (2026-07-27)
Fixed an intermittent SPA “Something went wrong” error on Meetings. The Meetings pages unwrapped the shared users React Query cache to a bare array while Leads/Tasks stored the API envelope, so navigating Leads → Meetings called .map on an object. Meetings now uses a shared useWorkspaceUsersQuery helper with the same envelope shape (and a defensive normalizer).
Password show/hide on all secret fields (2026-07-26)
All password and secret inputs in the SPA now use the shared PasswordInput control with an eye toggle (show/hide), including profile, users, tenants, mail settings, payment gateway credentials, and meeting integrations. Auth screens already had this pattern.
Meta App Setup operator guide (2026-07-25)
Docs-only: added Meta App Setup — create Meta Developer App (Marketing API use case), App Domains, Lead Ads permissions, OAuth vs webhook URLs, Central META_LEAD_ADS_* / integrations API, tenant Connect Meta, production gates, and troubleshooting. Linked from Meta Lead Ads, Leads deployment, installation, user overview, Tenant Leads API, and Developer Guide index.
Platform domain auto-generated on signup / Central create (2026-07-25)
Central tenant create/update and public workspace registration no longer accept a domain field. The platform hostname is always {slug}.{PLATFORM_DOMAIN_SUFFIXES} (company name spaces/special characters → hyphens via slug). Custom domains stay Branded self-service (Settings → Domain after purchase). SPA register + Central tenant form hide the domain input; Pest/e2e updated.
Backend .env.example production template (2026-07-25)
Backend .env.example is production-shaped: https://api.example.com, https://app.example.com, reverb.example.com (443/https), Redis cache/queue required with Reverb, S3 disk, secure session cookies, minimal comments. Secrets stay empty placeholders — never commit real keys. Local install docs override to Herd/localhost.
Laravel Forge production deployment guide (2026-07-25)
Docs-only: production-friendly Laravel Forge Deployment covering three Forge sites (API / SPA / Docs), production .env, deploy scripts, scheduler, queue + Reverb daemons, email, deploy order, and troubleshooting. Installation page, Deployment index, Production Runbook, and Frontend build-artifacts docs now point operators at Forge first for go-live.
Installation guide — local configuration plan (2026-07-25)
Docs-only: new Installation & Local Configuration page covering backend (Herd, .env, migrate/seed), frontend Vite/VITE_*, VitePress docs, Reverb + Echo alignment, email (log/SMTP/Settings), queues/scheduler/Web Push, optional Stripe/storage, terminal layout, and a verify checklist — with pointers to production runbooks.
Lead ingest — Custom Webhook + Meta Lead Ads (2026-07-24)
Inbound lead capture through the Lead Source Driver pipeline:
- Shared
NormalizedLeadData→LeadDuplicateService→LeadService(no driver DB writes) - Custom webhooks per tenant: URL + API key / HMAC (
X-EloSync-Timestamp+ signature), editabledefault_source - Meta Lead Ads: OAuth connect, Page picker UI, Page subscribe, shared
/webhooks/leads/meta, queuelead-ingest - Leads UI → Integrations panel; permission
leads.manage_integrations - Production hardening: module entitlement on ingress, body size limits, per-endpoint throttle, Graph timeouts, auth-only
needs_reauth, force-delete page reclaim - Docs: Lead Source Driver Architecture (implemented), Custom Lead Webhook, Meta Lead Ads (shipped)
- Pre-merge hardening:
guzzlehttp/guzzle→ 7.15.1; SPApostcss+react-router→ 8.3.0 (high audit clear); Pest expectations updated formeetings/brandedand platform subdomain domains; Larastan typing cleared (lead ingest + meetings/CRM)
Branded Domain settings UX (2026-07-24)
Settings → Domain uses a guided three-step flow (enter address → copy-friendly DNS cards → check connection) instead of raw DNS admin rows, aimed at non-technical workspace admins.
Branded module — custom domains + notification chrome (2026-07-24)
Billable marketplace module branded (not default-included) for custom domain mapping and white-label email / web push.
- Tenant self-service Settings → Domain: propose hostname (ccTLDs like
myai.com.pk/app.domain.co.uksupported), DNS/IP instructions, verify ownership - Host resolution ignores unverified or non-entitled custom domains (IP pointing alone cannot hijack a workspace)
- Central / registration auto-generate platform subdomains from slug (no client domain field)
- When branded is active: tenant logo / app name in mail chrome and web push icon/title prefix
- Cancel/deactivate branded clears custom-domain verification
- Production hardening: force-delete on remove/expire (unique reclaim), verify fails closed without
BRANDED_SERVER_IPV4/CNAME, CORS allowlist for verified custom Origins, hourly stale-claim purge - Pest + Playwright
test:e2e:branded; docs: user / developer / deployment / API
Daily CRM summary emails (2026-07-23)
At Daily Reminder Time (task_reminder_time, default 09:00 local), each workspace sends a mail-only CRM snapshot in addition to the existing task due digest.
- Personal summary: open leads by stage (excludes Won/Lost), open tasks by status (excludes completed/cancelled), scheduled meetings (excludes cancelled; host/attendee distinct)
- Users flagged Receive all-users daily summary (
receive_all_users_daily_summary) get a user-wise team email (active users only) instead of a personal summary - Durable delivery ledger
daily_summary_deliveries(personal/team) with stale-queued reclaim (45m) and max 5 attempts - Aggregations run once per tenant per tick (SQL meeting distinct counts)
- Settings label Daily Reminder Time; user create/edit checkbox; Playwright flag toggle
- Tests: Pest
DailyCrmSummaryNotificationTest,TenantUserDailySummaryFlagTest - Production report: Daily CRM summary
Tenant session timeout setting (2026-07-23)
Each workspace can set its own session idle/token lifetime, including never timeout.
- Tenant Settings → Security:
session_lifetime_minutes(0= keep users signed in until they sign out) - Falls back to Central
session_lifetime_minuteswhen unset - Sanctum tenant tokens use the workspace value (
expires_atnull when0); SPA idle logout is skipped when0 - Password policy remains Central-only
- Tests: Pest tenant settings + remember-me TTL; Vitest
session-timeout - Docs: tenant settings user/developer guides
Lead assignee exclusion (2026-07-22)
Workspace owners and users flagged Exclude from lead assignment no longer receive leads via import auto-distribute, bulk equal distribute, or manual assignee pickers.
- Backend:
users.exclude_from_lead_auto_assign,User::eligibleLeadAssignees,EligibleLeadAssigneevalidation on assign/create/update/bulk/import - Frontend: checkbox on tenant user create/edit; assignee pickers filter ineligible users (keeps current assignee so they can be cleared)
- Docs: User Guide Leads + Tenant RBAC, developer Leads notes, tenant leads API
Expired session redirects to login (2026-07-22)
Expired or revoked tenant sessions no longer leave the SPA on protected pages toasting Workspace context is required.
- Backend:
InitializeTenancyreturns 401 Unauthenticated when a Bearer token is present but cannot resolve a workspace (pruned/revoked/unknown tokens); anonymous requests without context still return400 workspace_required - Frontend: axios treats
401and any non-skipAuth400 workspace_required(with or without a Bearer — covers the idle token-clear race) as session expiry — clears the token and hard-redirects to login without toasting; concurrent errors while redirecting are also suppressed - Idle timeout hard-redirects immediately, then best-effort logout
- Tests: Pest
TokenExpirationTest; Vitestsrc/api/axios.test.ts - Docs: authentication developer, authentication user guide
Meetings module (2026-07-22)
Workspace Meetings marketplace module (CRM, default-included) with Calendar projection, per-tenant Zoom/Google Meet OAuth credentials + account connect, and one multi-channel reminder.
- Deploy: create migration is production-safe against a leftover pre-redesign
meetingstable — replaces Meetings-related tables only (preserves other production data), purgescalendar_eventswithsource_type=meeting, then creates the current schema - Backend: meetings/attendees/reminders/provider connections; permissions (
view/create/update/delete/view_all/assign_host/manage_integrations); tenant API; migrate-only catalog registration + required Meetings → Calendar dependency - Providers: each workspace stores its own Zoom/Google OAuth client ID/secret (encrypted); connects a workspace account; manual join URL when provider is
none; OAuth token refresh; bounded retry job with explicit tenant init; OAuth one-time nonce - Cancel/delete: remote Zoom/Google delete is best-effort so missing scopes cannot block local cancel; Zoom authorize requests write/read/delete scopes
- Reminders: atomic
pending→sendingclaim; external guest mail dedupe;crm:send-due-notificationsdelivers in-app + web push + email - Frontend: Meetings list/form/detail/integrations; provider options gated until connected; cancel confirm; retry sync; Calendar projections open in Meetings (read-only on Calendar)
- Docs: user/developer/deployment/API; webhooks documented as stub until native provider verify
- Pest:
tests/Feature/Tenant/Meeting/*
Tenant branding bootstrap consistency (2026-07-21)
SPA branding no longer flickers through placeholder product names or stick on Central after soft login.
- Static shell / tab-title fallback standardized on EloSync (
index.html, settings-store defaults,VITE_APP_NAME) - Sidebar and auth layout show empty brand text until public settings are loaded (no
DC SaaSplaceholder) - Settings re-bootstrap after auth settles so tenant
public/settingscan resolve via Bearer token and optionalX-Tenant-Domain - Central fallback on tenant routes only applies on first load — does not overwrite branding after login or settings save
- Unit coverage:
src/store/settings-store.test.ts - Docs: tenant-settings developer, tenant-settings overview
Daily task digest emails (2026-07-21)
Due/overdue task alerts no longer email once per task.
- In-app: one
task.due_today/task.overduedatabase notification per task (unchanged click-through to/tasks?task={id}) - Email: one consolidated daily digest per assignee with task links and a View my tasks CTA
- Workspace setting Daily task reminder time (
task_reminder_time, default09:00) in tenant timezone - Durable delivery ledger
task_digest_deliveries(queued/sent/failed + retry) so cache flush cannot duplicate and queue failures can retry - Scheduler:
crm:send-due-notificationsevery 5 minutes withonOneServer(lead follow-up due emails unchanged)
Calendar module v1 (2026-07-21)
Personal Calendar marketplace module (CRM, default-included).
- Backend:
calendar_events, permissions (view/create/update/delete/view_all), tenant API, migrate-only catalog registration - Frontend: Week (default) + Day time grids with drag-and-drop reschedule, Month + Agenda, create/edit/cancel/delete, upcoming dashboard widget
- Workspace timezone-aware display/edit; overlapping events laid out side-by-side on Week/Day
- Visibility: staff sees own events; Owner/Admin/Manager with
view_allsee all — no calendar assignment - Platform audit via
CalendarEventSubscriber(create/update/cancel/delete), mirroring Leads/Tasks - Docs: user/developer/deployment/API; Pest incl. deploy-migration + audit coverage; Playwright
test:e2e:calendar - Meetings / Zoom / Google Meet later projected onto Calendar (shipped 2026-07-22)
WhatsApp Cloud Integration roadmap (2026-07-20)
Documentation-only: official architectural blueprint for a future WhatsApp Cloud API communication platform.
- Added WhatsApp Cloud Integration under Future Integrations (current
wa.mestate vs Cloud API vision, messaging driver architecture, OAuth, multi-tenant WABA/phone ownership, conversations, templates, security, automation, Meta Lead Ads complementarity) - Cross-linked with Lead Source Driver Architecture and Meta Lead Ads Integration
- Sidebar / Developer Guide index / Product Roadmap updated
- No backend or frontend application code changes
Lead Source Driver Architecture (2026-07-20)
Documentation-only: architectural decision for all future lead ingestion in EloSync.
- Added Lead Source Driver Architecture (
LeadSourceDriverInterfaceresponsibilities, shared pipeline,NormalizedLeadData, driver vs Lead ownership, driver catalog, Open/Closed extensibility) - Updated Meta Lead Ads Integration:
MetaLeadAdsDriveris the first production implementation of the architecture - Sidebar / Developer Guide index: Future Integrations cross-links both pages
- Product Roadmap: Phase 1 Planned entry for Lead Source Driver Architecture
- No backend or frontend application code changes
Meta Lead Ads Integration roadmap (2026-07-20)
Documentation-only: official implementation blueprint for a future Meta Lead Ads → Leads integration.
- Added Meta Lead Ads Integration under Future Integrations (architecture, OAuth, multi-tenant Page ID resolution, field mapping,
LeadDuplicateService+LeadServicegates, security, Meta permissions, error handling, future enhancements) - Sidebar: Developer Guide → Future Integrations → Meta Lead Ads
- Product Roadmap: Phase 1 Planned entry linking to the blueprint
- No backend or frontend application code changes
Fix: persist Open/Click webhook event settings (2026-07-19)
- Fixed settings save treating
mail_webhook_eventslist arrays as{value:…}wrappers (Open/Click were stored as null and UI checkboxes cleared after save) - Default webhook events now include
openedandclicked - Open webhooks update email log status once events are persisted
Email open/click tracking on email logs (2026-07-19)
- Open and Click webhooks now set email log status to
opened/clicked(counts also stored inmeta.opens/meta.clicks) - Email Logs UI shows Opened/Clicked statuses plus open/click counts in the detail panel
- Mail settings no longer wipe Open/Click checkboxes after save/reload
- Postmark setup instructions note that Open tracking and Link tracking must be enabled in Postmark (settings save does not sync the provider UI)
Email webhooks, body logging, and resend (2026-07-19)
- Provider delivery webhooks for Postmark / Mailgun:
POST /webhooks/email/{provider}(Central) and…/{provider}/{tenant}(Tenant custom mail) - Configurable event multiselect (
mail_webhook_events) + signing secret in Central/Tenant Mail settings;meta.mail_webhookon settings GET - Full message body capture (
body_html/body_text) on email logs by default (EMAIL_LOGS_STORE_BODY=true) for audit/proof - One-click resend from email log detail (
email-logs.resend,POST …/email-logs/{uuid}/resend) - Docs: Email webhooks, updated Multi-Provider Email
v1.1.0 — Platform Stabilization (prepared 2026-07-19)
First official coordinated platform tag. Official record: v1.1.0.
Git tag: v1.1.0 (clean SemVer — create only after CI is green on all three repos). No -platform suffix.
Highlights:
- Multi-provider email (SMTP / Postmark / Mailgun) + logs + queue isolation
- Production hardening (migrate-only modules/RBAC, auth≠RBAC, go-live runbooks)
- Notifications + Communication Templates on the frozen foundation
- Larastan level 5 at zero errors; standardized PR Quality Gates (Backend / Frontend / Docs)
- Documentation Governance — same-PR rule for code + tests + docs
- Release Process with branch-protection checklist for admins
Package versions: Frontend & Docs 1.1.0; Backend tag-only. Legacy docs alias: v1.1.0-platform.
Multi-Provider Email Delivery — production hardening (2026-07-19)
Production-readiness fixes on top of the multi-provider email implementation:
- Runtime isolation:
EmailManagerclears prior SMTP/Postmark/Mailgun secrets on every apply; queue middleware clears secrets after each mail job - Central HTTP middleware
central.mailre-applies Central mail on every Central request - Tenant system-mode test mail inherits Central; test-mail restores prior runtime config in
finally - Provider-conditional save validation; additive
email-logs.*permission migration; unsupported webhook capabilities no longer advertised - Upgrade/runbook notes for migrate,
email:migrate-tenant-mail-modes, composer packages,queue:restart
Multi-Provider Email Delivery implementation (2026-07-19)
Platform infrastructure: provider-agnostic email delivery for Central and Tenant.
EmailManager+ driver registry (SMTP, Postmark, Mailgun, log/array/sendmail) with Laravel mailer overlay +Mail::forgetMailers()- Central/Tenant settings:
mail_provider, encrypted API secrets,mail_mode(system|custom), reply-to, timeout - Queue middleware
ApplyEmailRuntimeConfigre-applies tenant/central mail config on theemailsworker - Structured test-mail responses (draft settings supported); email logs API + UI + weekly prune
- Artisan
email:migrate-tenant-mail-modesbackfills tenant modes from legacymail_host - Future stubs: webhook capability interface, resend/analytics services, failover config keys
- Developer guide: Multi-Provider Email
Multi-Provider Email Delivery roadmap (2026-07-19)
Documentation-only: product roadmap for provider-agnostic email delivery (Central + Tenant).
- Updated Product Roadmap: Multi-Provider Email Delivery section (
EmailManager/ driver abstraction, Central + tenant providers, logs, queue/retry, optional provider capabilities, analytics, test email, enterprise routing) - Distinguishes Planned, Future, and Enterprise capabilities; SMTP remains one interchangeable driver
- No application code, schema, API, or settings implementation changes
Clean URL static fallbacks (2026-07-18)
Deep links work on default Forge/Nginx without per-server $uri.html rewrites.
- Post-build script
scripts/ensure-clean-url-indexes.mjscopies each VitePress page to a directoryindex.htmlso/path/to/pageresolves via standardtry_files $uri $uri/ - Wired into
npm run docs:build(CI publish included)
Modular architecture convention (2026-07-18)
Documentation-only pass establishing the long-term modular architecture standard for all future modules.
- Added Architecture section: Module Architecture, Module Dependencies, Module Licensing
- Updated Product Roadmap: Calendar, Meetings (scheduling, Zoom, Google Meet, email reminders), AI Integration (Planning)
- Documented development convention: self-contained modules, declared dependencies, independent licensing compatibility
- Updated site footer copyright to © 2026 EloSync. All rights reserved.
- No application code, billing, marketplace, schema, or API changes
Documentation sync — migrate-only modules & auth/RBAC separation (2026-07-18)
Docs pass aligning architecture, deployment, RBAC, provisioning, entitlements, database, API, and Communication Templates guides with the final production architecture.
- Production deploy is migration-driven (
migrate --force+optimize); no catalog/permission reseeding - Authentication has no authorization side effects; workspace RBAC is provisioned explicitly
- Communication Templates documented as a reusable platform module (placeholders, render/preview, WhatsApp)
- Future modules follow the data-migration registration pattern
Authentication / authorization separation (2026-07-18)
Keep login and authenticated requests free of RBAC side effects.
TenantAuthorizationProvisioningServiceprovisions workspace roles during workspace createTenantAuthBootstrapServiceonly creates owners and issues tokens- Dashboard, role listing, and user listing no longer repair permissions
- Legacy shared-role isolation remains an explicit
tenants:isolate-rolesmaintenance command - Existing workspaces still receive new permissions through additive data migrations
Communication Templates production deploy hardening (2026-07-17)
Eliminate production db:seed for Communication Templates.
- Data migrations register the catalog module and grant permissions additively during
php artisan migrate DefaultModuleRegistrarinstalls the module only for workspaces missing a subscription row (never reactivates cancelled/suspended)TenantPermissionSynchronizercreates missing permission vocabulary and grants only new permissions without resetting customized roles- Authentication and dashboard requests no longer mutate roles or permissions
TenantAuthorizationProvisioningServicecreates default RBAC during workspace provisioning; deploy migrations add future permissions- CatalogSeeder remains insert-only for fresh/local environments
Communication Templates module (MVP) (2026-07-17)
Catalog module for reusable plain-text templates with a placeholder registry and WhatsApp Web (wa.me) from Leads.
- Backend:
communication_templatestable, CRUD + preview/render APIs, Lead/shared placeholder providers, UUID route binding, permissions (view|create|update|delete|use), platform audit - Frontend: Templates admin page with chip inserter, Lead detail WhatsApp picker, module-gated nav
- Docs: developer / user / API / deployment guides; E2E
test:e2e:communication-templates - Default-included with Leads and Tasks for new workspaces
In-app notifications production hardening (2026-07-16)
Release hardening for the frozen notification stack (no architecture changes).
- Backend: after-commit queueing, broadcast-after-persist guard, reassignment dedupe keys, Reverb origin pinning
- Frontend: Forge
window.envReverb config, Echo reconnect + poll-only-when-disconnected, dead-code cleanup - Docs: Notification System deployment runbook, contract/API/runbook links, production checklist + troubleshooting
In-app notifications production stack (2026-07-16)
Phased delivery of the frozen notification architecture (payload v1 → digests → Reverb/Echo → registry → browser → prune).
- Backend: schema_version envelope, route descriptors, NotificationBatch + lead digests, Reverb private channels,
notifications:prune, unread indexes - Frontend: Laravel Echo, modular
src/notificationsregistry, optimistic bell UX, Web Notification API manager - Docs: notification-architecture-contract.md, API + roadmap updated
- Lead assigned: database + broadcast only (mail deferred); bulk/import → one digest per assignee
Notification architecture contract frozen (2026-07-16)
In-app notification contracts are frozen before phased implementation (payload v1, route descriptors, NotificationBatch, Reverb/Echo, modular registry).
- Docs: notification-architecture-contract.md
- Linked from module development standard and tenant notifications API
- No application code in this change; implementation follows Phases 1–8
Branding disk split (local logos/favicons) (2026-07-16)
Logo and favicon can use a dedicated disk while other uploads stay on S3.
- Env:
FILESYSTEM_BRANDING_DISK(defaults to uploads /FILESYSTEM_DISK) - Production split:
FILESYSTEM_DISK=s3+FILESYSTEM_BRANDING_DISK=public+php artisan storage:link FileUploadServicebranding helpers; central/tenant branding + workspace logos use the branding disk- Docs:
developer-guide/object-storage.md; settings production checklists updated
Lead Import (reusable Import framework) (2026-07-16)
Bulk CSV/XLSX lead import with a reusable framework for future modules.
- Permission:
leads.import(admin/manager; mirrors export) - Package: Maatwebsite Laravel Excel; queue:
ProcessLeadImportJobonimports - API: template, upload, mapping, options, preview, run, history, original/failed/error downloads
- SPA: 5-step wizard + import history beside Export; polls queued progress
- Duplicate modes: skip / update (needs
leads.update) / keep; unique fields email and/or phone - Single migration
lead_imports; row failures as downloadable CSVs (no per-row tables) - Writes via
LeadService::create()/update(); auditlead_import_completed/lead_import_failed - Tests:
LeadImportTest; Playwrightleads.import.spec.ts - Docs: user/developer/API/deployment guides updated (import no longer deferred)
Tenant manual member email verification (2026-07-16)
Workspace owners and admins can help members who never receive the verification email.
- Permission:
users.verify(owner + admin role map) - API:
POST /api/tenant/v1/users/{user}/verify-email,POST /api/tenant/v1/users/{user}/resend-verification - SPA Users row menu: Resend verification / Mark as verified
- Audit:
tenant_user_email_verified,tenant_user_verification_resent - Tests:
TenantUserVerifyEmailTest; Playwright tenant RBAC covers mark-as-verified
Dedicated emails queue (2026-07-15)
All ShouldQueue notifications dispatch to the named emails queue via QueuesOnEmails.
- Worker:
php artisan queue:work --queue=emails --sleep=1 --tries=3 --max-time=3600 - Optional standby:
--queue=defaultfor future non-mail jobs - Docs: production runbook, module-development-developer, backend README Queue Setup
SPA runtime config (multi-client / Forge) (2026-07-15)
Frontend API origin is runtime via Forge-generated /config.js (window.env), not baked into CI.
- Deploy script sources site
.envand writesVITE_API_URL/VITE_APP_NAME/VITE_API_MODE - Same
build-artifactsartifact for every client; each Forge site owns its.env - No committed
config.js/config.example.jsin the SPA repo - Docs:
architecture/frontend-build-artifacts.md, production runbook
Frontend production CI/CD (build-artifacts) (2026-07-15)
Automated SPA production builds on merge to main without committing dist/ to source.
- GitHub Actions workflow
frontend-build.yml: lint, typecheck, Vite production build, validation, secret scan - Uploads GitHub Actions artifact
frontend-build(30-day retention) - Publishes deployment-ready assets to the
build-artifactsbranch withbuild-info.jsonprovenance - Docs:
architecture/frontend-build-artifacts.md; production runbook SPA section updated
Object storage migration (Wasabi / S3) (2026-07-15)
All user uploads go through Laravel Storage via FileUploadService. Production uses FILESYSTEM_DISK=s3 (Wasabi or any S3-compatible provider); local uses FILESYSTEM_DISK=public.
league/flysystem-aws-s3-v3+ fullAWS_*env (ENDPOINT/URLfor Wasabi)- Central/tenant branding and admin tenant logos use
FileUploadService(unique filenames, relative keys, disk-agnostic URLs) - Tenancy filesystem bootstrapper no longer remaps the shared
public/uploads disk (prefix isolation viatenants/{uuid}/…) - Artisan
storage:migrate-to-s3copies existing local objects idempotently - Docs:
architecture/object-storage.md; settings production guides updated
Go-live hardening (2026-07-15)
Production readiness audit for Central + Tenant with Leads & Tasks. Billing and security fixes only — no architecture redesign.
Critical / High
- Module cancel now calls Stripe/Creem
cancelSubscriptionbefore local entitlement revoke (prevents silent continued charging) - Failed webhook logs are reclaimed on provider retry (no permanent swallow after a transient 500)
- Recurring
invoice.payment_succeededcreates a renewal invoice/payment when the provider transaction id is new - Cashier
/stripe/webhookhandlesinvoice.payment_failed - Email verification asserts the user belongs to the current tenant
- Boot refuses
APP_DEBUG=truein production; HTTPS scheme forced in production;SecureHeaders+TrustProxies - Lead CSV export escapes formula injection (
=+-@) - Checkout merges admin
gateway_metadatainto Stripe/Creem session metadata - SPA: Leads/Tasks assignee fetches gated on
users.list(no spurious 403 toast for staff); assignee fields PermissionGated in create/edit dialogs
Ops / docs
- CRM due-notification command applies tenant SMTP runtime config
- Production runbook: cache isolation wording, Creem webhook secret, frontend SPA deploy notes
- Tests: cancel-at-gateway, failed-webhook retry, renewal ledger; marketplace Stripe mocks allow
completedCheckoutEvent
Creem payment gateway (2026-07-14)
Add Creem as a second provider behind the existing PaymentGatewayInterface / GatewayManager stack. Stripe behavior is unchanged.
CreemGateway+ HTTPCreemClient, webhook HMAC verification, provider-neutraltenant_gateway_customers- Config:
config/creem.php(CREEM_*env fallbacks); seed + admin enable/disable/default/config - Webhooks:
POST /webhooks/gateways/creem(creem-signature) → Billing Engine module activation - Product mapping uses Creem
prod_…ids; Central UI credential fields + Creem-specific mapping copy - Return-URL / “Complete subscription” sync via
confirm-checkoutso paid checkouts activate without a second session when webhooks are delayed - Docs:
billing/creem.md; tests:CreemGatewayTest,CreemWebhookTest,CreemCheckoutConfirmTest
Provider-agnostic billing (2026-07-14)
Decouple Modules from Stripe. Stripe is one payment driver; catalog pricing stays on Modules.
- Added
payment_gateway_module_prices(gateway × module × billing cycle → product/price references) - Migrated existing
modules.stripe_*IDs into the mapping table, then dropped those columns - Added
modules.currencyandworkspace_module_subscriptions.payment_gateway_id BillingEngine/StripeGatewayresolve checkout prices from mappings; consolidated billing skips recurring-gateway subscriptions- Central API + UI: Module form is catalog-only; Payment Gateways → Product Mapping manages provider refs
- Docs:
billing/*,architecture/database.md,api/central-v1.md,admin-ui.md
RC1 — Production Readiness (2026-07-14)
Release Candidate hardening for paying-customer launch. No new business modules. Official notes: releases/rc1-production-readiness.md. Recommended tag: v1.2.0-rc.1.
Critical / High
- Revoke Sanctum tokens on user suspend; reject suspended sessions via
not.suspended - Webhook unique idempotency + safe payload summaries; Cashier + gateway ingress share claim store
- Registration defaults off; tenant password-reset URLs include
workspace - Impersonation limited to workspace owner role
- SPA query-cache cleared on session switches; email-verify URL allowlist; verified gate treats missing timestamp as unverified
- Board APIs cap per-column payload size; drawer query-param history for leads/tasks
Ops / a11y
- Health check probes Redis when used; skip-to-content; billing ErrorState; remember-me default off
Production Hardening Pass (2026-07-13)
Security, ops, and SPA hardening for launch readiness. Platform freeze unchanged (no Features/Plans/Limits reintroduced).
Critical / High security
- Block assignment of protected
superadmin(workspace owner) roles via tenant user APIs - Central role permission sync filtered to
central-apiguard only - Gateway webhooks verify signatures before persisting payloads; store safe summaries only
- Branding uploads reject SVG (stored XSS)
- Narrowed
User/CentralUser$fillableprivileged fields - CORS origins pinned via
FRONTEND_URL/CORS_ALLOWED_ORIGINS(no*) - Payment API redacts raw
gateway_response/webhook_payload - Webhook + API rate limiting; schedule
withoutOverlapping - SPA: safe post-login redirects, route
RequireAccessgates, ErrorBoundary, query cache clear on logout
Ops
- Notifications implement
ShouldQueue - Health
/upverifies database connectivity - Production runbook: architecture/platform-production-runbook.md
.env.exampleproduction guidance
Sprint 2 — CRM UX (2026-07-13)
Leads/Tasks UX + notifications + tenant dashboard widgets. Platform freeze unchanged.
Leads
lead_value(renamed fromestimated_value); independent statusactive|waiting|on_hold|closed|archived; priority; assignment history- Kanban (default) + table; DnD opens drawer, save commits stage; KPIs / board / stats APIs
- Follow-up update/reschedule; export CSV/XLSX (filtered); convert stub (
converted_at, activity, status closed — Contacts deferred) - Permissions:
view|create|update|delete|assign|export|convert(no import); assignee scoping withoutleads.assign
Tasks
waitingstatus; UI labelsopenas To Do; board (default) + list; KPIs / board / stats APIs- Comments + History tabs;
tasks.change_due_daterequired to changedue_atafter create - Assignee scoping via
tasks.assign
Notifications & dashboard
- Historical initial release: database + mail channels; APIs list / unread-count / mark read / mark all; realtime was added in the later notification architecture rollout above
- Hourly
crm:send-due-notificationsfor due/overdue follow-ups and tasks GET /dashboardwidget registry gated by module + permission + assignee scope; no calendar widget until Calendar module
Docs
- Updated Leads/Tasks guides, database, module-development patterns, tenant UI
- API: tenant-v1-leads.md, tenant-v1-tasks.md, tenant-v1-notifications.md, tenant-v1-dashboard.md
v1.1.0-platform — Production Ready (2026-07-13)
Platform foundation declared Production Ready and frozen. No new business modules in this release — hardening only.
Logical freeze notes (never tagged in git). Official first tag: v1.1.0.
Alias: v1.1.0-platform
Verification
- Pest: 230/230 pass
- Playwright: 69/69 pass (
--workers=1, all projects: setup, auth, tenant, chromium) - Manual Cursor browser happy path: Central + Tenant
Authentication & SPA
- Auth matrix expanded: Pest + Playwright coverage for lockout messages (includes minutes remaining), remember-me, email verification, registration validation, tenant login, and wrong-workspace rejection
VerifyEmailGateSign out navigates to the context login page after logout
Billing & gateways
BillingEngine::resolveTenantfalls back topayment_idin event metadata when customer/subscription maps are absent- Gateway webhook failure/cancel Pest coverage
Validation & settings
- Central user and module validation Pest; Lead validation; module deactivate gate after uninstall
- System settings runtime asserts: Sanctum expiration, locale, Cashier currency, date/time formats
E2E stability
- Focused auth/tenant/central specs; page-object delete/clone assertions; local Playwright
workers: 1
Product roadmap documented (2026-07-12)
- Added product-roadmap.md: CRM → Sales → Billing → Purchasing → Inventory → Finance → HR → future expansion
- Linked from README and platform-freeze docs
- Phase 1 CRM: Leads + Tasks complete; Contacts / Companies / Calendar / Activities next
Tasks module (2026-07-12)
Architecture
- Second product module; mirrors Leads (flat Laravel,
module:tasks+ Spatie RBAC) - Functional differences only: status/priority/complete vs stages/follow-ups
Backend
- Tables:
tasks,task_notes,task_activities TaskService, events/subscriber (audit + assignment mail), tenant API routes- Permissions:
tasks.view|create|update|delete|assign|complete - Pest:
tests/Feature/Tenant/Task/TaskTest.php
Frontend
- Tasks list / form dialog / detail drawer; nav gated by module + permission
- Playwright:
npm run test:e2e:tasks(--project=tenant)
Docs
- modules/tasks.md (+ user / developer / production)
- api/tenant-v1-tasks.md
Leads reference module (2026-07-12)
Architecture
- First product module on the frozen foundation; blueprint for Tasks and later modules
- Flat Laravel layout (no Modules package);
module:leads+ Spatie RBAC - Pipeline-ready domain: stages, status workflow, assignment, notes, follow-ups, timeline
Backend
- Tables:
lead_stages,leads,lead_notes,lead_follow_ups,lead_activities LeadService, events/subscriber (audit + mail notifications), tenant API routes- Permissions:
leads.view|create|update|delete|assign - Auth payload includes
modules[]for SPA entitlement gating - Pest:
tests/Feature/Tenant/Lead/LeadTest.php
Frontend
- Leads list / form dialog / detail drawer; nav gated by module + permission
- Playwright:
npm run test:e2e:leads(--project=tenant)
Docs
- modules/leads.md (+ user / developer / production)
- api/tenant-v1-leads.md
Platform Freeze & Module Development Standard (2026-07-12)
Architecture
- Platform foundation locked — no redesign of Auth, Tenancy, RBAC, Billing, Marketplace, Settings, or Gateway architecture except critical security / data-integrity / production bugs
- Official Module Development Standard: flat Laravel layout, catalog +
module:+ Spatie permissions, audit + activity logging, Pest + Playwright, docs DoD - Cursor rules added in Backend and Frontend (
.cursor/rules/platform-freeze.mdc,module-development.mdc) - Leads designated as the reference business module; future modules must mirror it
Docs
- architecture/platform-freeze.md
- modules/module-development.md (+ developer / production)
Production Hardening (2026-07-12)
Authentication
- Shared tenant
/loginwith Workspace field (slug/domain);/central/loginisolated for platform admins - Server-owned workspace resolution: host → bearer token →
workspaceinput; fail-closed without context - Email verification enforced for Central and Tenant (
verifiedmiddleware + SPA verify pages) - Sanctum token TTL wired to
session_lifetime_minutes; Remember Me extends TTL; login lockout after failed attempts - Removed client tenant persistence (
tenantStorage/VITE_DEFAULT_TENANT_DOMAIN)
Provisioning & tenancy
- Central workspace create always requires an Owner; settings defaults and default role permissions seeded
- Suspended/archived workspaces denied at the API edge
Security, payments, impersonation, audit
- Tenant setting cache keys namespaced; isolation Pest coverage
- Stripe webhook signature verification + idempotency; payment matching by metadata; PaymentAttempt logging
- Impersonation issues real tenant tokens with timeout, banner, and revoke-on-end
- Broader PlatformAuditService coverage (auth, workspace, users, roles, settings, modules)
Client / docs
- Currency, date/time, locale, and timezone formatters consume bootstrap settings; SPA idle logout
- Authentication / tenancy docs updated for hybrid resolution and custom-domain readiness
Manual QA — Production Readiness Fixes (2026-07-12)
Backend
DomainRulenow accepts platform hostnames includingtenant.localhostandtenant.myapp.com(previously required three labels and rejected local domains)- Central
RoleServicelists onlycentral-apiroles (orphanedtenant-apirows no longer leak into Central Users/Roles UI) - Central role create always sets
guard_name=central-apiand scopes uniqueness to that guard InitializeTenancyre-resolves and switches tenant per request when the domain/header changes- New
tenant.usermiddleware rejects Sanctum tokens used against a different workspace (EnsureTenantUserBelongsToCurrentTenant) - Pest:
DomainRuleTest,RoleListIsolationTest,TenantTokenIsolationTest
Frontend
- Tenant form shows domain validation errors and uses
tenant.localhostplaceholder - Timezone options always include
UTCso the default value displays correctly
Tenant Users, Roles & Permissions (RBAC)
Backend
- Per-workspace roles via
roles.tenant_id(no Spatie teams); permissions remain sharedtenant-apivocabulary - Expanded
config/tenant-permissions.php(users, roles, settings, leads, tasks) - Tenant User + Role APIs: CRUD, suspend, password change, clone, permissions matrix
- Owner (
superadmin) bootstrap with full permissions; protected default roles - Legacy backfill:
php artisan tenants:isolate-roles(+ lazy ensure on Users/Roles APIs) - Pest: user/role CRUD, permission assignment, authorization, workspace isolation, legacy backfill
Frontend
- Tenant
/users,/roles,/roles/matrixreusing Central pages with workspace-aware copy and routes - Administration nav: Users / Roles (permission-gated)
- Playwright:
npm run test:e2e:tenant-rbac
Docs
authorization/tenant-rbac*.md— architecture, user guide, production security
Tenant Branding & Configuration
Backend
TenantSettingServiceresolves workspace settings with hierarchy: tenant override → tenant profile → Central → system default- Tenant Settings API: list/update, branding upload, test mail, public bootstrap
- SMTP passwords encrypted; runtime mail uses tenant SMTP when
mail_hostis set, otherwise Central - Branding assets stored under
tenants/{uuid}/branding/… - Permissions:
settings.list,settings.update - Pest coverage for resolver + API
Frontend
- Tenant Settings page (
/settings) — General, Branding, Mail with searchable selects, color picker, uploads - Settings store bootstraps resolved tenant branding when a workspace domain is known
- Playwright:
npm run test:e2e:tenant-settings
Docs
settings/tenant-settings*.md— hierarchy, SMTP, storage, security
Tenant Application UI Foundation
Frontend
- Tenant protected routes now use the shared
AppLayoutshell (Sidebar, Topbar, Breadcrumbs, Command Palette, User Menu) - Context-aware navigation:
centralNavigationGroupsvstenantNavigationGroups - Tenant sidebar: Dashboard, Leads, Tasks, Settings, Profile
- Tenant dashboard redesigned as layout-matched placeholders (welcome, workspace info, modules, activity, quick actions)
- Leads / Tasks / Tenant Settings reserved via
PlaceholderPage - Shared Profile page works on both
/profileand/central/profile
Docs
- Shared layout developer + tenant user guides
- Architecture note for shared design system / layout reuse
Tenant Authentication Foundation
Backend
- Self-service registration creates workspace owner, default roles/permissions, and returns a tenant Sanctum token
- Password reset emails point at SPA routes via
FRONTEND_URL - Tenant dashboard placeholder returns workspace + installed modules
- Email verification architecture prepared (
MustVerifyEmail+ signed verify/resend routes) - Impersonation helper reserved for future tenant token handoff
Frontend
- Tenant auth at
/login,/register,/forgot-password,/reset-password/{token} - Central auth moved under
/central/*with isolated token storage - Tenant dashboard placeholder for auth/impersonation verification
Docs / tests
- Authentication developer, user, and production guides
- Pest coverage for tenant auth; Playwright auth suites updated for
/central+ tenant flows
Features layer removed — module licensing + Spatie authorization
Licensing and authorization are fully decoupled.
Architecture
- Removed Features catalog (model, API, UI, entitlements
features[],features.*permissions,featurestable) - Modules remain pure licensing products via
workspace_module_subscriptions - User access stays on Spatie Roles & Permissions
- Tenant gating:
module:{slug}(licensing) thencan:{permission}(authorization) - Entitlements payload is now
{ core, modules }only - Default modules unchanged: Leads + Tasks (free, included, non-removable)
Backend
EnsureModulemiddleware replacesEnsureModuleFeature- Catalog seeder no longer creates Feature rows
- Migration drops
featurestable
Frontend
- Features admin page, nav, API client, and Playwright suite removed
- Marketplace / tenant details no longer show feature badges
Payment Gateway Management
Gateway-agnostic payment provider management for Central Billing.
Architecture
- Billing Engine talks only to
PaymentGatewayInterfaceviaGatewayManager - Stripe/Cashier isolated inside
StripeGateway; arch tests enforce isolation - Admin APIs for enable/disable/default/config/mode/test/webhooks/logs/capabilities
- Encrypted gateway credentials; secrets never returned to the frontend
- Generic webhook ingress
POST /webhooks/gateways/{code}+ Cashier Stripe route
Schema
- Expanded
payment_gateways(mode, encrypted config, webhook/test metadata) - Added
payment_attempts,gateway_logs,webhook_logs payment_methodsremains the workspace preferred-method store
Frontend
- Top-level Billing nav: Dashboard, Invoices, Payments, Transactions, Refunds, Payment Methods, Payment Gateways, Coupons, Taxes, Billing Logs
- Full Payment Gateways management UI (configure Stripe fields, sandbox/live, test connection, logs)
Docs
billing/payment-gateways.md(+ developer/user/production/webhooks guides)- Updated
billing-engine.mdandstripe-cashier.md
Central Application Settings refactor
Rebuilt system settings so every key is validated and consumed at runtime.
Backend
- Expanded mail/security/branding/localization settings; removed unused placeholders (
primary_color, feature-flag duplicates, display-only queue/disk keys) - Runtime config overlay (app name, timezone, locale, session lifetime, mail From/SMTP, Cashier currency)
- Encrypted
mail_password, branding file uploads, test-mail endpoint - Public bootstrap + self-service workspace registration gated by
registration_enabled - Tenant-only maintenance middleware (
tenant.available); Central stays fully operational - Centralized
PasswordRule/Password::defaults()from security settings - Support email + company name injected into tenant-facing auth emails
Frontend
- Settings UI rebuilt with SearchableSelect for timezone/locale/currency/formats
- App title, sidebar, button color CSS vars, favicon/logo from public settings
- App-wide date/time helpers; registration-closed and branded maintenance pages
Tests / docs
- Expanded Pest settings suite; Playwright settings coverage for registration-closed + maintenance copy
- Added Settings user / developer / production guides under
settings/ - Updated API, admin UI, database, and testing docs
Tenant form cleanup
Removed owner_id and address from the tenants schema and admin UI. Timezone, currency, country, and locale are searchable selects. Logo is an image upload (logo file field → logo_path / logo_url).
Central QA stabilization (Playwright)
Stabilized the Central Application with a full Playwright pass before further Tenant Application work.
Fixed
- Logout now revokes only the current Sanctum token (was deleting all tokens and breaking parallel E2E sessions)
- Dashboard growth/revenue series API keys aligned to frontend contract (
count,amount) — removed chartNaNdisplay - Settings E2E opens the Billing tab before toggling trial flags
- Auth greeting assertion matches time-based welcome copy
- Roles cleanup searches before deleting the original role after clone
Added (Playwright)
- Module suites: marketplace, billing, impersonation, permissions, profile
- Per-module npm scripts (
test:e2e:marketplace,test:e2e:billing, …) - Smoke coverage for Marketplace route
Docs
- Updated
testing/playwright.mdand Frontenddocs/testing/PLAYWRIGHT.md
Modular foundation complete (Phases 1–6)
Delivered marketplace APIs, Billing Engine, impersonation, and the financial ledger on top of the module-licensing foundation.
Added
- Marketplace:
GET /marketplace/modules,GET /marketplace/modules/{module}(published catalog, dependency hints,?tenant_id=install state) - Module subscriptions:
GET /module-subscriptions, show,POST …/cancel,POST …/deactivate;POST /tenants/{tenant}/modules(install) - Billing Engine (
BillingEngine,GatewayManager,ManualGateway,StripeGateway,ProrationCalculator) - Consolidated billing:
php artisan billing:run-consolidated(scheduled daily); one invoice per workspace billing cycle for all billable active modules - Financial ledger tables + read APIs:
invoices,invoice_items,payments,payment_transactions,payment_gateways,payment_methods,billing_addresses,taxes,coupons,refunds,credit_notes - Impersonation:
impersonation_sessionstable;POST /tenants/{tenant}/impersonate,POST /impersonation/{id}/end - Permissions:
module-subscriptions.*,invoices.*,payments.*,impersonation.*
Admin UI
- Marketplace browse screen
- Tenant details tabs: Overview | Modules | Billing; impersonate action with reason prompt
- Module catalog form retains marketplace pricing fields
Unchanged from prior entry
- Plans removed; Leads + Tasks remain default-included, non-billable
workspace_module_subscriptionsis licensing SoT; Cashier/Stripe is a gateway driver only
Module licensing foundation (Plans removed)
Replaced plan-based licensing with workspace module subscriptions.
Removed
- Plans, plan modules/features/limits, limit definitions, usage counters
- Plan-based
tenant_subscriptions/subscription_events - Central API/UI for plans, limits, and tenant-subscriptions
default_plan_idsystem setting
Added
- Module catalog fields:
uuid, pricing,trial_days,version,status,is_default_included,is_billable, Stripe price IDs module_categories,module_dependenciesworkspace_module_subscriptions+ history- Workspace billing profile:
billing_anchor_day,billing_cycle,proration_mode,next_billing_at EntitlementService+GET /tenants/{tenant}/entitlementsconfig/core-platform.phpfor always-on platform capabilities- Catalog seed: Leads and Tasks only (included, non-billable)
- New workspaces auto-install Leads + Tasks (
source=included, not cancellable by owners)
Prior: Central SaaS Platform completion
See git history for the earlier plan-based Central Platform delivery notes (tenants, users, roles, Cashier scaffolding, dashboard). That licensing model has been superseded.