SIGNALS Documentation
API Reference

Changelog

All notable changes to Signals, ordered by version.

0.6.0 — Billing & Financial — Quote-to-Cash, Tax, Documents, Notifications, Sub-Hire & Containers

Licensing change

  • Signals is now licensed under AGPL-3.0-only, effective v0.5.2 and all later versions (releases prior to v0.5.2, including v0.5.0/v0.5.1, remain available under MIT per the NOTICE file). This release ships a NOTICE file (copyright, commercial licensing contact, trade marks) and CLA.md (Contributor License Agreement — licence grant, not assignment) for external contributors
  • A platform documentation page for Licensing explains AGPL obligations, commercial licensing enquiries, the CLA, and trade mark restrictions

Added

Event-sourced financial aggregates

  • Invoices, payments, credit notes, purchase orders, and virtual stock intakes are Verbs event streams with projected read models, matching the Phase-3 Opportunity aggregate pattern — InvoiceGenerated, InvoiceIssued, PaymentRecorded, CreditNoteIssued, PurchaseOrderCreated, VirtualStockIntakeConfirmed, and related lifecycle events
  • Concurrency-safe document numbering via document_sequences with store-scoped sequences, collision guards, and entity-number generators for members and payments; opportunity numbering converges onto the same sequence infrastructure
  • Human-readable download filenames on every document download path; entity numbers exposed in search, API DTOs, webhooks, and billing documents

Quote-to-cash

  • Checkout — generate an invoice from an opportunity with a frozen tax snapshot at conversion time; Idempotency-Key support on checkout, payments, and refunds
  • Invoice issue and lifecycle — draft → issued → paid/overdue states with issue, void, and write-off actions; partial-quantity invoicing for billing only part of an order
  • Deposit and pro-forma invoices with partial-invoice tax policy; running balance tracking across payments and credits
  • Payments and refunds — record payments against invoices, issue refunds, and generate receipt documents; invoice margin reporting behind costs.view
  • Credit notes — full and partial credit with negated tax mirror; credit-note PDFs via the Document Engine
  • Customer billing emails — invoice, receipt, and statement delivery through the Notification Engine with merge-field templates
  • Finance navigation — full-page Invoices, Credit Notes, and Payments list/detail/create flows under a Finance menu section, gated by finance permissions; bare /credit-notes and /payments URLs redirect with 301s to the canonical routes
  • Invoicing API — REST surfaces under /api/v1/invoices, /credit_notes, and /payments with Sanctum abilities, Ransack filtering, includes, and OpenAPI schemas

Four-stage pricing pipeline

  • Stage 1 — price categories threaded through the Rate Engine with configurable tiers (P8, P4C21)
  • Stage 2 — discount rules with a JSONB criteria engine (CriteriaMatcher), auto-apply, and Verbs event sourcing (P5P7)
  • Stage 3 — surcharges with per-surcharge tax classes; surcharge line snapshots survive source deletion (P9, P4C3)
  • Stage 4 — quantity-break/volume and bundle pricing (P10)
  • Deal price — flat-headline and proportional modes with Recalculate, clear flows, and plugin hooks; mutually exclusive with price lock on opportunities (P11, P4C9)
  • Rounding strategy — half-up and banker's rounding via PricingSettings (P1)

EU/UK tax engine

  • DocumentTaxBreakdown extraction and a TaxResolver seam for all financial documents (P2)
  • Tax zones and tax categories with zone-aware resolution through DocumentTaxBreakdown (P12P13)
  • Four buyer types — business, individual, government, and charity — with reachable seeded zone rules; reverse charge with legal text, zero-rated, exempt, and outside-scope treatments
  • Product tax-category dimension live end-to-end on line items and documents (P4C2)
  • Async VIES VAT validation feeding reverse-charge eligibility, with 30-day revalidation and scheduled expiry checks (P14, P4C10)
  • Finance settings and tax-point policy with tax field-registry exposure; reverse-charge label setting and tax configuration audit trail (P15, P4C11)
  • Fresh installs now compute VAT correctly via named-rate fallback and default tax classes

Document template engine

  • Document Engine foundations — DomPDF dependency, schema, store branding, and seeded system templates (P3, P19)
  • Sandboxed Blade compiler with an allowlist, render pipeline, PDF drivers, and repeating headers/footers on every page (P18, P4C7)
  • Field-registry browser and document resolver layer for template field insertion (P16)
  • Concurrency-safe document numbering sequences with store scoping (P17)
  • Three-panel Livewire template editor — CodeMirror 6 source, live preview, version diff/restore, and export/import (P20, P4C18, P4C30)
  • Document API and public token sharing — PIN protection, expiry, and QR codes on shared links (P21, P4C19); frozen regeneration from rendered_data snapshots
  • Opportunity Documents tab for attaching and previewing documents on an opportunity
  • Document events, audit trail, and webhooks — including pdf_generated; plugin seams and resolvers for extensibility (P22, P4C17)
  • Admin numbering page with live preview (P4-N4); sandbox hardening against stored XSS on public HTML document responses and IDOR in preview rendering

Notification & communication engine

  • Channel provider registry — email, database, broadcast, and a plugin seam for additional channels (P23)
  • Three-layer resolution and dispatch pipeline with communication_log persistence (P24)
  • Event→notification wiring with replay guard to prevent duplicate sends on projection replay (P25)
  • Merge-field renderer and communication templates with versioning (P26)
  • Customer communication path with opt-out/unsubscribe and configurable TTL plus effective prune safety window (P27, P4C22)
  • Notification settings UI, notification bell (real-time via Reverb), member notification preferences, and communication log admin view (P28, P29)
  • Communication templates and communication-log API; notification preferences and channel-providers settings API (P4C13, P4C14)

Sub-hire & procurement

  • Supplier costs — product supplier cost reference data (P40)
  • Event-sourced purchase orders — full PO aggregate with receipt workflow (P41)
  • Virtual stock intakes feeding the availability engine as signed supply (P42, R1)
  • Sub-hire shortage resolver with batch cross-opportunity consolidation (P43)
  • PO receipt → intake confirm → shortage clear loop with purchase-axis tax (P44)
  • Cost apportionment — five strategies through the cost event stream, with date-change recalculation (P45, P4C16)
  • Return-to-supplier workflow with intake P&L surfacing and per-opportunity P&L (P46)
  • Shortage transition API, escalation/overrun jobs, and partial dispatch support (P47)
  • Serialised virtual-stock assets for tracked sub-hire inventory (P4C15)
  • Field-level cost visibility on POs and intakes — costs.view gates cost fields, not whole resources (P4C5, P4C12)

Serialised container lifecycle

  • Container config, templates, and policy validation (P48)
  • Container CRUD, temporary containers, and pack/unpack events (P49)
  • Seal/unseal with compliance manifest (P50)
  • Manual dissolve with warn-and-resolve packing and transfer (P51)
  • Dissolve-on-dispatch transport mode (P52)
  • Kit dispatch, kit return, and check-in modes (P53)
  • Repack-on-return with UI trigger, transfer hooks, and editable scan mode (P54, P55.1)
  • Container warehouse UI with plugin and webhook seams (P55)
  • Cross-engine integration pass validating pricing, tax, documents, notifications, sub-hire, and containers end to end (P56)

Cross-cutting

  • Custom views for Phase-4 entities — saved list configs on invoices, credit notes, payments, purchase orders, and related resources (P4C8)
  • Global Flux confirm modal replacing native wire:confirm dialogs across the UI (P4-CL1)
  • Dashboard KPI queries, full Horizon queue coverage, and Phase-4 demo seed substrate (P4-CL1)
  • Webhook parity scanner and temp-barcode namespace guard (P4C23)

