current-state
Stripe-Hosted aegi Subscriptions
- aegi organization subscriptions now use Stripe Checkout for creation and a dedicated verified Stripe Customer Portal configuration for all later plan, cadence, payment, invoice, cancellation, and renewal actions.
- The Billing page is a compact mirrored-state, seat/storage, and billing-email surface; duplicated plan comparison and in-app subscription controls have been removed.
- Webhooks remain primary for entitlement transitions. Cadence and Stripe current-period end are mirrored separately from app trials and cancellation dates, and unknown Prices fail closed without discarding the last confirmed tier.
- Agency-operated Stripe Connect billing remains a separate product domain.
Stripe Client Billing Foundation
- Added backend runtime config for a dedicated Stripe-powered client billing provider while keeping it separate from Stripe-hosted aegi SaaS billing.
- Added Convex schema, connection state, webhook event tracking, Connect callback routing, Stripe Node actions, and webhook sync scaffolding for org-operated client billing.
- Added app wiring for org billing to connect/reconnect Stripe, refresh connection state, provision and sync Stripe customers from client accounts, and open connected-account billing portal sessions.
- Added first Stripe-native invoice and subscription operations on top of that foundation, including draft invoice creation, invoice finalize/send/void flows, subscription create/update, and subscription cancel/reactivate actions from the org billing workspace, invoice/subscription detail routes, and client billing tab entry points.
- Added a read-only existing-account Stripe scan and dedicated review workspace with safe matching rules, explicit link/create/ignore decisions, resumable history import, duplicate-link enforcement, and aegi metadata attachment.
- Added granular connection/invoice/refund permissions, emailed session step-up, persisted financial operation IDs/results, and immutable audit events for sensitive finance work.
- Expanded webhook finance coverage with an unmatched-event repair queue, rotating Stripe reconciliation, dispute/refund/credit/tax-ID mirrors, real account tax-ID selection, and an auditable revenue-recovery checklist.
- Remaining follow-up is the next delivery slice: remaining collaboration closeout depth, richer time reporting polish on top of the now-landed approval and lock workflow, and smaller residue cleanup on top of this live Stripe layer.
Current State
Repo Reality
This repo is already beyond the first real org client-work phase.
The product shape in code today is:
orgis the live agency staff workspaceportalis the live client-facing workspace for account context, delivery visibility, billing visibility, files, activity, and supportplatformalready has operator and directory infrastructure, but full multi-agency operations are still a later phaseTodayis the role-neutral org home and daily execution surface, organized into deterministicNeeds you,Today, andContinuelanes for actionable workflow, local-date commitments, and resumable work;Overviewremains the separate organization-health and intervention surface, and valid deep links remain authoritative- the org shell now includes durable personal/team reminders with linked-record context, due notifications, Today and Calendar projections, compact shell-first quick views, explicit edit dialogs, and durable reminder detail routes
- manual calendar events are now user-scoped Convex records rather than browser-only data, with one-time import and cleanup of legacy local events; they support daily, weekly, monthly, and yearly recurrence with intervals, selected weekly days, date/count/never endings, and occurrence-versus-series editing and deletion, while Calendar, Today, and the shell quick view expand only the date windows they consume
- active timers are Convex-authoritative across devices and tabs; the client keeps only in-memory optimistic state and performs a one-time import and cleanup for legacy browser timers
- pinned projects now act as compact resume surfaces: their on-demand previews lead with open blockers and viewer-first ranked tasks, allow inline completion and exact-context timer start, expose task creation, and keep deeper reporting on the full project route
- the desktop org shell now has a persisted per-user, per-organization client focus before Search; focused mode scopes cross-client operational pages and quick views while preserving personal/clientless work, leaves administration and curated pinned work organization-wide, locks ownership reassignment, guards out-of-focus detail routes, opens Clients and Portals directly as the focused client's singular workspaces, and presents portal creation when that client has no portal yet
- client detail is now the relationship/account home—Overview, People, Portal, and Activity—rather than a duplicate set of operational feature workspaces; legacy client-feature URLs preserve bookmarks by activating that client focus and canonicalizing to the corresponding global workspace
- org members and portal users now share a source-linked communications layer across tasks, client requests, file requests, project feedback, shared files, and agreements; thread history is shared among eligible participants while unread/archive state remains personal, current access and portal source visibility are revalidated on every channel, and signed recipient-bound email aliases can route verified replies back into the same conversation
The monorepo and route boundaries are no longer just starter scaffolding. The app and backend already carry a real client-work slice.
What Has Shipped
Convex backend refactor and hardening
- The first seven backend-refactor phases are complete. They split oversized runtime entrypoints, domain helpers, webhook projections, portal workflows, and schema ownership without changing behavior.
- Phase 8 is active because file and namespace ownership is not yet fully
normalized. It is replacing historical buckets such as
clientBilling,workspace,premium, and broadaccess/libareas with concrete, consistently named aggregate families. The inventory and target map live indocs/dev/architecture/convex-phase-8-layout-plan.md. - Phase 8.1 has moved the connected billing catalog implementation and its
tests to
billing/catalog, with shared Stripe connection, durable-write, webhook parsing/order, and organization-capability primitives moved to neutral owners. Existingorg/clientBilling*registered paths remain stable while the org files act as authorization/runtime adapters. - Phase 8.1 has also moved the connected billing-customer aggregate to
billing/customers: billing-profile persistence and contracts, customer context/activity, customer and payment-method provider orchestration, and customer webhook projection. Organization authorization remains injected by the stableorg/clientBilling*adapters, and provider-only customer tests now live undertests/providers/stripe. - Phase 8.1 has moved the complete invoice aggregate to
billing/invoices: invoice actions and settlements, exact-once tracked-time allocation, record detail/sync/context, invoice contracts and payment cache, and atomic invoice/payment webhook projection. Registered paths, authorization, provider-write checkpoints, and action/webhook transaction boundaries remain unchanged. - Phase 8.1 has moved the complete quote aggregate to
billing/quotes: draft and lifecycle actions, expiring one-time PDF tokens, record detail/sync/context, contracts, and atomic quote webhook projection. Registered paths, revision lineage, exact finance confirmations, durable provider checkpoints, and webhook repair boundaries remain unchanged. - Phase 8.1 has moved the complete subscription aggregate to
billing/subscriptions: creation and pricing changes, discounts and trials, schedules and lifecycle controls, record detail/manager context, contracts, and atomic stale-aware webhook projection. Shared invoice/subscription billing-profile state now lives atbilling/projectionState.ts; registered paths, organization authorization, finance step-up, and action transaction hops remain unchanged. - Phase 8.1 has moved connected-account transaction reads to
billing/transactions: provider-read orchestration, normalization/deduplication, commercial-record correlation, response contracts, invoice-payment cache integration, and finance-event webhook projection. Registered Node/webhook paths, viewer authorization, org audit ownership, and repair atomicity remain unchanged. - Phase 8.1 has moved the Stripe Connect account lifecycle to
billing/stripeConnect: connection state and generation pinning, OAuth issuance/callback/cleanup, account claims, switching and revocation, lifecycle actions, contracts, and connection webhook projection. Atomic organization finance authorization now lives underaccess/org; stable registered paths remain authorization, HTTP, and runtime adapters until Phase 8.2. - Phase 8.1 has moved Stripe customer imports to
billing/customerImports: bounded discovery, deterministic matching and review, revenue-recovery acknowledgement, generation-pinned apply leases, idempotent client/profile linking, durable metadata writes, and resumable billing-history synchronization. Org visibility, authorization, audit policy, and stable registered paths remain in the audience adapters. - Phase 8.1 has moved scheduled Stripe reconciliation to
billing/reconciliation: oldest-first connection rotation, starvation-resistant profile selection, generation-pinned history projection, monotonic resumable checkpoints, missing-customer tombstones, bounded continuations, and operations-notice policy. Registered cron and function paths remain unchanged; webhook-repair action orchestration now lives withbilling/webhooksinstead of the reconciliation aggregate. - Phase 8.1 has completed connected-write finance safety under
billing/financialOperations: exact confirmations, actor/request/account reservation, provider leases and idempotency keys, durable checkpoints, conservative failure classification, idempotent projection, completion audits, recovery retention, and safe unresolved-operation visibility. Organization and portal authorization now live under their neutralaccess/*owners and are revalidated inside the atomic ledger mutation. - Phase 8.1 has completed connected-account webhook infrastructure under
billing/webhooks: signed transport policy, atomic projection/receipt/repair processing, exhaustive dispatch, bounded repair scanning and replay, administrator retry/acknowledgement state, and privacy-first payload retention. Stable registered paths, HTTP routes, cron targets, and schema remain unchanged; org search, audit, authorization, and notification policy are injected at the compatibility adapters. - Phase 8.1 has eliminated the remaining cross-cutting client-billing junk
drawers. Billing access policy is under
access/org, commercial project validation underprojects, time contracts and rollups undertime, overview contracts and aggregation underbilling/overview, and catalog, customer, invoice, quote, subscription, Stripe Connect, and provenance contracts live beside their owners. Organization-authorized manager adapters remain in the stable registered surface and call domain builders directly inside the existing Convex transaction. - Phase 8.1 has removed every private
orgsource import from the portal and current organizations/future-tenancy boundaries. Reusable organization authorization, billing lifecycle policy, client scope, project contracts, audit persistence, organization billing identity, plan repair state, and portal search indexing now live under neutral owners. Registered API paths, organization and portal identity isolation, search persistence, and transaction boundaries remain unchanged. - Phase 8.1 now owns every Stripe SDK adapter and transport translation under
providers/stripe. The provider is grouped by catalog, Connect, customer, invoice, quote, subscription, webhook, shared snapshot, and runtime concerns; no mega-barrel remains. Customer and subscription adapters were split below the backend size threshold, provider-only tests live undertests/providers/stripe, and architecture enforcement prevents Stripe SDK imports or retiredlib/stripe*locations from returning. - The final performance phase used Convex Cloud insights and a complete static
read/write inventory. All 420 runtime direct
.collect()paths are now indexed or backed by indexed scoped-query helpers; whole-table maintenance and bulk-write paths run in bounded continuation transactions. - Selected-client staff now point-read only their assigned client accounts, reducing read amplification and unrelated reactive invalidation.
- Stripe webhook projection, receipt persistence, and repair creation retain their intentional atomic mutation boundary.
Controlled release rollout
- Platform admins can move organization signup through owner-only, private-beta allowlist, and capacity-limited public-beta phases from the audited control plane.
- Convex Auth reserves public-beta capacity before account creation and organization onboarding rechecks the grant server-side, so direct clients cannot bypass the limit.
- Marketing-safe rollout state is public for a future countdown or remaining-slots treatment, while the current marketing experience remains the newsletter/get-notified CTA.
- Stripe-hosted SaaS Checkout and Customer Portal accept configured promotion codes, including a Stripe-configured 100%-off code for selected private-beta agencies.
Production account and organization lifecycle
- Final-access account deletion now revokes authentication, sessions, MFA/recovery state, preferences, notifications, portal grants, and live impersonation/support state while retaining only an anonymized identity tombstone for historical reference integrity.
- Organization owners, admins, and account managers with portal-management capability can open a client-scoped, audited 15-minute portal support session as an active portal contact; tenant and assigned-client boundaries are enforced server-side, the portal shows the active support-session banner, and stopping returns the operator to the source client’s portal-access workspace.
- Staff removal now requires an active in-organization handoff target and atomically reassigns owned clients/projects, open tasks, reminders, and submitted support requests before access is removed.
- Ownership transfer atomically updates organization ownership, both membership roles, owner-derived billing contact, directory state, and audit history.
- Organization closure is now checkpointed and retry-safe across subscription cancellation, custom-domain removal, workspace access cleanup, credential revocation, and archival. Billing changes and ownership transfer fail closed while closure is incomplete.
- Destructive account, member, ownership, and organization actions now use alert-dialog semantics with explicit confirmation and stable progress presentation.
- The operational and deployment contract is documented in
docs/app/product/account-organization-lifecycle.md, including the orphaned-portal-grant repair migration.
Backend data model
packages/backend/convex/schema.ts now includes the first agency client-work tables:
clientAccountsclientContactsprojectsprojectUpdatestimeEntriesclientBillingProfilesinvoiceRecordsquoteRecordssubscriptionRecordsdriveItemsfileAssetsrichDocumentscontractsremindersfileRequestsclientAccessAssignmentsportalAccessGrants
The repo also includes demo-seeding for this slice under packages/backend/convex/dev/seed/clientWork.ts.
Stripe client-billing foundation is now present in the backend too:
organizationStripeConnectionsstripeConnectStatesstripeBillingWebhookEvents
Those tables back the first real agency-operated Stripe layer for:
- agency Stripe account connection state
- OAuth callback state for safe reconnect/return handling
- webhook receipt tracking and idempotent billing sync diagnostics
Org surface
The org dashboard now has live pages for:
clientsas the live client-accounts surfaceprojectstimequotesbillingcontractsdriveportals
Those routes are backed by real Convex functions, not placeholders:
org/clients.ts, backed byclients/accounts/*org/projects.tsorg/time.tsorg/clientBilling.tsorg/clientBillingNodeActions.tsorg/clientBillingWebhooks.tsorg/clientBillingHttp.tsorg/contracts.tsorg/drive.ts
Current org functionality is now workspace-first, not just list-first:
- org navigation, launchpad creation, the quick-action dock, keyboard handling, and the shortcut reference now consume one permission-aware workspace/action registry, so route and create visibility no longer drift between shell surfaces; client visibility is independent from portal-management capabilities
- the org sidebar uses
Billingas the shared user-facing workflow umbrella for contracts, quotes, invoices, subscriptions, catalog, and transactions;Revenueis reserved for earned/financial metrics rather than navigation taxonomy - client detail is the relationship/account home with Overview, People, Portal, and Activity; operational work lives in the corresponding global workspace under client focus instead of being duplicated as client-local tabs
- overview recommendations can still open context-prefilled creation dialogs directly, while open-workspace actions activate the client focus before navigating
- legacy client operational routes—including the former billing route—remain compatibility entry points that activate the client focus and canonicalize to their global workspace
- project detail exposes
Overview,Tasks,Updates,Files,Time,Notes, andBillingas direct tabs without an overflow menu; Files includes file requests, and the duplicate permanent update composer has been removed from the Overview reading surface - client accounts can be listed, created, and edited; agency staff access is managed centrally from Settings → Team rather than from client rows or client detail
- paired org create/edit dialogs now share one field contract across client accounts, contacts, projects, project work, links, reminders, notes, calendar events, contracts, and time entries; immutable owning context stays visible on edit instead of silently disappearing
- client create and edit flows keep the company-level client account as the owning record; adding or editing a person does not silently grant portal access, which is managed explicitly from the portal Access workspace
- projects can be listed, created, opened in a detail workspace, and moved through authored draft and published update flows
- the Projects boundary is centered on organization-owned parent tasks, inline checklist-style subtasks, task-attached Requests the portal user can fulfill, and an Updates timeline containing authored entries plus automatic parent-task completion and reopening events; the Tasks tab is a responsive operational list with aligned assignee, due date, priority, time, and direct-action columns on large workspaces rather than stretched standalone cards, while narrower workspaces fold metadata beneath the title; subtasks are created and edited inline without their own editor, working tab, or timeline events; Today is the cross-project parent-task projection, task-linked time rolls into task and project reporting, and portal-visible parent tasks show their subtasks and approved billable hours in project detail
- the client overview leads with current projects, shows project state and update freshness, and opens Add update directly from each project without first navigating through the project workspace
- pending portal invite links now have visible copy actions on portal overview, client overview, and per-contact portal access rows instead of being discoverable only through overflow menus; a newly sent invite also copies its exact emailed link when clipboard access is available, and later copy actions preserve the still-valid emailed link
- time entries can be listed, logged, summarized by explicit date range, and automatically valued for invoice-authorized viewers through project override → client default rate resolution, with retainer/subscription work excluded, missing rates called out, and currencies kept separate; the active timer is server-backed, single-instance per member and organization, locally mirrored for immediate UI, and atomically converts to an entry on stop; entry capture snapshots billing mode, rate source, hourly rate, and currency so later default changes do not reprice older work; entries may optionally target a project task, capture is complete immediately by default, organizations may opt into staff review from General settings, and explicit
time.view_team,time.review, andtime.managepermissions separate team visibility, review, and management from client-record authority; tracked-time portal totals are a separate per-project opt-in that defaults internal; unbilled tracked time is context rather than an overview warning;All invoicesconditionally shows one current-month period-readiness surface grouped by client and currency, andPrepare invoicecarries the exact range into a separate draft without adding time automatically; invoice creation then shows one confirmableReady to billrecommendation while keeping rate/project breakdowns, custom periods, manual lines, and advanced invoice controls available through progressive disclosure; client-wide invoices can include multiple separately priced time groups while project invoices remain project-scoped; project-linked time inherits the project's billing treatment, projectless billable time can remain separately invoiceable or opt into an active client-wide subscription, and invoice allocation remains exact-once - client billing now runs through the client account model: rich billing identity/defaults can be edited from client create/edit and client detail overview, invoices, quotes, and subscriptions can be authored from the billing workspace and client detail billing tab, and client billing summaries reconcile tracked time with invoice and subscription state
- billing treatment comes directly from real work instead of a client service-model concept: project create/edit owns hourly, retainer, or non-billable time treatment and any hourly-rate override, subscriptions own included minutes and optional project scope, and a client may only provide the initial treatment default for new projects—not a billing classification, allowance, gate, or rule for existing work; linking a subscription never rewrites a project's treatment
- client account is the only required commercial anchor: projects remain optional context, an invoice's optional project association is independent from its approved-time allocation scope, and standalone invoices and subscriptions continue to work without projects or tracked time
- invoice, subscription, quote, and contract detail surfaces now expose explicit commercial provenance blocks so client, project, quote, contract, invoice, and subscription handoffs only appear when the relationship is real
- subscription detail now exposes a richer derived lifecycle view with plan-history and billing-timeline sections based on explicit quote, invoice, and subscription timestamps already in the commercial model
- the backend now supports agency Stripe connection setup, callback completion, billing-portal session generation, and webhook-first Stripe mirror updates for invoices and subscriptions
- Stripe-hosted invoice, quote, and receipt links are now explicit shareable billing artifacts, while billing-portal sessions and account-level controls stay authenticated; portal subscription detail is read-only in-app and customer-authorized changes defer to the connected agency's hosted Stripe portal configuration
- the visible file surface is consistently named
Filesacross org, client, project, and portal UI, while internaldrive*route and data names remain implementation details; the org route now opens on compact client-drive cards plus 12 recent items, switches to indexed Drive-only results during search, and resolves exact files and rich docs by ID instead of expanding every client's contents or relying on a bounded finder cache - drive folders, uploads, and docs now run through the drive model instead of the legacy
documentstable - the client drive workspace now supports nested folders on the live drive backend, richer rich-doc authoring via starter structures and live preview/word-count feedback, and a request backlog that can route upload/doc responses into request-targeted folders
- the drive backend now ships the stronger contributor-upload baseline through
driveAccessLinks,driveExternalUploadSubmissions, org-side create/list/revoke link management, portal-side create/list/copy/revoke link management inside visible shared destinations, explicitexternalprovenance on drive items, portal-hosted upload-link resolution, email-OTP verification, and upload-only outside-contributor submission flow - quotes and contracts now exist as client-account-scoped commercial records and should stay attached to the same client-account commercial spine instead of drifting into separate relationship models; contracts are no longer Drive-backed, own their own authored content, and now carry their own contract-attachment surface for signed PDFs and supporting files
- Drive storage metrics are now explicitly Drive-only and roll up file asset bytes without mixing in contract attachments
- the org overview route is now a real operations dashboard backed by
org/overview:getOrgOverview; its attention queue uses backend-owned recommendation kinds and urgency levels plus typed interventions containing the exact record, current state, next actor, valid action, destination, and affected count; it prioritizes overdue billing and blocked delivery ahead of routine setup gaps and supports causal-record-aware ignore plus restore controls - Today projects typed operational work alongside project tasks, including blockers, time workflow actions, file submissions, support requests, and unhandled client feedback; its Needs you, due-task, and continuing-task queues now share the desktop-density OperationalList structure with project Tasks, exposing direct actions in stable columns while narrow list regions scroll horizontally instead of stacking record fields; viewer/shared responsibility, due work, and continuing work are classified without a universal cross-type score, while explicit team scopes preserve agency operation
OperationalListis now the single org record-list API across Today, Reminders, Clients, Projects, Time, Invoices, Quotes, Contracts, Subscriptions, Portals, Drive, Tasks, People, and Portal Access. It owns aligned, non-stacking desktop tracks at every viewport width, lightweight nested rows, visible routine actions, shaped loading skeletons, and region-scoped horizontal overflow when its desktop-density columns do not fit. Dashboard rankings and timelines now use separator-basedSummaryList; independent capability launchers useActionTile; portal Activity and Files use a portal-owned record-list composition rather than repeated cards.- the composition layer now has an explicit job-to-component contract in
docs/dev/architecture/ui-layers.md, a corrected AlertDialog-backedConfirmDialog, one shared page-stats sheet, consolidated metric cards, removed dead composition exports, a canonicalDetailFieldfor aligned label/value facts, and a strict item-onlySummaryList; the last publicBorderedTableAPI has been removed in favor of a billing-private responsive record shell, and the development-only/dev-tools/ui-patternsgallery now covers the complete composition map, interaction dialogs, skeletons, and loading/empty/warning/error states at wide and narrow widths - the quick-action dock now adds one quiet, client-focus-aware operational cue
without replacing its configured create tools: one viewer-actionable record
opens exactly, several open Today's bounded
Needs youlane, and view-only or teammate-owned work does not create personal urgency - client-authored portal responses now remain actionable until an agency user explicitly handles them, and can be reopened without changing the original response
- the org analytics route is now a launch-ready reporting drill-in with consistent home-surface cards and dedicated delivery, finance, collaboration, and team views; it reports delivery predictability with visible denominators, working-day retainer projections, unbilled-work aging, collection speed with payment-date coverage, currency-safe revenue concentration, receivables, lifetime gross profitability from confidential dated cost inputs, utilization from each member’s weekday schedule and leave, and collaboration performance. Margins are withheld when cost-rate or project-revenue coverage is incomplete.
- org job-switching routes now use real page paths instead of query-param main navigation:
timevstime/timer,invoicesvssubscriptions,clients/[clientId]/*, andprojects/[projectId]/* - the client detail workspace uses real detail subpaths for every client-scoped sidebar feature plus People, Notes, and Activity instead of query-param or nested umbrella tabs
- the client detail overview now surfaces cross-track next actions plus compact 30-day operating trends across access, commercial, delivery, and collaboration instead of acting as a static profile sheet
- the client detail
Peopletab is the canonical human directory for a client company: it keeps identity and simple responsibilities (Primary,Billing) separate from a concise portal-access summary and links to the portal Access workspace for invitations, scopes, and admin permissions - the client detail
People,Projects, andActivitytabs now render as denser operational collections instead of repeated grouped stacks, keeping the client workspace aligned with the rest of the org collection baseline - the org-facing company and people ownership model is correct, and the portal runtime now resolves company access through
portalAccessGrantsso one portal identity can hold multiple client-company grants inside one agency organization - the cross-feature ownership audit is now complete: delivery, commercial, collaboration, access, and portal projection still anchor on
clientAccount, with remaining drift limited to stable registered naming carryover and legacy presentation residue instead of live ownership regressions - org authorization enforcement is now capability-first across org settings and client-work core routes; role checks remain only for ownership invariants and role-transition safeguards
- org staff access now has an explicit client visibility mode on
orgUsersmemberships (allvsassigned), so client-company access is no longer an owner-only special case hidden behind assignments alone - the production V1 staff-access workflow now lives in Settings → Team: each non-owner has a role plus All clients or Selected clients, invitations capture that scope up front, owners remain fixed to All clients, and the Clients surface no longer presents a competing Assigned concept
- delegated team administrators are bounded by role hierarchy and grant-subset checks: they cannot manage themselves, peers, or higher roles, promote to their own rank or above, or grant capabilities they do not hold
- delegated Selected clients administrators can see and grant only client scope contained by their own; broader member scope is redacted, All clients cannot be granted, and role-only edits preserve the target's existing client scope
- Settings is now capability-aware: its navigation shows only usable destinations, its root selects the first accessible section, and direct restricted URLs render consistent denial states without issuing protected page queries
- Team's default rows are now compact and read-first, role/client controls open through Edit access, and read-only team responses redact client names, client scope, and capability details
- members with Selected clients but no active selections receive an explanatory client-scope state across client-owned workspaces; direct inaccessible client and project links use a neutral unavailable state instead of leaking record existence or showing a generic failure
- client-dependent quick actions are consistently suppressed when no client scope exists, while client-work navigation remains available to explain the state; saved workspace pins are re-authorized so revoked access cannot leave stale record names or unusable actions in the shell
- the org sidebar now exposes
InvoicesandSubscriptionsas first-class top-level destinations instead of nesting them under/billing - the org sidebar keeps a single time-records destination that now reads as
Entries, while the timer remains a shell and utility route instead of a peer sidebar workspace - the invoices workspace now uses the secondary bar for fixed invoice-status queues (
All invoices,Draft,Open,Past due,Paid) while search and client filters stay in the local collection toolbar - the entries workspace now uses the secondary bar for fixed approval queues (
All approvals,Draft,Submitted,Approved) while search stays in the local collection toolbar - the entries workspace no longer uses drag-and-drop approval lanes; it now renders as a dense records table with an inline approval dropdown per row
- the clients workspace is now one scalable dense operational list: durable lifecycle scopes (
All,Active,Paused,Inactive,Archived) and stats live in the secondary topbar, search and explicit name/status/recent sorting stay in the collection toolbar, and permitted users can change lifecycle status directly on a row without entering a drag-and-drop mode - the org collection-page baseline is now applied across clients, portals, projects, entries, files, resources, support, contracts, quotes, invoices, subscriptions, catalog, and transactions: each uses the same page shell and borderless toolbar, search remains fixed-width and left-aligned while controls remain right-aligned, the toolbar wraps only when its actual contents no longer fit, and record tables own their single visible border instead of sitting inside a second inset container
- row-level quick actions are now consistently exposed across the main org collections: clients, projects, time entries, invoices, subscriptions, contracts, and drive finder rows all use the same menu pattern for primary open plus adjacent client/project/billing follow-through where that relationship is real
- the org sidebar now includes a dedicated
Portalsworkspace for agency-side client portal operations, with fixed secondary-bar queues (All portals,Live,Unlinked,Disabled,Attention) and per-client portal detail sections for overview, access, workspace, and activity - the org
Portalsworkspace and per-client portal detail now read from the same unified client-account and client-contact access model as thePeopletab instead of inferring portal state from a separate primary-contact-only heuristic - org portal management now behaves as a first-class operations workspace: every collection row is fully clickable and keyboard accessible, queue rows explain the next readiness issue, overview presents a concise readiness checklist, and the Access tab directly owns invitations, resends, revocation, role changes, restoration, and contact disablement
- disabling a client portal is now an explicit confirmed action; it blocks portal sessions without deleting company grants, invitation history, or shared workspace data
- org shell search is now split into two surfaces: the launchpad remains the fast command-palette layer, while a dedicated topbar Spotlight modal now handles higher-limit operational search with pagination, type filters, and result routing across clients, projects, contracts, invoices, quotes, subscriptions, notes, drive items, time entries, and portal access
- the org topbar working set now has explicit production state classes: scoped reload-safe create and note drafts, a scoped active timer, browser-session record tabs, backend-authorized durable record pins, and route-lifetime edit/preview adapters; create-result pin intent stays with its local draft and pin mutations serialize against monotonic backend revisions
- saved-note topbar sessions now reconcile clean local editor state from the canonical Convex note subscription, preserve genuinely unsaved local edits, synchronize pinned-note labels centrally, and scope delayed save completion to the exact initiating session so Notes-page and topbar presentations cannot silently drift or clear one another
- note editing now follows one durable autosave contract across the Notes page, topbar quick editor, and shared compose dialog: valid drafts persist after a short pause and flush on close or switch, new drafts become durable records without closing the editor, rich-text formatting participates in dirty detection, incomplete drafts retain scoped local recovery, queued creation edits cannot duplicate records, and revision-checked backend updates reject stale writers instead of silently replacing newer content; routine Save buttons have been removed in favor of quiet saving, saved, and failure status
- creating a note from the Notes page now inserts a selected draft into the note list and opens it in the existing full page editor, inheriting current project or client context, retaining incomplete local recovery, and promoting meaningful content into one autosaved durable record; compact client, project, timer, topbar, and quick-capture note entry points retain the shared composer
- reminder create and edit now compose due dates and times through one local date-time contract: valid zero-padded time inputs, functional draft updates that preserve the latest date and time choices, and the established local
09:00convention for all-day reminders - notification inbox delivery now persists structured localization metadata; deterministic org and portal notification families, destination actions, billing plan/status values, and default reminder copy render from the English, Spanish, French, and Italian catalogs, while user-authored content and deliberate platform-operator technical notices retain their source text
- custom-domain organizations can now provision and verify a client-email sender on their attached domain; client-facing portal invitations, notices, password resets, support replies, and external-upload codes use that sender only while the domain remains verified, attached, and entitled
- org create workflows are dialog-first, including invoices; previews use one full-screen modal interaction model, contract detail stays read-only, and contract create/edit share the same full-screen composer instead of maintaining a second editor implementation
- org search freshness now resyncs dependent search documents when client-facing names change, so related search results do not drift behind renamed client and portal records
- portal settings include a company-local
Peoplesection; it becomes manageable only when the agency enables client-managed access for that company, and portal admins can then invite, pause, restore, or revoke regular members while the agency alone controls portal-admin appointments and workspace scopes - client contacts now use explicit primary, billing, and active responsibilities only; the obsolete four-way contact kind and account-field-generated synthetic contacts have been removed, and portal access remains exclusively grant-owned
- the app Convex API façade now uses generated backend references end to end, with browser ID branding handled by one typed identity adapter instead of a hand-written parallel contract system; a policy test prevents regression
- client billing vocabulary now comes from
@repo/billing, with Convex validators derived from the same state, interval, collection, connection, pause, and coverage constants consumed by app and backend types - all locale-routed billing money output now receives the active locale formatter, and a policy test prevents components from falling back to the default-locale formatter
- client billing shares route-level workspace state, while Stripe implementation is separated behind stable registered namespaces into catalog, customer, invoice, quote, subscription, import, reconciliation, snapshot, connection, webhook, financial-operation, record-sync, and transaction modules
- the Convex structural refactor is complete through Phase 6: the root schema
is a 28-line authoritative composition over domain-owned table maps, the
generated data model is unchanged, every remaining module at the 1,000-line
review threshold has explicit ownership, and surface-owned helpers no longer
remain in the generic backend
liblayer - production Convex is self-hosted: release tooling now clears conflicting Convex Cloud selectors, requires the self-hosted URL/admin-key pair for CLI access, checks the public API plus OIDC discovery and JWKS endpoints, and keeps local production credentials outside Git
- the demo seed now exercises the complete portal information architecture for Northwind: delivery projects and updates, shared files and file requests, open and accepted quotes, signed and review-pending agreements, paid and overdue invoices, portal resources, all four agency relationship-contact roles, lightweight client responses, and an agency support reply
- the demo portal stores Northwind as its preferred company, and the portal session guard replaces stale or unauthorized
clientAccountIdURL state with the resolved accessible company instead of leaving the shell and URL out of sync - portal overview spotlights now preserve exact authorized record identity across projects, shared files and resources, support requests and replies, file requests, project updates, quotes, contracts, and invoices; focused collection records scroll into view and receive visible focus, contract attention opens its review dialog, direct artifacts open directly, and aggregate billing summaries deliberately remain collection-level
- dashboard boot splash readiness is modeled as dashboard initialization across portal, org, and platform; identity, workspace selection, access policy, and persistent shell inputs resolve before the shell appears, with portal feature availability resolved once in the session provider and shared by its navigation and overview
docs/app/development/org-workspace-collection-ux.mdnow defines the org collection taxonomy and secondary-bar decision rules: durable workflow queues for entries and invoices, justified multi-view collections such as projects, pure record lists such as clients and subscriptions, and a global drive finder for cross-client retrievaldocs/app/product/client-drive-spec.mdis now the source of truth for the client drive productdocs/app/development/client-drive-implementation-plan.mdis now the source of truth for the backend schema and migration fromdocumentstodriveItems
Core system relationship check
The product spine is now clear enough to treat as a hard expansion guardrail:
clientAccountis the company-level anchor across org and portalclientContactis the company-local human recordportalUseris the org-scoped external identity, andportalAccessGrantis the company-specific access layer- delivery records (
projects,projectUpdates,timeEntries), commercial records (clientBillingProfiles,invoiceRecords,quoteRecords,subscriptionRecords,contracts), and collaboration records (driveItems,fileRequests) all attach to the client account first and only optionally to a project - the portal should remain a distilled projection of that same client-account truth, not a second operations model with its own parallel business records
- the emerging operating doctrine is now explicit in
docs/app/product/client-operating-model.md,docs/app/product/cross-feature-link-matrix.md, anddocs/app/development/cross-feature-surface-audit.md - the client detail workspace is the intended simple home for one client's world, while top-level sidebar workspaces are the advanced operational projections across the same client-root records
- Files is explicitly the advanced file-operations surface for general collaboration and retrieval, not the universal owner or required entrypoint for every file action in the product
Org visibility and role assumptions
The current org role model is implemented in orgUsers membership records and uses these base roles:
owneradminaccount_managerstaff
Client visibility is scoped in code and presented as a separate axis from role:
- owners have global client visibility inside the organization
- non-owners carry
clientAccessModeonorgUsers:allis presented as All clients and the internalassignedvalue is presented as Selected clients - Selected clients resolves through active
clientAccessAssignments; an empty selected set fails closed with no visible clients - client access controls which client-owned records are visible across the app, while project membership and task assignment remain separate delivery concepts
Current enforcement rules:
- operational actions require the relevant resolved capability as well as client visibility
- team access management is capability-gated and protected by ownership and hierarchy invariants
- role defaults are configured on Settings → Role permissions; the normal Team workflow keeps per-person editing to role and client access
- billing requires both billing capabilities and client visibility
Portal surface
The portal is now a real client-facing workspace, not just the earlier account shell.
Current portal dashboard areas are:
overviewprojectsbillingfilesactivitysettingssupport
Portal client-work is now live through workspace-backed views:
- the portal dashboard shell now uses one responsive navigation renderer, one grant-aware client-account switcher, and a top-right account menu for settings and sign-out; settings no longer competes with client-work destinations in the sidebar
- portal pages now share one dashboard spacing contract: the page shell owns the outer inset, top-border sections stay flush inside it, and fully bordered interactive cards retain their own padding
- client requests now follow a clear intent split: clients ask for help and review help conversations in
Support, while new-work requests and their conversations live inProjects; the agency handles both through oneClient requestsqueue with explicit Help and Project request labels and filters - portal language now treats the selected client workspace and current route as established context: page headers no longer repeat the client name or explain the page back to the user, scoped billing tables omit redundant client columns, and Stripe/database identifiers never serve as client-facing titles, subtitles, detail fields, or copy actions
projectsshows client-visible project status, published updates, approved billable time, and shared file countsprojects/[projectId]gives a project drill-in with explicitly shared open tasks, published updates, shared files, and recent activitybillingexposes the current client billing snapshotfilesexposes shared client-visible files- active portal sessions are now also gated by the linked client account's
portalEnabledflag, so company-level portal disablement turns into real authorization loss instead of just an org-side display toggle
What Is Partial
-
Platform-operated in-app campaigns are now live as a code-authored delivery system: platform admins can preview, launch, pause, and relaunch the localized
welcome_betastory, while organization users receive each delivery once with persistent dismissed/completed receipts and retain manual replay from the account menu. A visual campaign builder, organization-authored campaigns, portal delivery, and guided tours remain explicit non-goals for this pass. -
clientContactsis now a live workflow in the client detail workspace and now owns unified portal access state, while portal-side self-service people management now exists as a company-local access settings surface on top of the multi-company portal grant model. -
Client-level billing detail now lives in the client detail billing tab plus invoice/subscription/quote detail routes; there is intentionally no separate billing-profile detail route.
-
Billing is now a live operational summary plus Stripe-backed write flows for Stripe customer mapping, catalog sync, invoices, quotes, and subscriptions, and subscription detail now includes derived lifecycle context; reporting depth and post-send lifecycle polish are still incomplete.
-
Stripe Connect reconciliation now removes organization-orphaned connection records before contacting Stripe, and demo/full reset paths delete organization connection state so reseeding cannot leave scheduled reconciliation pointed at deleted organizations.
-
Billing relationships are now visible in the main detail routes, and project detail plus client/project time surfaces expose explicit billing context and billing-readiness cues; broader reporting depth beyond the now-landed analytics drill-in is still incomplete.
-
Commercial handoffs now retain direct local quote identity across revisions, contracts, invoices, and subscriptions while preserving Stripe identifiers for reconciliation; webhook projections repair out-of-order outcomes and a resumable migration backfills compatible legacy rows. Client feedback can likewise retain typed, client-validated task, revision, and organization-reply outcomes, and changed feedback reopens automatically.
-
Time tracking is live with explicit
draft → submitted → approved/returnedownership and review transitions, returned-entry reasons, edit-triggered review reset, and approved-entry locking. Billing status is explicit and filterable, manual duration has fast presets, entry durations use a consistent hours/minutes display, and client/project time analytics now separate first-class range controls and billable-mix trends from the detailed breakdown drawer. The organization ledger now uses indexed cursor pagination for date, client, approval, billing, and search views; bulk reviewer actions remain incomplete. -
The primary organization collection routes for clients, projects, contracts, portals, client requests, notes, reminders, time, invoices, subscriptions, and quotes now use cursor pagination with explicit continuation controls. Project list hydration is page-scoped, financial history uses collection-specific indexes, and partial-page counts are no longer presented as global totals.
-
Collection statistics are isolated from loaded-page length: transaction totals traverse complete Stripe charge and customer-balance histories, Today no longer truncates active reminders before computing its queue metrics, and Time statistics follow the selected client, date, approval, and billing scope while text-search mode suppresses unrelated totals.
-
Portal files now surface a request inbox, and clients can answer agency file requests with either uploads or docs that route into the requested destination, then acknowledge reviewed requests with actor and timestamp metadata preserved inside the grant-aware portal scope.
-
The visible org drive language, finder layout, row-level quick actions, request backlog, request-aware destination handling, and request lifecycle now have an explicit lightweight terminal rule: org reviews, portal acknowledges, org closes when no further follow-up is needed.
-
Contributor upload is now live in its stronger form: authenticated org users and authenticated portal clients can mint scoped upload links, portal clients can copy and manage those links from first-class portal UI inside visible shared destinations, outside contributors fulfill them without portal accounts through email OTP, and every submission remains destination-scoped and provenance-labeled.
-
Org drive link management now shows whether each contributor link came from agency staff or a portal client, and both org and portal surfaces support copy or reissue handling for any legacy tokenless link state.
-
Deeper collaboration affordances are still incomplete.
-
Quotes and contracts are already part of the client-work spine, but their future depth should stay attached to billing and legal/commercial workflow instead of becoming parallel CRM or document-management systems; contract attachments should stay on the contract surface rather than falling back into Drive.
Validation Status
Current repo-health validation is materially better than the older planning docs suggested:
- focused backend workspace validation is green, including
tests/org/workspace-summary.test.ts,tests/org/overview-summary.test.ts, andtests/portal/workspace-delivery.test.ts - app package typecheck is green
- backend package typecheck is green
- workspace-wide
pnpm typecheckis green - backend post-wipe smoke validation is green
- app i18n guard and inlang validation were previously green and no recent work reopened those surfaces
One environment caveat is still worth noting:
- in this macOS workspace, the backend premium-operations suite can exceed Vitest's default 5 second timeout, so focused or full backend validation should use a higher timeout (for example
--test-timeout=20000) to avoid false negatives
What This Phase Actually Proved
This phase is no longer about deciding whether the agency product should exist. It already exists as a working org-side and portal-side client-work spine.
What it proved:
- the repo can support real agency client accounts and scoped staff visibility
- the first billing, time, and collaboration surfaces fit cleanly inside the org workspace
- the portal can now expose project, billing, and file state in a believable client-facing way
- the time-to-billing spine can now derive client billing state from tracked work plus invoice and subscription records
- the org billing workspace can now perform the first real Stripe invoice, quote, subscription, and catalog operations without dropping back to manual mirrored records
- org project delivery can now move through draft and published update states without reopening the model
- the schema and seed data are broad enough to support the next detail-layer work without reopening the model
Immediate Constraint
The next work should finish the remaining operational detail and cleanup layer of the existing client-work slice, not invent new product areas.
The repo already has the right first-wave tables. The main gap now is finishing the remaining project, billing-detail, portal-facing, and language/IA coherence work on top of the live client workspace and billing spine.
Current Completion Gate
Use org-workspace-completion-milestone.md as the move-on gate for the current baton.
The short version is:
- dogfood the client-first Drive home, direct document editor routing, and project/general root separation before adding deeper file workflow
- finish org workspace consistency and detail-route IA
- add the analytics drill-in route
- polish and deepen the first real Stripe invoice and subscription workflows already in the app
- keep quotes and contracts anchored as client-account-scoped commercial artifacts inside the same billing and drive model
- keep company, people, and portal access ownership coherent around the model
clientAccount = company,clientContact = person,portal access = state on person, and evolve the access layer toportal identity = org-scoped person,portal access grant = company-specific access - keep portal follow-through coherent with what the org workspace already promises
Do not treat another major product expansion as the next step until that gate is materially clear.