API

Also in this tag (v0.5.1 — documented but never published as a GitHub Release)

  • API Console — summary at /api plus Explorer, Request Log, Usage, and Webhooks; weighted metering (#[PriceWeight]) on endpoints, outbound webhook events, and inbound sources; Redis-buffered usage rollups; inbound webhook receiver with HMAC verification; GET /api/v1/usage, /usage/export, /request_logs, /endpoints
  • Job Planner rework — overlap-only timeline (Gantt mode removed), lane-packed job cards, Delivery / In-Use / Collection phase bands, and a per-product Product Bookings page
  • Documentation IA overhaul — nav-based sections, landing page, and ⌘K search across titles, descriptions, headings, and body text

Changed

  • Owner bypass no longer skips state-dependent policy rules — authorization checks remain consistent for all roles (P4-CL1)
  • Admin sidebar always shows all sections (previously some were hidden until configured)
  • Flat-deal checkout carries headline totals correctly through to issued invoices (P4 UAT)

Fixed

  • Invoice overdue tab uses the derived overdue scope rather than a stale status filter (P4 UAT R2)
  • Credit-note modal partial-credit line editor and usage-metering FK resilience (P4-CL1)
  • Notification bell pane refreshes on open with event-specific bodies (P4-CL1)
  • Invoice payments app-allocated primary key collision (P4C4)
  • Invoice comms query uses exact document matching (P4C6)
  • Demo seeders survive real tax provisioning and per-store template setup
  • UAT sweep — bell badge sync, offline reverse-charge demo, real checklist detections, SQLite-safe tier migration rollback

Notes

  • Phase 4 closes the quote-to-cash loop: an opportunity converts to an invoice, gets paid, and reconciles — with EU/UK tax handled correctly, documents rendered from per-store templates, customers notified automatically, shortages resolved through supplier sub-hire, and serialised containers tracked through a full pack/seal/dispatch/return lifecycle
  • All financial totals follow the framework's ex-tax model with tax computed finally and grouped by tax class; money is stored in integer minor units and serialised as decimal strings in API responses
  • Financial POST endpoints honour Idempotency-Key headers to prevent duplicate side effects on retry
  • This tag will not be published as a GitHub Release until after Phase 5/5.5; the repo changelog documents the work in the meantime

Testing & Quality

  • 9,412 passing tests (28,329 assertions) across three lanes — the parallel SQLite suite (9,183, 10 baseline skips), the sequential env-writing group (97), and the real-PostgreSQL suite (132) — all green at release
  • Coverage: 96.68% lines (44,290/45,809), 93.50% methods (5,526/5,910), 88.60% classes (1,352/1,526) — the remaining gap is documented genuinely-unreachable defensive code
  • Static analysis: PHPStan level 6 passes with zero errors across the full project; Laravel Pint clean
  • Shipped after a full pre-release review across correctness, simplification, and maintainability lenses, with all high- and medium-severity findings remediated

0.5.1 — Planning Interface Polish — Overlap Planner, Multi-Store Availability & Product Bookings

Added

Product Bookings

  • Product Bookings page at /availability/bookings/{product}/{from}/{days} — a per-product demand grid (Current RMS "Product Bookings") with summary rows for available, held, and post-rent quantities; one row per opportunity for bulk demand and one row per serialised asset; Delivery / In-Use / Collection day-phase colours on every demand cell; row labels link back to the parent opportunity

Shared Services

  • App\Services\Opportunities\OpportunityPresentation — single source of truth for opportunity phase band colours (Delivery / In-Use / Collection), status swatches, icons, labels, customer Collecting/Returning badges, and legend assembly; shared by the Job Planner and availability booking grids
  • App\Services\Planning\PlanningStoreFilter — multi-store name↔id resolution for planning views (Signals tag-input store filter; empty selection = all in-default-query stores)
  • AvailabilityService cross-store aggregationgetCalendarForStores, productTotalStockForStores, and getGanttForStores sum availability and stock across selected stores for the By Group grid and Product Bookings drill-down

Demo Data

  • PlannerDemoSeeder and AvailabilityDemoSeeder — local demo datasets for the Job Planner and availability grids, including asset allocations

API Console

  • Full-page API Console at /api (new API sidebar section) with a summary dashboard and navigation into Explorer, Request Log, Usage, and Webhooks — gated by api.access and per-area permissions (api.explorer.use, api.logs.view, api.usage.view, webhooks.manage)
  • Explorer at /api/explorer — interactive request builder backed by ApiEndpointCatalogue; select resource, endpoint, query params, and token; executes via ExecuteExplorerRequest using a short-lived ephemeral Sanctum token that is deleted immediately after each call
  • Request Log at /api/logs — unified browse across api, webhook_sent, and webhook_received channels with date/status/token/method filters, expandable detail rows, and async CSV export
  • Usage dashboard at /api/usage24h / 7d / 30d / custom windows, channel filter, breakdowns by day/token/endpoint/resource/status, and async CSV export
  • Webhooks at /api/webhooks — outbound subscription CRUD with delivery logs plus inbound source management (slug, secret, signature header/algo, expected events, per-source price weight)

Metering & logging

  • API request logging via LogApiRequest middleware on all /api/v1/* routes — captures route, token, timing, status, bytes, and optional redacted bodies into request_logs
  • Weighted metering with integer price weights 0–4; weight 0 = logged in detail and request counts but zero weighted units (audit-only, no quota consumption)
  • Three metering channelsapi (REST), webhook_sent (outbound deliveries), webhook_received (inbound receiver)
  • Daily rollups in usage_daily with dedupe keys; Redis-buffered writes flushed by api:flush-logs (every minute); detail rows pruned by api:prune-logs (default 90-day retention)
  • Price weight sources#[PriceWeight] attribute on API controllers (PriceWeightResolver); per-event weights in WebhookEventRegistry for outbound webhooks; price_weight DB column on inbound_webhook_sources for inbound traffic

Inbound webhooks

  • Public receiver at POST /webhooks/receive/{slug} with VerifyInboundWebhookSignature middleware — HMAC verification using per-source secret, algorithm, and header name; 200 + InboundWebhookReceived on success, 401 on bad signature, 404 for unknown/inactive slugs
  • inbound_webhook_logs table and Request Log integration on the webhook_received channel

Outbound webhook metering

  • DeliverWebhook records price_weight / weighted_units on first delivery attempt and increments webhook_sent daily rollups using WebhookEventRegistry weights

API surfaces

  • GET /api/v1/usage and GET /api/v1/usage/export — aggregated usage with channel/dimension/window filters (api-usage:read)
  • GET /api/v1/request_logs — paginated detail log with Ransack filters (api-log:read)
  • GET /api/v1/endpoints — endpoint catalogue with abilities and price weights (system:read); all meta endpoints declared at weight 0

Registries & catalogue

  • AbilityRegistry — canonical Sanctum ability definitions with labels and groups
  • WebhookEventRegistry — extended with per-event price weights alongside the existing allow-list
  • ApiEndpointCatalogue — introspected route metadata for Explorer, endpoints API, and request log enrichment

Documentation

Docs site

  • Documentation landing page at /docs — hero plus a card grid per section with icons, descriptions, and key page links (replaces the redirect to the Introduction page)
  • ⌘K / Ctrl+K search across all documentation — matches page titles, descriptions, headings, and body text with context snippets; available from the docs header on every page
  • Placeholder pages for Workflows and Plugin SDK under Development

Changed

Docs site

  • Navigation restructured around the app's areas — top-level sections are now Getting Started, CRM, Job Planning, Resources, Finance, Operations, and Development, with the Changelog at the bottom; page URLs are unchanged
  • Collapsible sidebar sections with grouped sub-headings, per-level indentation, and persisted open/closed state; the API reference now sits under Development as a nested sub-section
  • SSO and Magic-Link Login pages merged into Authentication & Settings; the old URLs redirect permanently

Job Planner

  • Overlap-only timeline — Gantt-per-row mode and the layout toggle removed; jobs are lane-packed into a horizontal day-column grid that fills its panel edge-to-edge
  • Per-day job-count heatmap row above the lanes (colour intensity by load) and weekend shading on day columns
  • Sticky chrome — control bar, day headers, and heatmap row stay pinned while scrolling
  • Lane-packed job cards showing number — subject, date/time range, venue address, status line, opportunity tags, and Customer Collecting/Returning badges; the whole card links to the opportunity
  • Card colours and labels driven by OpportunityStatus; Delivery / In-Use / Collection phase bands snapped to day columns via OpportunityPresentation
  • Default view = 1 month, with both orders and quotes included

Equipment Availability (By Group)

  • Tightened grid so 30 days fit on load; softer sans-serif table font for long planning sessions
  • Multi-store selection via Signals tag-input (default = all stores; availability summed across the selection)
  • Signals datepicker; filter toggles for Shortages only, Warnings only, and Include quotes (switches)
  • Loading skeletons on first paint; planning filter row aligned with the Job Planner (shared store tag-input, datepicker, and toggle styling)

Fixed

CI & Tooling

  • Claude Code Review GitHub Action — restored anthropics/claude-code-action@v1 after a beta→v1 regression (PR #43)
  • Coverage scripts — purge stale .php coverage dumps before each composer test:coverage run so merged reports stay accurate

0.5.0 — Opportunities, Availability Engine, Shortage Resolution & Quote Versioning

Added

Opportunities

  • The opportunity — the event-sourced quote/order document at the centre of a rental job — with a two-axis model: a state (Draft → Quotation → Order) and a per-state status (Quotation/Reserved, Order/On Hire, and so on). Reads hit a fast read-projection; every write is recorded as an event so the full history is preserved and replayable
  • Full-page Opportunities list at /opportunities (under a new Job Planning navigation section, gated opportunities.access) with Active / Archived / All filtering, state and status chips with counts, column toggling, export, multi-select bulk archive/restore, row actions, and saved custom views (All / Drafts / Quotations / Orders)
  • Opportunity detail page with tabs for items, costs, versions, shortages, activities, custom fields, and files; a header carrying the state and status badges, breadcrumbs, an Edit action, and a split-button of the legal state-transition actions
  • State-transition actions — Convert to Quotation, Convert to Order, Reinstate, Reopen, Revert to Quotation, Revert to Draft, Lock / Unlock pricing, and Change Status — each offered only when legal for the current state and status (the page asks the same available_actions engine the API exposes), with raced or illegal moves surfaced as clear messages rather than errors
  • Create / edit form mirroring the API path: subject, member, store, currency, hire and charge dates, reference, tags, tax-inclusive flag, and custom fields, all flowing through the same CreateOpportunity / UpdateOpportunity actions and DTOs as the API; opportunity numbering follows the configurable Opportunity Settings format
  • FX and tax locks — an opportunity snapshots its exchange rate and tax treatment when committed so later rate or tax-rule changes don't silently alter an agreed deal; an Unlock Rates action (gated opportunities.unlock_rates) releases the locks to re-rate
  • Asset allocation, dispatch, and return for orders — serialised products allocate to specific assets with batch allocate / prepare / book-out / check-in actions; bulk products dispatch and return by quantity, driving the order's statuses (Active → Dispatched → On Hire → Returned → Checked → Complete) with automatic status promotion

Line-Item Editor

  • A fast, local-first line-item editor on the Items tab — edits apply instantly in the browser and sync to the server in the background, so typing, dragging, and adding lines never wait on a round-trip — with live ex-tax totals (line, group, and grand) recomputed through the rate and tax engines on every change
  • Two-tier product search — an instant client-side index plus a debounced server fallback (PostgreSQL pg_trgm trigram matching) that marks newer, server-only catalogue hits with a "new" badge — and a quick-add bar that parses a quantity and term together (6 spiider), with full keyboard navigation
  • Nestable groups — product lines, group rows (named sub-sections with their own subtotal), and free-text note lines all live in one drag-sortable tree; drag any row to reorder, nest a line under a group, or nest groups within groups, and add lines, groups, or text inline. Ungrouped lines fall back to grouping by their product-group tree
  • Deal price and price lock — set a single negotiated deal total that caps the headline charge while keeping the component breakdown, or lock the pricing to freeze every line's rate, quantity, discount, days, and removal against further edits; the two are mutually exclusive
  • Per-line edits — quantity (with rippling accessory quantities), manual price override, discount, line-level dates, optional flag, product substitution, description and warehouse notes, merge-duplicates, and removal
  • Accessory sub-rows — real, persisted line items nested beneath their principal product, priced through the rate engine and generating their own availability demand, whose quantities ripple from the parent line by their configured ratio — and per-line availability status chips (available / reserved / booked-out)

Availability Engine

  • Demand-based availabilityavailable = total stock − total demanded per product, store, and time slot, at a configurable resolution (daily, half-daily, or hourly), backed by PostgreSQL range types, a GiST index, and an exclusion constraint
  • Full-page Equipment Availability view at /availability (Job Planning section, gated availability.view) with three view modes: a By Product Group grid (the default), a products-by-days Calendar reading a pre-calculated daily summary, and a per-product Timeline (Gantt) decomposing each booking into its prep, on-hire, and turnaround zones
  • By Product Group grid — select a product group and date window (7, 14, 28, 30, or 60 days) to see every product's daily availability as colour-coded cells (green/amber/red/cyan for available/warning/shortage/booked) with available and held quantities; filterable by shortages-only or warnings-only; rows link directly to the per-product timeline
  • Store, date-range, product, and view state persisted in the URL; a shortage count for the visible range; and a caveat surfaced for kit/composed products, whose availability is read-time and so do not appear in the daily-summary grids

Shortages

  • Live shortage detection — the gap between what an opportunity's lines need and what is free for their products, store, and dates; shortages are computed on every read and never stored, while resolutions and acknowledgements are persisted
  • A Shortages tab (gated shortages.view; resolving needs shortages.resolve) showing each short line's requested, available, shortfall, and remaining-shortfall figures, with critical shortages flagged on confirmed orders
  • A Block / Warn / Allow confirmation gate on Convert to Order driven by the store's shortage policy, with the shortages.ignore permission relaxing the gate one level and recording an acknowledgement (a frozen snapshot of the shortage state) when proceeding past a warning
  • Six non-purchase-order resolvers — Reallocate, Substitute, Transfer, Date shift, Partial, and Waitlist — each contributing concrete options per short line, with self-contained resolvers (Partial, Date shift, Waitlist) taking effect immediately and the rest recording a pending intent for follow-up
  • A persisted resolution lifecycle (Confirm → Start → Fulfill, plus Cancel and Fail with reasons), with only the legal transitions offered and the store's dispatch policy surfaced for fulfilment

Quote Versioning

  • A Versions tab maintaining multiple quote versions per opportunity — revisions (successive iterations that supersede their parent) and alternatives (parallel options) — shown as a version tree with the active version marked and each node showing its type, status, net total, and author
  • Per-version lifecycle actions (Create, Activate, Send, Accept, Decline, Rename, Delete) offered only when legal, with activation confirm-gated because it swaps the reserved demand to the newly active version
  • A version diff — select any two versions to see the added, removed, and changed lines with the net total delta; on Convert to Order the accepted (or active) version becomes the order and the rest are superseded

Dashboard & Navigation

  • A new Job Planning navigation section (header mega-menu and sidebar) hosting Opportunities, Equipment Availability, and the Job Planner, gated opportunities.access
  • A dashboard pipeline + shortage widget and a New Opportunity quick action (gated opportunities.create)
  • A Job Planner at /planner — a visual Gantt and overlap timeline of quotes and orders across a configurable 1-week, 2-week, or monthly window, with store, type (quotes/orders), and free-text filters and a colour key by workflow status

Real-Time

  • The Laravel Echo / Reverb client bundle is wired, so opportunity availability badges, the Equipment Availability calendar and Gantt, and the Shortages panel all update live (via availability.opportunity.{id}, availability.store.{storeId}, and per-product channels) as bookings, returns, stock movements, and resolutions occur

API

  • New REST surfaces under /api/v1 for Opportunities (CRUD, lifecycle transitions, line items, costs, assets, allocation/dispatch, deal-price override, quote versioning, available-actions engine, participants, restore, clone, reinstate, reopen, revert-to-quotation, revert-to-draft, unlock-locks), Availability (point and range reads, available-assets, calendar grid, Gantt, store-wide shortage sweep), Shortages (detection, shortage gate pre-check, resolver listing, resolution, resolution lifecycle transitions, resolutions index, acknowledgement), and Containers — each with Sanctum abilities, Ransack filtering, includes, and OpenAPI schemas

Documentation

  • Platform documentation pages for Opportunities (the lifecycle, line-item editor, quote versioning, state transitions, and FX/tax locks), Equipment Availability (the calendar grid, demand-bar Gantt, and resolution setting), and Shortages (detection, the Block/Warn/Allow gate, the six resolvers, and the resolution lifecycle)

Notes

  • Opportunities are one of the three soft-deleted entities; the list offers Active / Archived / All with archive and restore, and there is no merge (opportunities are not a duplicate-prone entity)
  • All opportunity totals follow the framework's ex-tax model — line and group totals are net, and tax is computed finally, grouped by tax class
  • Product lines, group sub-sections, and free-text lines are all event-sourced rows in a single nested line-item tree (a unified model — there is no separate sections table); each row's order and nesting are part of the opportunity's event history and survive replay
  • Availability on PostgreSQL uses native range types, a GiST index, and an exclusion constraint; the calendar grid reads a pre-calculated daily summary and so excludes kit/composed products, whose availability is derived at read time

Testing & Quality

  • 6,654 passing tests (19,529 assertions) across three lanes — the parallel SQLite suite (6,470, 8 skipped), the sequential env-writing group (97), and the real-PostgreSQL suite (87) — all green at release
  • Coverage: 96.64% lines, 95.31% methods, 91.24% classes (the remaining gap is documented genuinely-unreachable defensive code — concurrent-delete race guards, schema-impossible states, exhaustive-match defaults)
  • Static analysis: PHPStan level 6 passes with zero errors across the full project (~1,700 files); Laravel Pint clean
  • Shipped after a full pre-release review across correctness, simplification, and maintainability lenses, with all high- and medium-severity findings remediated

0.4.2 — Activities Calendar, iCal Feeds, SSO & Magic-Link Login

Added

Calendar

  • Full-page Calendar at /calendar that visualises scheduled activities, gated on the existing activities.access permission — no new entity, table, or permission
  • Three views — Day (staff columns against a vertical hour-axis), Week (seven day-columns against an hour-axis, coloured by owner; the default view), and Month (a seven-column grid of day-cells with event chips)
  • Pixel-precise hour-axis placement in Day and Week views; the visible hour window auto-expands to include any event outside working hours so nothing is clipped
  • All-day handling computed from the activity's times (starts at 00:00 with no end or ending at 23:59/next midnight), rendered in a band above the grid and as month chips
  • Pickable start date defaulting to today and looking forwards, with Today / Previous / Next navigation that steps by the active view
  • Owner filter restricting the calendar to selected staff users, with deterministic per-user colours shared across columns, blocks, chips, and avatars
  • Owner and participant avatars on each event (owner first, then participants), showing each user's photo where available and coloured initials otherwise
  • Unscheduled tray listing activities with no start time, each opening its detail modal so it can be scheduled
  • View, date, and filter state persisted in the URL so a calendar link reopens exactly as shared

Modals & Realtime

  • Add modal opened from an empty slot (pre-filled owner and start time) or a month day-cell (pre-filled date), reusing the existing activity form
  • Detail modal showing an activity's subject, type, status, owner, time, and location, with Complete, Edit, and Delete actions and a link to the full /activities/{id} page
  • Edit modal using the same form as create, reached from the detail modal
  • All writes flow through the existing CreateActivity / UpdateActivity / CompleteActivity / DeleteActivity actions — same validation, authorisation, audit logging, and activity.* webhooks — and the calendar refreshes instantly and locally after every change

iCal Feeds

  • Secure global iCal feed at /calendar/feed.ics (every scheduled activity) and per-user feeds at /calendar/feed/{user}.ics (a single owner's activities)
  • Feeds secured with Laravel signed URLs (no expiry); a missing or tampered signature returns 403, and the feeds serve without an authenticated session
  • Feed window covers activities from one year ago with no forward bound; activities without a start time are excluded
  • Hand-rolled RFC 5545 output (no new Composer dependency) — one VEVENT per activity with UID, DTSTAMP, DTSTART/DTEND in UTC, SUMMARY, LOCATION, DESCRIPTION, STATUS, TRANSP, and ORGANIZER, with proper text escaping and 75-octet line folding; all-day events emit ;VALUE=DATE
  • Copyable subscribe URLs surfaced in a feed modal on the calendar page (global and own feed for every user; full per-user list for administrators)

Settings

  • New Settings → Calendar page at /settings/calendar showing the current user's personal feed: a subscribe URL with a copy button, a Download .ics button, and Google/Apple/Outlook subscription help; administrators also see the global feed URL

Navigation

  • Calendar opened from the top navigation bar's calendar icon (gated on activities.access), with Activities relocated into the CRM menu; a "Go to Calendar" command-palette navigation entry is also available

Documentation

  • Platform documentation page for the Calendar (views, navigation, owner filter, modals, unscheduled tray, the settings page, and the week-start / working-hours / weekend settings that affect it)
  • API documentation page for the calendar feed endpoints, covering the signed-URL security model and its global-only revocation tradeoff

Single Sign-On (SSO)

  • SSO login via Google and Microsoft 365 (Azure common tenant) powered by Laravel Socialite — staff can sign in with their Google or Microsoft account without a Signals password
  • Auto-link by verified email: on first SSO login the provider's verified email is matched to an existing, active Signals user and the identity is linked automatically; unknown or unverified emails are denied and the user must be invited before using SSO
  • Subsequent logins match by stored provider identity (robust to later email changes in Signals)
  • Self-hosted credential configuration in Settings → Integrations → Single Sign-On: per-provider enable toggle, Client ID, and Client Secret (stored encrypted at rest; write-only — not re-displayed after save), with the correct callback URL shown for copy-paste into the Google Cloud Console or Azure portal
  • Signals Cloud mode: credential fields are hidden in the Integrations UI; only enable/disable toggles are shown (credentials are managed centrally by Signals Cloud)
  • Per-role SSO enforcement in Settings → Security → SSO Enforcement: admins can require specific roles to use SSO; password login for enforced roles is blocked with an inline guidance message and SSO buttons; the Owner role is always exempt (break-glass — cannot be selected)
  • 2FA still applies: SSO replaces only the password step; users with 2FA enabled are redirected to the existing one-time-code challenge after a successful OAuth callback
  • Allowed email domains allow-list (Settings → Integrations → Single Sign-On): when set, only users whose IdP email domain is on the list may sign in or auto-link — enforced on both the auto-link and existing-link paths — strongly recommended with Microsoft's multitenant common setting to prevent cross-tenant account takeover; an empty list permits any domain
  • Client secrets are now write-only end-to-end: they are never loaded back into the Integrations form, never rendered to the browser, and a blank secret field on save keeps the existing stored secret (enter a new value to rotate)
  • Routes: GET /auth/google/redirect, GET /auth/google/callback, GET /auth/microsoft/redirect, GET /auth/microsoft/callback; unknown providers return 404
  • oauth_identities table and OAuthIdentity model recording the (provider, provider_id, user_id, email) link

Authentication — Password Manager & Accessibility

  • One-time-code 2FA input now auto-submits when six digits are entered, removing the need to press Enter or click a button
  • /.well-known/change-password well-known URL pointing to the profile password-change page, so browsers and password managers can locate the change-password flow automatically
  • Invitation acceptance page includes a username field pre-filled with the invited email, giving password managers the anchor they need to save the credential against the right account on first use
  • Login and password forms now carry canonical autocomplete="username" / autocomplete="current-password" / autocomplete="new-password" attributes throughout the auth flow for consistent password-manager integration

Documentation

  • Platform documentation page for Single Sign-On — covers enabling Google and Microsoft (self-hosted and Signals Cloud), callback URL registration, auto-link matching, per-role enforcement, and 2FA interaction

Magic-Link Login

  • Passwordless login for existing users via a single-use link emailed to their address — an opt-in alternative to password login, enabled in Settings → Security → Magic-Link Login (off by default)
  • Links are valid for 15 minutes and can only be consumed once; requesting a new link invalidates any prior unconsumed link for that account
  • 2FA is not bypassed — users with 2FA enabled (or enforced) are redirected to the existing one-time-code challenge after the link is consumed
  • Anti-enumeration: Signals always returns the same neutral response ("if an account exists, we've emailed a link") regardless of whether the email is registered, so no information about account existence is leaked; the email is dispatched asynchronously so response timing cannot be used to infer an account's existence
  • Blocked for SSO-enforced roles at both the request step and the consume step, so a policy change after issue invalidates the outstanding link; the Owner role is always exempt
  • Tokens are stored as a SHA-256 hash only — the plaintext travels only in the email and is compared with hash_equals; request and consume endpoints are rate-limited per email and per IP
  • Platform documentation page for Magic-Link Login — covers the login flow, enabling the feature, anti-enumeration behaviour, SSO interaction, and the security model

Changed

Activities

  • Activity type is now a user-editable list of values — the seeded "Activity Type" list (Task, Call, Fax, Email, Meeting, Note, Letter), manageable in Settings → List Names — replacing the previous fixed enum. The change applies across the Activities pages, the Calendar create/edit/detail modals, and the API, where type_id now references a list_values id and responses include activity_type_name
  • Activity participants now resolve to the linked user's name and avatar (with photo where available) on the activity page, in the calendar, and in the owner/participant pickers; the activity page lists the owner first followed by participants
  • Activities API operations now publish full OpenAPI request/response schemas — the paginated list envelope, the single-resource envelope, and the create/update request bodies

Notes

  • The calendar reuses the existing Activities write path and activities.* permissions; no new permissions, webhook events, or database tables were introduced
  • Feed URLs are not individually revocable — rotating the application's APP_KEY invalidates all existing feed URLs at once

0.4.1 — Member Merge, Anonymise & Docs Overhaul

Added

Members — Merge & Anonymise

  • MergeMember action — transfers relationships, contact details, custom field values, and memberships from a secondary member to a primary member of the same type, then archives the secondary
  • AnonymiseMember action — erases a member's personally identifiable information (name, description, icon, emails, phones, addresses, links); irreversible; users cannot anonymise their own record
  • POST /api/v1/members/{member}/merge API endpoint — requires members:write ability and members.delete permission; returns the updated primary member under the member key; 422 on self-merge or membership-type mismatch
  • POST /api/v1/members/{member}/anonymise API endpoint — same auth requirements; 422 if the authenticated user targets their own record
  • member.merged and member.anonymised webhook events — both registered in WebhookService::EVENTS and dispatched by their respective actions

Navigation & UI Permission Gating

  • CRM mega menu in the application header is now gated behind members.access — hidden entirely for users without access
  • Command palette "Members" navigation entry and "New Member" create entry gated behind members.view / members.create permissions respectively
  • Dashboard "Add Member" quick action is now a live link gated behind members.create

Removed

  • Admin Modules page (/admin/settings/modules) — the page's toggle cards did not actually enable or disable functionality. Module selection now lives solely in setup feature profiles (FeatureProfile::modules()); stored settings('modules.*') values are unaffected

Documentation

  • docs/api/webhooks.md — Events table expanded from 7 to 39 events, grouped by domain (Members, Products, Stock, Activities, Rate Definitions, Tax, Users, Roles, Settings)
  • docs/platform/members.md — updated bulk actions (archive + conditional merge), row actions (View/Edit/Archive/Restore), archive filter chips, column toggle/export, merge modal, and anonymise; detail page tabs corrected from 7 to 8 (Overview, Information, Contacts, Activities, Opportunities, Movements, Invoices, Files)
  • docs/getting-started/seeders.md — expanded from 4 to 16 seeders; added CountrySeeder, ListOfValuesSeeder, CurrencySeeder, TaxClassSeeder, TaxRateSeeder, RevenueGroupSeeder, CostGroupSeeder, ProductGroupSeeder, EmailTemplateSeeder, NotificationTypeSeeder, ViewSeeder, RateDefinitionPresetSeeder, ProductSeeder, and ActivitySeeder; corrected DatabaseSeeder run order; added signals:seed-demo / signals:clear-demo demo data commands; updated idempotency notes
  • docs/api/members.md — documented POST /merge and POST /anonymise endpoints including request body, response shape, and 422 error cases

0.4.0 — Rate Engine

Added

Rate Engine

  • Composable rate engine — a rate definition combines a calculation strategy, a base period, and optional modifiers to describe how a charge is calculated over a rental window
  • Three calculation strategies — Period-based (charge per chargeable unit), Fixed (flat charge), and Hybrid (fixed initial charge plus per-unit charge thereafter)
  • Five base periods — half-hourly, hourly, daily, weekly, and monthly, each converting elapsed time into chargeable units
  • Multiplier modifier — tiered duration multipliers that scale the unit price as a rental lengthens, with the final tier inheriting forward
  • Factor modifier — quantity ranges that scale the per-unit subtotal, with an open-ended final range
  • Rate breakdown value objects — lossless integer-minor-unit arithmetic via Brick Money, rounded once at assembly, with structured line items and applied-modifier records
  • Time options for period and hybrid strategies — clock vs business-hours day type, business hours, rental days per week, leeway minutes, and first/last day cutoffs
  • 11 rate definition presets replicating industry-standard RMS engine types (Daily Rate, Daily Multiplier and Factor, Hourly Rate, Hourly Multiplier and Factor, Half Hourly Rate, Weekly Rate, Monthly Rate, Monthly Multiplier and Factor, Fixed Rate, Fixed Rate and Factor, Fixed Rate and Subs Days)
  • Presets seeded on install and re-runnable from the Database Seeders admin panel
  • Rate resolver — resolves the highest-priority product rate by store, transaction type, date validity, and priority, with per-product tagged caching invalidated on write
  • RateTransactionType enum (rental, sale, service) for product rates, distinct from the int-backed stock transaction type

Config Schema System

  • Reusable App\Support\ConfigSchema subsystem for dynamic, schema-driven configuration forms — Field hierarchy (Text, Number, Decimal, Toggle, Select, Time), GroupField, RepeaterField, Schema, and Section
  • Conditional field visibility evaluated server-side, validation rule generation for visible fields, and sanitisation that strips hidden and disabled-modifier values

Persistence

  • rate_definitions table — strategy, base period, JSONB enabled modifiers and config, preset slug, and self-referential clone tracking
  • product_rates table — integer minor-unit price, transaction type, currency, optional store scope, validity dates, and priority
  • RateDefinition and ProductRate models with factories, schema definitions, and a Product.rates() relationship

Actions & DTOs

  • Seven rate actions — Create/Update/Delete/Duplicate RateDefinition and Create/Update/Delete ProductRate — each authorising via gate, wrapping writes in a transaction, firing audit events, and dispatching webhooks
  • Rate DTOs for input validation and API serialisation, including RateBreakdownData (decimal-string money, structured line items)
  • Config validation on create/update against the composed schema; disabled-modifier configs stripped on update
  • Non-blocking product rate overlap detection surfaced as a warning

API

  • Rate definition endpoints — full CRUD plus duplicate, with Ransack filtering by strategy, base period, and preset
  • Nested product rate endpoints under products/{product}/rates, with an overlapping_rate_ids warning in the response meta
  • POST products/{product}/calculate_rate — returns a rate breakdown, or a zero-priced breakdown with meta.resolved: false when no rate is configured
  • Rate engine metadata endpoints — rate_engine/strategies, /modifiers, /presets, and /schema for external form builders
  • rates:read / rates:write Sanctum abilities and the rates.* permission group

Admin & Catalogue UI

  • Rate Definitions admin panel under Admin → Pricing — list of presets and custom definitions with in-use counts, schema-driven create/edit form with preset picker and From Scratch option, and duplicate support
  • Server-driven config form — choosing a strategy constrains base periods, enabling a modifier reveals its tier/range table, and fields show/hide based on other values
  • Config-schema Blade field partials styled to Signals conventions, including bordered repeater row cards with add/remove/reorder controls
  • Product Rates tab on the product detail page with a dedicated rate assignment form, modal-confirmed removal, and an overlap warning banner
  • New Pricing group in the admin sidebar, landing grid, and group switcher

Documentation

  • Platform documentation page for Rate Definitions
  • API documentation page for rate definitions, product rates, calculation, and metadata, including the RMS rate engine mapping table

Changed

  • AppServiceProvider registers the rate engine registry as a singleton with the three strategies and two modifiers
  • PermissionSeeder adds the rates.* permission group, assigned to the Admin and Operations Manager roles
  • CompleteSetup and DatabaseSeeder seed the rate definition presets on install

0.3.0 — Products, Activities & Inventory

Added

Products Module

  • Product model with ProductType enum (Rental, Sale, Service, Loss & Damage), StockMethod enum (Bulk, Serialised), and HasSchema integration
  • Products list page with type filter chips (Rental/Sale/Service), archive filter (Active/Archived/All), per-type counts, and data table with column sorting, filtering, and bulk selection
  • Product detail page — 3-column layout with description, quick stats, product group, tax & revenue (left), product details, pricing, activity timeline placeholder (center), product image, key attributes, tags (right)
  • Product create and edit forms — 2-column layout with basic info, identification, stock, pricing, tax & revenue, options, and custom fields panel
  • Product merge — select two products, side-by-side comparison modal, migrates stock levels, accessories, attachments, and custom fields to primary; archives secondary
  • Product icon upload on show and edit pages
  • Product tabs — Overview, Stock, Accessories, Custom Fields, Activities, Files
  • Product API endpoints — full CRUD with Ransack filtering, custom view support, and RMS-compatible response shape
  • Product policies for view, create, edit, and delete permissions
  • Product column registry for custom views with 11 columns and 6 defaults
  • Custom views for products — All Products, Rental Products, Sale Products, Active Products, Inactive Products

Product Groups

  • Product group model with hierarchical structure and product count
  • Product groups list page with data table, search, and product count column
  • Product group detail page — sidebar with group details, main area with products-in-group data table
  • Product group create and edit forms
  • Product group API endpoints — full CRUD with Ransack filtering
  • Product group policy for view, create, edit, and delete permissions
  • Delete product group action with audit trail and webhook dispatch
  • Navigation — mega menu link, command palette commands

Stock Levels

  • Stock level model with StockCategory enum (Bulk, Serialised) and HasSchema integration
  • Stock levels list page with data table and search
  • Stock level detail page — 2-column layout with details, quantities (with available calculation), dates, and transactions panel
  • Stock level tab on product pages — scoped data table showing stock for the product
  • Stock level API endpoints — full CRUD with Ransack filtering and custom view support
  • Stock level policies and column registry
  • Custom views — All Stock Levels, Serialised Stock, Bulk Stock

Stock Transactions

  • Stock transaction model with TransactionType enum — 11 types: Opening Balance, Increase, Decrease, Buy, Find, Write Off, Sell, Return, Make, Transfer Out, Transfer In
  • Signed quantity calculation via quantitySign() — negative for reductions (Decrease, WriteOff, Sell, TransferOut), positive for additions
  • quantity_move accessor on model — signed quantity for display
  • Manual transaction creation restricted to Buy, Find, Write Off, Sell, Make (system types like Opening, Transfer are API-only)
  • Inline transaction form on stock level show page — type selector, quantity, date, description
  • Automatic quantity_held update on stock level when transactions are created
  • Stock transaction API endpoints — RMS-compatible nested routes (products/{product}/stock_levels/{stock_level}/stock_transactions) with index, show, and store
  • RMS-compatible response shape with transaction_type, transaction_type_name, quantity, quantity_move, manual flags

Activities (CRM)

  • Activity model with polymorphic regarding relationship (Member, Product, StockLevel), owned_by user, and participants many-to-many with members
  • ActivityType enum — Task (1001), Call (1002), Fax (1003), Email (1004), Meeting (1005), Note (1006), Letter (1007) — mapped to industry-standard type_id integer codes
  • ActivityStatus enum — Scheduled (2001), Completed (2002), Cancelled (2003), Held (2004)
  • ActivityPriority enum — Low (0), Normal (1), High (2)
  • TimeStatus enum — Free (0), Busy (1) for calendar blocking
  • Activities list page with type filter chips (Task/Call/Meeting/Email/Note), status filter chips (Scheduled/Completed/Cancelled/Held), per-type and per-status counts
  • Activity detail page with badges (type, status, priority), details panel, regarding link, participants list, quick actions (Complete, Edit, Delete)
  • Activity create and edit forms — 2-column layout with basic info, classification, schedule (left), assignment and entity picker (right)
  • Member activities tab — scoped data table with "New Activity" button pre-filling regarding_type=Member
  • Product activities tab — scoped data table with "New Activity" button pre-filling regarding_type=Product
  • Stock level activities tab — scoped data table with "New Activity" button pre-filling regarding_type=StockLevel
  • Complete activity action — sets status to Completed, completed=true, fires audit event
  • Activity API endpoints — full CRUD plus POST /activities/{id}/complete, Ransack filtering, custom view support
  • RMS-compatible API responses — type_id, status_id, activity_type_name, activity_status_name, time_status_name, participants array, nested regarding and owner objects
  • Activity permissions — access, view, create, edit, delete, complete — assigned to Admin, Operations Manager, Sales, and Read Only roles
  • Activity column registry for custom views with 11 columns and 7 defaults
  • Custom views — All Activities, Scheduled Activities, Completed Activities
  • RMS type mapping via Activity::resolveRegardingType() and Activity::shortRegardingType() — stores full class names in DB, exposes short names (Member, Product, StockLevel) in API responses

Accessories

  • Accessory model linking products to other products with quantity
  • Accessories tab on product pages
  • Accessories API endpoints — index, store, destroy nested under products

Navigation & Search

  • Activities mega menu link under CRM > Engagement with active state
  • Product Groups mega menu link under Resources with active state
  • Command palette — Activities, Product Groups, Stock Levels navigation commands; New Activity, New Product, New Product Group create commands
  • Global search — activities searchable by subject (permission-gated), results displayed in command palette with calendar icon
  • Search results for products, stock levels, and product groups in command palette

Schema & Services

  • Schema definitions registered on 14 Phase 1 models (Address, Country, Currency, ExchangeRate, Email, Phone, Link, Attachment, User, ActionLog, Webhook, CustomView, TaxRate, TaxRule)
  • Two-tier caching on SchemaRegistry — L1 in-memory + L2 Redis with tag-based invalidation
  • Schema Discovery API — GET /api/v1/schema and GET /api/v1/schema/{model} for introspecting field metadata
  • VisibilityRuleEvaluator — 11 operators (equals, not_equals, contains, in, gt, lt, etc.) with AND logic
  • AutoNumberService — atomic increment, preview, and reset for custom field auto-numbering
  • Formatter::money() and moneyDecimal() — brick/money string-based formatting without float conversion
  • EnforceSessionTimeout middleware — settings-driven idle session expiry

Security

  • SecuritySettings enforcement for login rate limiting (max_login_attempts, lockout_duration)
  • FileService hardened — S3 path changed to entity-organised structure, scan_status default changed to pending, Storage::put() failure checks added
  • Icon thumbnail size corrected from 400px to 150px

Changed

  • Member activities tab replaced placeholder with scoped data table and action buttons
  • Custom view description column added to custom_views table
  • Attachment scan_status default changed from clean to pending
  • AppServiceProvider cleanup — removed unused Relation import after morph map evaluation
  • Search controller expanded to cover products, stock levels, product groups, and activities alongside members
  • Command palette expanded from 22 to 30+ static commands with product, activity, and product group entries

Quality

  • 227 new/modified files, 17,200+ lines of code added
  • 130+ new tests across enums, models, policies, actions, API controllers, column registries, Livewire components, and search
  • PHPStan level 6 — zero errors across all new code
  • Pint formatting enforced on all changes
  • Documentation pages for Activities (platform + API)

0.2.0 — People & Places

Added

Members Module

  • Members module — universal entity for contacts, organisations, venues, and users
  • Members list page with search, type filter chips, column sorting, column filtering, bulk selection, and archive/restore
  • Member detail page — CRM-style 3-column layout with left sidebar (avatar, quick actions, key contacts, account details), center content (stat cards with sparklines, AI recommendations, activity timeline), and right sidebar (customer health score, health factors)
  • Member create and edit forms — 2-column layout with membership type, status, description, locale, currency, tax class, and conditional org/contact sections
  • Contact detail management — addresses, emails, phones, and links with primary flags and type classification
  • Member relationships — link contacts to organisations with relationship type labels
  • Member archive and restore — replace delete with soft-archive, restore from archived view, archive filter chips (Active/Archived/All)
  • Member merge — select two same-type members, side-by-side comparison modal, migrates all polymorphic relations (addresses, emails, phones, links, attachments), relationships, custom fields, and memberships to primary; archives secondary
  • Member profile icon in page header — visible on all member pages, white box with border and shadow, initials fallback
  • Phone country code — country_code column on phones table, flag emoji picker with searchable country dropdown and dial code display, international format on information page
  • Searchable country combobox on address form — replaces plain <select> with x-signals.combobox for 246 countries
  • what3words geocoding on address form — forward lookup (three words to coordinates), Nominatim geocode from address fields, interactive Leaflet.js map with draggable marker
  • Command palette with live member search — triggered by / or Cmd+K, 22 static commands plus live search with type badges and status indicators
  • Keyboard shortcuts on member pages — e to edit, n to open New dropdown
  • Member API endpoints — full CRUD for members, addresses, emails, phones, links, and relationships
  • RMS-compatible member API responses — active flag, membership object, type-specific fields, icon object, child/parent members
  • Auto-create User-type member on user creation via InviteUser action
  • BackfillUserMembers command for existing users

Custom Fields

  • Custom field groups — create, edit, reorder, and delete groups that organise custom fields
  • Custom fields admin — 16 field types (Text, TextArea, Integer, Decimal, Boolean, Date, DateTime, Time, Select, MultiSelect, URL, Email, Phone, Colour, Currency, Percentage, RichText) with validation rules, default values, and list-backed dropdowns
  • Custom field values on member detail pages, grouped by field group
  • Custom field enforcement — default values applied on creation, is_required validated before persistence, is_searchable and is_active enforced in queries
  • Custom field multi-value support for multi-select fields
  • Auto-number sequences for custom field auto-numbering
  • Default custom_fields in member API responses — RMS-compatible, no explicit ?include=customFieldValues required
  • Custom field API endpoints — CRUD for groups and definitions

Custom Views

  • Custom views system — saved list configs with columns, filters, sort order, and visibility levels (personal, shared, role-restricted)
  • View builder Livewire component — create and edit custom views with column picker, filter builder, and sort configuration
  • Column registry — schema-driven column definitions for DataTable, custom view validation, and API field selection
  • DataTable custom views integration — view selector dropdown, filter/sort application from saved views
  • Custom views API endpoints — full CRUD

Multi-Currency

  • Currencies table (ISO 4217) with CurrencyService for conversion
  • Exchange rates with effective dates — create, update, delete via admin and API
  • Currency and exchange rate API endpoints
  • Currency seeder with ISO 4217 data

File Attachments

  • Polymorphic attachments table with S3 signed URLs and virus scanning support
  • File service — S3/public disk abstraction with signed URL generation, icon upload, and thumbnail creation
  • Icon upload Livewire component
  • File upload modal on member pages
  • Attachment API endpoints

Reference Data & Lists

  • Lists (reference data) — create and manage named lists with hierarchical values, system/non-system flags, and active/inactive status
  • Built-in system lists seeded on install: AddressType, EmailType, PhoneType, LinkType
  • Additional list categories: Lawful Basis Type, Location Type, Rating, Invoice Term, Locale, Currency
  • Countries admin page — browse, search, and toggle active status
  • List API endpoints — CRUD for list names and list values
  • Countries API endpoints — read-only index and show

Tax

  • TaxCalculator service — resolves tax rules by organisation + product tax class matrix with fallback to defaults, bcmath precision, currency-aware decimal formatting
  • Tax classes — product and organisation tax classifications with default designation
  • Tax rate and tax rule API endpoints — full CRUD with Ransack filtering
  • Tax rate and tax rule admin pages — create, edit, delete with priority ordering
  • Tax class API endpoints — CRUD for product and organisation tax classes

Authorization & Security

  • Model policies for 10 domain models — Member, Store, CustomField, CustomFieldGroup, ListName, OrganisationTaxClass, ProductTaxClass, Webhook, ActionLog, EmailTemplate
  • RoleLevel enum — hierarchical role ordering for authorization comparisons
  • Policy traits — AuthorizesByPermission and ChecksStoreAccess extracted from 8 policies
  • Store scoping via StoreScope global scope and HasStoreScoping trait — request-scoped via Context facade, Octane-safe
  • Cost visibility trait — HasCostVisibility with fail-closed design, explicit user parameter for queue contexts
  • Blade permission directives — @area, @action, @costs for template-level authorization

Schema & Query Engine

  • Field Registry and Schema Engine — HasSchema contract, SchemaBuilder, SchemaRegistry for unified field metadata across core, computed, and custom fields
  • Ransack filter enhancements — matches (regex), start, end, present, blank predicates; relationship filtering; custom field EAV filtering via cf. prefix; per-token rate limits
  • Reusable applyIncludes() in FiltersQueries trait with $defaultIncludes and $allowedIncludes properties

Admin & Settings

  • Admin Integrations settings page — encrypted API key storage for what3words, Google Maps, and future third-party services
  • Feature profiles — Full, Lite, Warehouse, Services presets for module configuration
  • Setup wizard improvements — infrastructure checks, branding step, module selection, feature profile presets
  • Admin seeder management page — run seeders from the admin panel
  • Getting started checklist on dashboard — tracks setup completion progress

Infrastructure

  • Reusable DataTable Livewire component — configurable columns, sorting, filtering, pagination, row selection with shift-click range, bulk actions, and event-driven refresh
  • CustomFieldCopier service — copies custom field values between entities when field name + type match
  • Permission registry validate() method for reusable permission validation
  • Action log export job — async CSV export with date validation, failure notifications, and retry configuration
  • Social promo pages — LinkedIn (1200x627) and Instagram (1080x1080) format

Quality

  • PHPStan upgraded from level 5 to level 6 — 168 generic type annotation fixes, zero errors across 618 files
  • 7 new migrations: currencies, exchange rates, attachments, custom views, custom view roles, user view preferences, phone country code
  • 100+ new tests across actions, services, Livewire components, DTOs, and pre-existing coverage gaps
  • Documentation pages for Members, Custom Fields, Lists, Tax Classes, Countries, Currencies, Exchange Rates, Attachments, and Custom Views

Changed

  • Member show page redesigned as 3-column CRM-style layout with stat cards, AI recommendations, and activity timeline
  • Member form redesigned as 2-column layout with conditional org/contact sections and list-value dropdowns
  • Members list bulk action changed from delete to archive with confirmation modal
  • Row actions changed from delete to archive/restore based on member state
  • Admin sidebar expanded with Data section, Tax section, and Integrations under Preferences
  • Demo data seeder updated with sample members, contact details, custom fields, lists, and tax classes
  • RansackFilter uses grammar-wrapped column quoting for SQL safety
  • CustomFieldSerializer uses batch loading for ListOfValues fields (eliminates N+1 queries)
  • ExportActionLog validates date filters and notifies users on failure
  • AnonymiseMember uses refresh() instead of fresh() for null safety
  • PermissionRegistry centralises permission validation (removed duplication from CreateRole/UpdateRole)
  • UpdateMemberData membership_type changed from ?string to ?MembershipType enum
  • MergeMember action wraps all operations in DB::transaction with orphan cleanup
  • ArchiveMember action wrapped in DB::transaction for atomic state changes
  • What3WordsService adds HTTP timeouts (10s) and logging on all failure paths
  • MergeModal catches exceptions with user-friendly flash messages instead of raw 500 errors

Fixed

  • SQL injection vector in RansackFilter matches predicate — column names now safely quoted via query grammar
  • Relation filter column names validated against [a-z_][a-z0-9_]* pattern to prevent injection
  • ILIKE wildcard characters escaped in SearchController to prevent pattern injection
  • TaxResult decimal accessors now respect per-currency minor unit exponents (JPY, BHD, KWD, etc.)
  • TaxCalculator uses pure bcmath chain — eliminated float intermediate that could cause precision loss
  • StoreScope replaced static mutable state with request-scoped Context facade (Octane-safe)
  • HasCostVisibility throws LogicException when $costColumns property is missing (fail-closed instead of fail-open)
  • BackfillUserMembers wrapped in DB::transaction with per-user error handling
  • Gate::authorize('members.view') permission check added to SearchController
  • RestoreMember guards against restoring non-deleted members
  • Migration logs warnings for unresolvable ListOfValues during type code realignment

0.1.0 — Initial Alpha

Added

  • Infrastructure install wizard (signals:install) with interactive configuration for PostgreSQL, Redis, S3, and Reverb
  • Web-based setup wizard for company details, stores, feature profiles, branding, and admin account creation
  • CLI setup command (signals:setup) as terminal alternative to web wizard
  • Status command (signals:status) for checking infrastructure connection health
  • Livewire authentication system with login, forgot password, email verification, and profile settings
  • Two-factor authentication (TOTP) with recovery codes, manageable from Settings → Profile
  • Composite dashboard with branded design system and KPI cards
  • Documentation system with markdown rendering, full-text search, and three-column layout
  • API documentation auto-generated via Scramble at /docs/api
  • Demo data seeding (signals:seed-demo) for evaluation and testing
  • Landing page with technical blueprint aesthetic
  • Non-interactive mode for CI/CD deployments
  • System admin panel with settings management (Company, Stores, Branding, Modules)
  • User management with invite, deactivate, reactivate, password reset, and ownership transfer
  • User edit page with role assignment and authorization checks
  • Role management with system roles (Admin, Manager, Operator, Viewer) and custom role creation
  • Role edit page with multi-user assignment
  • Permissions reference page showing all registered permissions grouped by domain
  • Security settings page for password policies, session timeouts, login lockout, and 2FA enforcement
  • Email settings page for SMTP configuration with connection testing
  • Database seeders admin page showing seeder status with run controls
  • Permission and role seeders integrated into default DatabaseSeeder
  • Branding colours (primary and accent) applied across the UI via CSS custom properties
  • Invitation system with signed URLs and accept-invitation flow
  • REST API (v1) with Sanctum bearer token authentication and scoped abilities (resource:action)
  • API token management — create, list, and revoke personal access tokens with granular ability scoping
  • Ransack-compatible query filtering engine with 18 predicates (_eq, _cont, _lt, _in, etc.) and sort support
  • API endpoints for users, roles, settings, action logs, and system health
  • Webhook system — register URLs with event subscriptions, HMAC-SHA256 signed delivery, exponential backoff retry
  • Webhook management API — create, update, delete, toggle, and view delivery logs
  • Automatic webhook dispatch on user, role, and settings changes
  • Auto-disable webhooks after 18 consecutive delivery failures
  • ForceJsonResponse middleware ensuring all API responses return JSON
  • EnsureActiveUser middleware blocking deactivated users from API access
  • Rate limiting on API routes (60 requests/minute per user)
  • API documentation pages for authentication, webhooks, and overview

Changed

  • Moved symfony/yaml and livewire/volt to production dependencies for documentation rendering
  • Updated documentation URL to docs.signals.rent
  • Header navigation bar and mobile sidebar use brand primary colour from settings
  • Search input and focus states in header use brand colour tokens instead of hardcoded values
  • Auth views updated to use signals component library (s-* classes)
  • Component library expanded with 17 new components and s- prefix convention