Revido ERP — Plan & Prototype Spec
AI-native internal ERP for a 14-person software agency. Vision prototype, Linear-inspired design language. Generated 2026-05-31.
Revido ERP — System Map (DRAFT v0.1)
Master consolidation of the office-hours session — the overview section of this plan. All companion docs are compiled into this same page (see the left nav / sections below):architecture-decisions.md,design-system.md,information-architecture.md,status-model.md,automation-engines.md,permission-matrix.md,qa-module.md. Every*.mdreference is a clickable in-page link — comment on any of them right here.
0. Mission & Deliverable
- What: an AI-native internal ERP for Revido — a 14-person software dev agency + product studio. Built for ourselves. Single-tenant now, with the door left open (S4) — we design tenant-shaped (an
org_idon every row, per-org config/secrets, clean engine seams) but build single-tenant; a future multi-tenant SaaS / template / extractable microservice becomes a feature project, not a rewrite. Seearchitecture-decisions.md→ S4 / S4-fwd. - THIS repo = a VISION PROTOTYPE. A clickable, role-toggleable, pixel-perfect prototype on seeded fake data whose job is to show the full vision. It is a design/communication artifact, not the foundation the real build sits on.
- The real portal is ALREADY being built in a separate repo (a dedicated dev is on it). The eng "real-build correctness" decisions here are spec guidance handed to that team, not implemented in this repo. See
architecture-decisions.md→ "Gap ownership". - Audience: internal 14-person team (vision + alignment).
- Guiding principle: remove any manual work that can be automated.
1. The core idea
Revido ERP = a small set of configurable engines operating over one project tree, serving two audiences (internal cockpit + client portal) off one data model.
The project tree (the spine)
Project → Milestone → Module → Feature → Task
- PMs/POs define down to Feature; Devs create Tasks.
- Estimation/measurement unit is set per project (Owner/PO at project setup) — two modes:
- Story points (complexity, not time), estimated as ranges — default for retainers; velocity ~30 SP/week/project applies to SP projects.
- Time / hours (also estimated as ranges) for projects that estimate in time — typically older / legacy engagements — paired with a time tracker logging actual hours (feeds hourly billing & profitability).
- A project is one unit or the other; the UI shows SP or hours accordingly. The chosen unit and % complete roll up the tree automatically (pure computation); velocity/burn charts use the project's unit.
- Every issue/task has a background Markdown doc with full info + backlinks to its Feature/Module/etc. — an Obsidian-like knowledge graph.
2. Roles
Internal: Owner · Product Owner (PO) · Dev · Product Builder (Dev+PO) · QA · Sales Client: Admin · Staff
- Table role = default; overridable per project (override grants full target role).
- See
permission-matrix.mdfor the full object × role grid.
3. The Engines (see automation-engines.md)
- Permissions — object-level who-can-do-what (Owner-configured).
- Workflow — stage-gated manual transitions (e.g. QA-only out of "QA Review"); per-role, for projects and CRM (separate).
- Cascade — bottom-up status roll-up; rules on status groups or specific statuses; templates applied per project; fallthrough = leave untouched.
- AI Recommendations — Inbox sources → AI proposes changes → human always approves.
- Event Automations — PR merge → "Merged to Staging"; test runs auto-drive workflow/cascade (pass advances, fail blocks/→Revise).
- Cadence & Check-ins — time-triggered: scheduled/conditional check-ins (standups, project updates) per role → collect in-app → AI compiles to project digests + client-update drafts (human-approved). Owner builds templates, assigns per project. Missed check-ins ding bonuses.
4. Status model (see status-model.md — PROPOSAL, needs approval)
- 5 status groups: Backlog · To Do · In Progress · Done · Cancelled.
- Per-type statuses (Task richest: Backlog→To Do→In Progress→PR Open→Merged to Staging→QA Review↔Revise→Done).
- Cascade keys on groups so differing per-tier statuses still roll up.
5. Modules
| Module | Purpose |
|---|---|
| Dashboard | Role-specific overview — fixed curated layout per role (full spec for all 8 roles in dashboards.md); Owner: utilization, profit, velocity trends, at-risk, employee cost/profit |
| Inbox | Linear-style; ingests Slack/@bot, recall.ai, email, Ybug → AI recommendations |
| Projects | The tree; estimates, scope, velocity |
| Sprint/Retainer planning | Cycle scope (commit next 1–2 wks), velocity, backlog→to-do |
| My Work / Tasks | Personal task list, SP/time logging |
| Time & SP tracking | Per-project unit (SP or time). SP projects: SP "claimed" on done, "earned" after QA (for bonuses). Time projects: estimate in hours (ranges) + a real time tracker logging actual hours — drives hourly billing & profitability |
| QA / Testing | Test cases, suites, runs; linked to Features/Tasks/Modules + PRs; runs drive workflow |
| Sales CRM | Pipeline, deals, SOWs/proposals, own workflow; deal→project conversion |
| Estimates | Modules+Features+SP ranges+cost ranges; client-signable; triggers billing |
| Invoicing | Xero two-way sync; biweekly retainer + fixed-scope; per-estimate billing mode |
| Profitability | Project + team-member grain; employee cost (Owner-only) |
| Client Portal | Approvals, recent shipped, request/reprioritize features, invoices, progress (SP + friendly view) |
| Client Updates | AI-drafted on recurring schedule → human approves → send |
| Knowledge Base | Obsidian-like, MD, backlinks; internal + client-visible docs |
| People / Capacity | Allocation (current + future), assignment, internal user mgmt |
| Staff / Compensation | SP → bonus engine (staff-comp.md, A16): flat $210/tier ladder on delivered SP per 4-week period; live preview + immutable period snapshots; append-only adjustment ledger (clawback / fix-up transfer / correction); leave pushes the window; missed check-ins subtract SP; reference-class anti-inflation. QA = mean(devSP×coverage); PO = sum(project SP) on a PO floor; Deel = contracts + payout system-of-record |
| Time off / Leave | Lightweight request (My Work) → Owner approve (Inbox), managed on a People → Leave admin screen (requests + team away-calendar); approved leave suppresses Engine 6 check-in nudges & excludes the period from "missed" (never dings a bonus); plus an Owner one-tap "excuse" for one-off misses (sick/emergency) and retroactive leave for multi-day. Future Deel integration as upstream source — deferred |
| AI Workflows | Owner-editable templates (ERP planner, Startup planner…) generating project trees |
| Settings / Admin | Roles, workflow/cascade config, integrations, rate cards, CLI/MCP tokens |
| System Activity | Owner-only observability (A15): unified audit feed + integration/job/AI-run history + recent errors (filterable, trace-id linked), per-object History tab, live-tailing log viewer, alerting config. "Everything tracked, no UI for all" — see architecture-decisions.md → A15 |
6. Billing & money
- Engagements: weekly/biweekly retainers + fixed-scope/one-off.
- Estimate signed by client → billing per selected mode: auto-bill · draft-for-approval · manual.
- Rate cards: per-SP or per-hour, with currency; default per-client → deal → project (overridable). Drives client-facing cost ranges.
- Xero: two-way sync (invoices out, payment status back) + SSO-gated Owner chatbot for finance Q&A (no secrets exposed to devs).
- Profitability at project + team-member grain; PO/PB never see money (efficiency/health only).
7. AI surfaces (built later, designed-for now)
- Inbox-driven proposals (tasks, edits) from transcripts/Slack/email/Ybug.
- AI proposes milestones/modules/features via planner workflows (not estimates).
- AI-drafted client updates on schedule.
- Xero finance chatbot (Owner, SSO).
- Everything human-approved; nothing auto-applies.
8. Integrations
- Xero (two-way + SSO chatbot) · recall.ai (transcripts) · Slack (bot + notifications) · Gmail (INBOUND: client requests → Inbox for clients not on Slack; AND CRM email sync) · WhatsApp (client comms in/out — for clients not on Slack/email) · Resend (outbound email) · Ybug (bugs — to be replaced by a custom in-house bug-reporting tool later) · GitHub (PRs ↔ features/tasks/modules, tests in QA module) · Deel (future, deferred — upstream source for Time off / Leave).
- Notifications: Slack + in-app + email + WhatsApp (client-facing).
- Inbox ingestion sources: Slack (@bot), recall.ai, Gmail (client requests), WhatsApp (client requests), Ybug, email → AI proposals.
- Client-channel principle: meet each client on the channel they actually use (Slack / email / WhatsApp), but every outbound message deep-links into the portal — approvals, requests, check-in forms, and updates all resolve in the portal. The standing goal is to build the portal habit, so the channel is the doorbell and the portal is the room. Powers Engine 6 (Cadence & Check-ins) and the Client Updates module.
- CLI / MCP layer exposes the system so devs work via Claude Code; issues' MD docs are first-class.
9. Tech stack (real build — prototype foreshadows it)
- Next/React, Revido Design System (NPM), ShadCN (prototype UI), Supabase, Infisical (self-hosted secrets), Railway/Coolify (hosting), Resend (email), Playwright (E2E).
- Design language: DEFINED — Linear-inspired (near-black canvas, single lavender
#5e6ad2accent, surface ladder, hairlines, negative-tracking display), on ShadCN. See **design-system.md**. (Resolves OV-T2: design prompt has landed.) Fonts substituted Inter/Geist; Revido Design System NPM may override. Seed data still TBD.
10. Prototype build plan (decided)
- Real clickable web app, seeded fake data, role-switcher to view every role's experience.
- Pixel-perfect once design prompt + brand provided; logic/flows complete now.
- Demonstrates: the tree, all 6 engines (incl. greyed-gated transitions w/ tooltips, and the cadence/check-in template builder), client portal vs internal, AI approval inboxes, billing/estimate flow.
11. Detail captured — round 2
Knowledge Base
- Feel = getoutline.com + Obsidian in one: folders + wiki structure and an MD/backlink graph.
- Role-based visibility per folder or per doc.
- Can publish a doc to a specific client, or generate a public link (anyone with the URL).
CLI / MCP (dev workflow)
- Devs work with ERP entities from their terminal, the way they already do with GitHub issues (
gh-style). - Same surface exposed via MCP so Claude Code can read/act on projects, tasks, docs, etc.
AI surfaces (round 2)
- Buttons in the right places to trigger AI flows contextually.
- A place to define AI flows (the templates/orchestrations from automation-engines.md).
- A portal-wide chatbot (Notion-style) that pulls info from anywhere in the portal via MCP, and cites sources (projects, tasks, docs, etc.).
Estimates / Proposals — generation pipeline
- Most often generated from a recall.ai transcript when the salesperson picks a template.
- Salesperson oversees the modules, features, priorities, and milestones (the scope shape — no tasks, no estimates).
- Sent to a PO for estimations (SP ranges).
- Shared with the client (with SP + cost ranges per rate card).
- Estimate is linked to an existing project or a deal.
- On acceptance → one-click convert to a Project.
Main screens
Primary nav: Projects · Billing · Staff · Inbox (plus the modules in §5). **Full navigation / information architecture now specified in information-architecture.md** (grouped rail, role-filtered visibility, project workspace, client portal shell, Cmd-K, route map).
Auth
- Supabase Auth on everything — magic link, Google, password, etc.
- (Plus Xero SSO for the finance chatbot; Infisical for secrets.)
Non-functional
- Scalable data structure is an explicit requirement — model for growth from day one.
Rodrig: "think of these, will add more later" — round 2 is not exhaustive.
Editing, comments, email (round 3)
- Rich text via TipTap everywhere — the same editor powers KB docs and Module / Feature / Task descriptions (one consistent editing component across the whole app).
- Comments everywhere, with a per-comment (or per-thread) option to hide from clients — internal-only discussion alongside client-visible threads.
- Email templates via react.email — built as reusable components (ties to Resend for delivery).
- (Rodrig's note continued: "same for features and …" → resolved: Module/Feature/Task descriptions.)
CRM — full-featured (round 4)
Not a lightweight pipeline — a proper CRM:
- Customizable stages (Lead → Qualified → Proposal → Won/Lost, editable) driven by the Workflow engine (same engine as projects, role-gated transitions — Sales/Owner).
- Lead automations — rules that act on leads (auto-assign, auto-advance, follow-up reminders, stage-entry actions).
- AI workflows on leads — e.g. automatically research a lead (enrich company/contact, summarize, suggest approach) as a proposal for the salesperson.
- Entities: Leads / Contacts / Companies / Deals (Deal links to estimate → one-click project on win).
- Feeds the Estimate/Proposal pipeline (transcript + template → scope → PO estimate → client → convert).
"etc" — Rodrig will add more CRM detail later.
Resolved decisions log
- Roles uniform; project override = full role. Sales view-only on own projects. PO/PB no money.
- Client signs estimates (modules+features, no tasks); drag backlog→to-do = approval; cost ranges shown via rate card.
- Client Staff requests features; Admin approves start.
- Cascade: groups or specific statuses; templates per project; fallthrough untouched; no manual pin yet.
- PR merge → Merged to Staging; test runs drive workflow/cascade.
- Billing per estimate (auto/draft/manual); rate card per-SP or per-hour + currency, client→deal→project.
- AI: planner templates generate tree (not estimates); always human-approved.
- Measurement unit is project-level: Story Points (complexity, ranges, velocity) OR Time (hours, ranges, with a real time tracker on actuals). Legacy/older projects use time; retainers use SP. Billing & profitability follow the project's unit + rate card.
- Engine 6 (Cadence & Check-ins) refinements (2026-05-29): check-in forms use a reusable typed field-builder (shared later w/ QA/intake); compile reads two sources (responses + project activity feed from integrations); send-time in recipient tz + per-check-in response deadline relative to send; an AI quality check (soft author nudge + Owner signal, non-blocking, not bonus-linked); a lightweight Leave record (request→approve) suppresses nudges & excludes OOO from "missed"; Deel as leave source and a custom bug tool replacing Ybug are both deferred.
- Everything is logged — THREE tiers, separated by who reads it (eng review 2026-05-30, A15): (1) Audit log = A8 in two planes — a DB-trigger change-log (guaranteed floor, catches raw SQL/MCP/migrations) + the app-level semantic **
audit_event; Postgres, UI+RLS, long retention. (2) Operational run records** —IntegrationRun/JobRun/AiWorkflowRun/WebhookReceipt, light Owner-only admin UI, ~90d. (3) System logs + traces — structured JSON + OpenTelemetry → external aggregator (Railway/PostHog, swappable), no UI, ~30–90d. One **trace_id** threads all three. Secrets/PII redacted; writes async off the critical path. "Everything tracked, no UI for all." Full spec:architecture-decisions.mdA15. - Engine 6 accountability (CEO review 2026-05-29): missed = automatic, small, transparent-tally penalty (prior call stands); an Owner one-tap "excuse" + retroactive leave flip missed→excused (sick/emergency never dings); chronic thin entries (repeated needs-detail flags over a window) eventually carry a small weight — a single thin entry stays coaching-only. All of missed / excused / quality-flag are derived, audited events (see A8 + A14).
Still open (small, for review)
- Status model approval (status-model.md).
- Project "Completed" — cascade-driven or manual Owner action?
- Seed data volume/shape (Rodrig to provide a base; I help expand).
- Capacity planning depth (future allocation vs current only) — assumed both.
Revido ERP — Architecture Decisions (from /plan-eng-review)
Decisions locked during the engineering review. Source of truth for build sequencing + architecture.
Scope
- D1 — Build sequence: PHASED, spine-deep-first. Phase 1 = the architectural spine fully interactive (project tree + 14-status lifecycle + cascade + workflow gates w/ preconditions + role-switcher proving permissions + dual internal/client view + one AI approval inbox + estimate→project seam). Phase 2 = expand to all remaining modules, reusing proven components. Final prototype still shows everything.
Architecture
- A1 — Four-layer authorization, fixed resolution order:
effective-role-per-project → object permission → workflow gate → visibility. Each a pure, independently testable function. Prototype demonstrates each layer (greyed control + tooltip naming the blocking layer). - A2 — DB is canonical. Rich text stored in Postgres (TipTap JSON); the Markdown/backlink graph is a generated VIEW; CLI/MCP read & write THROUGH the API under the same permissions as the UI. Backlinks are real DB relations. No files-on-disk source of truth (preserves multi-user, client portal, Supabase RLS).
- A3 — Workflow gates carry preconditions (not role-only). A transition is allowed iff (role permitted) AND (required artifacts present). →QA Review needs Loom + preview link (auto from PR/Railway if available) + updated estimate. →Client Review needs client-facing Loom. Prototype shows a blocked transition as a CHECKLIST ("Missing: Loom, preview link"), not just a greyed button.
- A4 — Cascade auto-advances to QA Review; gate relocates to feature/module level. When the last child task is Done, cascade auto-advances the feature into QA Review (option B) — BUT that transition requires a feature-level OR module-level Loom + preview link, enforced via a blocking pop-up ("paste your Loom link / preview link"; empty → button unclickable). So cascade is not blocked and the gate is not bypassed — the precondition simply moves from per-task to feature/module granularity. From QA Review → Deployed, the workflow engine + preconditions + deploy/test events own it. Reopen-after-deploy is a workflow-owned reverse transition; cascade resumes rollup afterward.
- A5 — Estimate = the project tree in Draft. Not a separate copied document. Signing snapshots the SP/cost ranges immutably (for billing/legal) and flips Project Draft→Planned. "One-click convert" = a status flip, not a data copy. Kills Sales→Delivery double-entry.
- A6 — AI recommendations = uniform Proposal-as-diff. One Proposal entity {source, type, target ref, diff, status, actor} for all sources (recall/Slack/email/Ybug); one approval inbox; approve = apply diff, reject = discard, edit-then-apply supported. The portal chatbot is a SEPARATE read/cite surface (MCP).
Code quality
- CQ1 — Single visibility primitive (internal / client / public) attachable to ANY object (project, doc, comment, feature). Evaluated by auth layer (d). Build once, reuse everywhere.
- CQ2 — Config tables + fixed evaluators for all engines (NOT a general rule DSL). Owners edit rules as data rows in a UI. Explicit, testable, no parser. Prototype shows the editors on mock config.
- CQ3 — Exception states modeled now (not happy-path-only): estimate-rejected→revise, client-review-rejected→Revise, deploy-failed→revert, PR-closed handling, cancellation, and reopen-after-deploy SP rule. Default SP-reopen policy: earned SP stays earned; reopened work earns FRESH SP on re-deploy (Owner can flip).
Testing
- T1 — Playwright E2E on the spine hero flows. Per-role render, project-override effective role, gated transitions w/ precondition checklist, cascade rollup, dual-audience divergence, AI proposal apply, estimate sign. P0: visibility leak guard — as Client Staff, internal-only comments/objects are absent. Tests double as the executable spec for the real build. No unit tests on mock data.
Deferred to real build (not prototype blockers) — see TODOs
- Tree storage: adjacency list + recursive CTE or closure table (avoid N+1).
- Rollup denormalization: materialized SP/% columns updated by cascade trigger.
- Cascade recompute: debounced, branch-scoped (avoid fan-out storms).
- Rate-card resolution: client→deal→project precedence, currency, snapshot-at-sign.
- Bonus SP distribution rule across Dev/QA/PO — **resolved into the comp engine (A16,
staff-comp.md):** Dev earns delivered SP directly; QA/PO derive from a (parked) averaging formula, not a per-feature split. Reference-class library bootstrap remains real-build.
Gap Review (3-lens: design · CEO · eng) — folded technical decisions
Surfaced by parallel design/CEO/eng reviewers. Strategic calls handled separately.
- A7 — AI + MCP must obey the auth model (BLOCKER). The RAG chatbot and MCP/CLI surfaces bypass A1/CQ1 unless bound to it. Every embedding chunk carries
{object_ref, visibility, client_id, role-scope}; the retriever runs the auth resolver pre-rank; cited sources are re-checked at render. MCP/CLI tokens are role- and project-scoped, revocable, rate-limited; agentic writes support dry-run and are fully audited. - A8 — Audit log is a Phase-1 entity (BLOCKER). Append-only
audit_event{actor, object_ref, verb, before, after, source(human|cascade|event|ai|mcp), ts}. Every engine writes to it. Cascade provenance ("set by rule R2"), SP claimed→earned attribution, QA KPI clock, escalation pause, and the inbox all READ from it. - A9 — Money correctness. Store integer minor units + ISO currency; one explicit rounding rule; FX snapshot at estimate-sign. Rate cards carry currency.
- A10 — Xero source-of-truth + idempotency. Xero = system-of-record for invoice numbers + payment status; ERP = system-of-record for line items. Idempotency key per invoice; reconciliation/conflict queue; payment status via webhook. (Prevents double-invoicing on retry.)
- A11 — Notifications.
notificationentity + subscription model; cascade emits one debounced digest per actor (no per-node notify-storm); channels Slack/in-app/email. - A12 — AI proposal integrity. Every proposed diff passes schema + referential-integrity validation (only real node IDs — no hallucinated parents) before reaching the inbox. Prompts/templates/models are versioned; per-template cost + eval tracking.
- A13 — IA / Navigation is Phase 0. Role-aware nav (persistent primary rail + role-filtered secondary + command palette) designed BEFORE any module screen.
- Testing refinement (revises T1). The cascade evaluator, auth resolver, and visibility filter are pure REAL modules (not mock) carried in the prototype and unit-tested now — they ship verbatim into the real build. E2E covers flows; units cover evaluator edge cases.
- States matrix. Every key screen (tree, board, inbox, portal, estimate) specs empty / loading / error / zero-permission / overflow / first-run.
- Data layer. Typed repository interface (mock impl now → Supabase impl later); seed factories derived from the same entity schemas as real migrations; RLS derived from the visibility primitive.
- Entities ERD (define before Phase 2). rate cards · deals/contacts/companies · KB docs+backlinks · test cases/suites/runs · time entries · notifications · proposals · audit_event — schemas + FKs.
- Gmail ingestion. Thread grouping + dedup; sender→contact→company→deal/project resolution with an unmatched-email triage queue; spam/auto-reply filter BEFORE AI proposal generation.
- Design legibility. One canonical "blocked action" component that names which of the 4 auth layers blocked it; a status-board visual language distinguishing cascade-set vs gated vs AI-proposed vs hidden-from-client (provenance chips, distinct affordances).
CRITICAL REFRAME (Rodrig)
This repo is a VISION PROTOTYPE — it shows the full vision. The real portal is ALREADY being built in a separate repo. So:
- This prototype is a design / communication artifact, NOT the foundation the dev builds on.
- The eng-lens "real-build correctness" gaps below are spec guidance handed to the other repo's team, not implementation tasks here.
- The design-lens findings (IA/nav, states matrix, portal-as-product, visual language for cascade/gates/AI/visibility) are THIS repo's priority — a vision prototype lives or dies on them.
- Rigor on this repo (Playwright E2E, unit tests) is now optional/credibility-only, not a handoff gate.
Strategic calls — RESOLVED
- S1 — Client approvals: approve-by-reply is first-class (email/Slack reply captured + audited; portal stays the rich surface). Cascade never stalls on a portal login. (applies to real repo)
- S2 — No anchor; full prototype first. The real portal already delivers value separately, so the prototype's job is purely to show the complete vision.
- S3 — Targets now, baseline at launch. Define 2-3 success metrics (PM admin time, invoice cycle, escaped-bug <10%); capture the "before" baseline when the real portal launches.
- S4 — Single-tenant now, but keep the door open (REVISED 2026-05-29). Build for ourselves, single-tenant — no multi-tenancy overhead, no customer onboarding/billing-per-tenant, no tenant-isolation testing now. But there is a real (undecided) future appetite to: (a) make the whole ERP multi-tenant SaaS, (b) ship it as a template / Docker template, or (c) extract some microservices to offer clients as SaaS. We don't redesign the architecture for this, but we take the cheap, reversible moves that avoid hard-blocking it later (see S4-fwd). This is spec guidance for the real-build team; the vision prototype stays internal-focused (the role-switcher already implies a membership/org model it can lean on).
- S4-fwd — "Leave-the-door-open" checklist (cheap now, expensive later). Hand to the real repo; none of these build SaaS, they just avoid a painful retrofit:
- **Carry an
org_id/workspace_idon every domain row from day one — exactly one value today. Backfilling a tenant key into a populated schema + every query + every index later is the costly part; adding the column now is ~free. RLS policies key on it** (composes with A1 auth + the visibility primitive). - No global singletons for tenant-scoped config — rate cards, workflow/cascade templates, integration credentials, KB roots, notification settings hang off the org row, not constants/env.
- Per-org integration secrets (Xero, recall.ai, Slack, Gmail) in Infisical keyed by org, so a second tenant never needs a redeploy. (Aligns with A7 scoped/audited tokens.)
- Namespace storage / file paths / KB / MCP scopes by org rather than a flat global space.
- Clean service seams = path (c) for free. The "configurable engines over one data model" architecture already favors extractable boundaries; keep engine APIs free of cross-cutting globals and a single engine (e.g. Estimates, QA) can later be lifted into a standalone SaaS.
- Explicitly NOT now: tenant signup/provisioning, cross-tenant admin, per-tenant billing, plan/quota metering, tenant-aware rate limiting, data-residency. Defer entirely.
- > Net: the schema and config are tenant-shaped; the product is single-tenant. Flipping the switch
- > later becomes a feature project, not a rewrite.
- **Carry an
Gap ownership (post-reframe)
- THIS repo (vision prototype): IA/navigation (A13), states matrix, portal-as-product, status/gate visual language, the visibility/auth model depicted, all module screens — the full clickable vision.
- OTHER repo (real build) — spec guidance: A7 (AI/MCP auth binding), A8 (audit log), A9 (money), A10 (Xero idempotency), A11 (notifications fan-out), A12 (AI proposal integrity), A15 Tiers 2–3 (operational run records + system logs/traces + correlation + redaction), repository/RLS, entities ERD, Gmail ingestion internals, cascade recompute perf. Hand these to that team.
- THIS repo adds (A15): the four depicted observability surfaces — System Activity admin screen, per-object History tab, live-tailing log viewer, alerting config UI (Owner-only).
- A14 — Cadence & Check-ins engine (Engine 6) real-build (deferred from eng review 2026-05-29): (1) Scheduler — it's the first time-triggered engine; **Supabase
pg_cronis the natural candidate (already in the DB, no new infra), alt = a Railway cron worker or scheduled Edge Functions. (2) Data model / ERD** —CheckinTemplate → Assignment (per project) → Instance (per period) → Response (per person), every row carrying **org_id(S4-fwd). The template owns a typed field-schema** (Field{id, type, label, help, required, options[]}); aResponsestores values keyed to field IDs so editing a template never orphans prior answers. This field-builder is a reusable primitive (later: QA defect forms, client intake) — design it generic, not check-in-only. EachInstance/Responsecarries **sent_at+due_at(deadline = send + template window, in recipient tz). (3) WhatsApp Business API reality — outbound client nudges need pre-approved message templates + a 24h session window (no free-form DMs anytime); plan template copy + portal deep-links around it. (4) Leave / OOO source of truth — Engine 6 reads an approved Leave record at fire time to suppress nudges + exclude the period from "missed". Build a lightweight request→Owner-approve Leave record now; a Deel integration as the upstream leave source is deferred. Excuse semantics: an Owner one-tap "excuse" (one-off) and a retroactive Leave record** (multi-day) both flip amissedevent toexcused— derived, audited events (actor, ts, reason), never silent mutation; feed A8. (5) AI quality check — versioned rubric/prompt scoring response substance (cost+eval tracked per A12); produces a soft author nudge + an Owner-visible per-response flag (needs-detail/fine — no score/ranking), non-blocking. A single flag is coaching-only; a chronic pattern (count flags per person over a rolling window, Owner-set threshold) carries a small bonus weight — modelquality_flagas a stored per-response event and derive the chronic weight over the window (reconstructible, likemissed). Period/idempotency semantics are already pinned inautomation-engines.md→ Engine 6. - A15 — System-wide logging & observability is THREE tiers, separated by who reads it (from eng review 2026-05-30). "Everything gets tracked, maybe no UI for all" =/= one mega
system_eventtable (that's an OLTP write-firehose + a query graveyard). Split by read pattern:- Audit log (Tier 1) = A8, now in TWO PLANES (defense in depth — this is what makes "everything" literally true). Business/domain events (who changed what), append-only in Postgres, UI + RLS, long retention (years):
- (a) DB-trigger change-log = the guaranteed floor. A row-level trigger captures every INSERT/UPDATE/DELETE on domain tables, so writes that bypass the app layer (raw SQL, a migration, a direct MCP/CLI mutation) are still caught. No application code can forget to log.
- **(b) App-level semantic
audit_event(A8) = the human-meaningful layer.** Engines write rich verbs/provenance ("set by cascade rule R2", "SP earned"). This is what the UI and per-object History read. - The two planes are correlated by
trace_id; (a) guarantees coverage, (b) gives meaning.
- Operational run records (Tier 2) — NEW first-class tables, light admin UI, medium retention (~90d hot → archive). A small fixed set, each row carrying **
org_id(S4-fwd) +trace_id** (see correlation below):IntegrationRun{provider(xero|slack|gmail|github|recall|whatsapp), direction, status, attempt, idempotency_key→A10, duration_ms, redacted_summary, error?}— the Owner-visible "did the Xero sync run / did it fail?" record.JobRun{job, scheduled_at, fired_at, duration_ms, outcome, error?}— scheduled work incl. Engine 6pg_cronticks (A14). Surfaces silent-cron failures.AiWorkflowRun{template@version, tokens, cost, outcome, proposal_refs[]}— extends A12 (cost+eval) into a queryable run history, not a new system.WebhookReceipt{source, signature_verified, dedup_key, received_at, payload_ref}— inbound webhooks (Xero payment status, GitHub, recall) with dedup.
- System logs + traces (Tier 3) — the "no UI" firehose. Structured JSON to stdout + OpenTelemetry spans → an external aggregator (start with Railway logs / PostHog error tracking; vendor-swappable to Axiom/Better Stack — OTel keeps the wire format neutral, no call-site changes on swap). No app UI. Short retention (~30–90d). Errors/exceptions → the error tracker. Engineers query here, not Owners.
- Correlation (NON-NEGOTIABLE, D3). One **
trace_id/request_id, OTel-native at the HTTP edge, propagated into background jobs + outbound integration calls + every Tier-1 audit row + every Tier-2 record**. This is the cheap-now/expensive-later move that makes a single user action traceable click → audit → job → integration → log line. Closes A7's loop: an MCP/CLI agentic write carries one trace id prompt→mutation→audit. - Redaction (NON-NEGOTIABLE). Never log secrets (Infisical, Xero/Slack/Gmail/GitHub tokens) or full client-PII payloads. Tier-2 integration records store redacted summaries + references, never raw bodies. (Aligns A7 scoped tokens, A9 money/PII.)
- Write path. Audit + operational writes are async/batched off the request's critical path — logging must never add latency a user feels or fail a user's action.
- Prototype depiction (THIS repo, D4 = full mock). The vision shows the whole surface: (a) Settings → System Activity admin screen unifying the three streams, filterable, rows linked by
trace_id; (b) a per-object History tab reusing the audit feed; (c) a live-tailing log viewer (mock stream); (d) an alerting config UI (mock rules, e.g. "notify #eng whenIntegrationRun.xerofails 3×"). Owner-only visibility — adds an Observability / System Activity capability row topermission-matrix.md(Owner-only). States matrix applies (empty/loading/error/ zero-permission). Depicted, not wired — the real stack is Tiers 1–3 above. - Ownership. Tier 1 = A8 (already). Tiers 2–3 + correlation + redaction + async write path = real-build spec guidance (other repo). The four depicted surfaces = this repo.
- Audit log (Tier 1) = A8, now in TWO PLANES (defense in depth — this is what makes "everything" literally true). Business/domain events (who changed what), append-only in Postgres, UI + RLS, long retention (years):
- **A16 — Compensation engine (SP → bonus) — full spec in
staff-comp.md(CEO review 2026-05-30). A compute engine over existing data (SP earned-at-Deploy delivery triad + Engine-6 missed tally), not new tracking. Locked:** flat company-wide$210/tier (output piece-rate, base sets floor only — seniority shows up as more points, not a base multiplier); laddertiers = SP≥80 ? floor((SP−80)/20)+1 : 0; leave pushes the 4-week window (never scales the 80 floor); missed check-ins subtract effective SP pre-ladder; immutable period snapshots + append-only adjustment ledger {clawback, transfer, correction} for post-close corrections (closed periods never mutated); gated clawback only on genuine escaped bugs; fix-up = paired −X%/+X% transfer (default 50%, Owner override). SP is value, never time — anti-inflation anchored on reference-class value + client cost-range acceptance + drift flags + Owner-by-exception + a capped consistency modifier on dev & PO (neutralizes dev↔PO collusion); end-state = calibrated-SP payout. Reads/writes A8/A15 (auditable records are a contract requirement). QA = mean(devSP × coverage) on the dev ladder × capped quality modifier; PO = sum(delivered project SP) on an Owner-configurable PO floor. Deel = system-of-record for contracts + contractor payment (base syncs from Deel; bonuses export to Deel with per-(person,period) idempotency, mirroring A10). Ownership: policy + the four surfaces = this repo; the calibration/reference-class engine + Deel payroll integration = real build.
Revido ERP — Design System (DRAFT v0.2 — WARM)
Direction: Modern Notebook Warmth, quiet register — refined toward Linear-grade crispness. A tool you live in all day should feel as nice to use as a good notebook and as crisp as a well-built native app. Warm-neutral surfaces, generous air, and one warm accent carry the calm; razor hairlines, a recessed→lifted surface ladder, tight contact shadows, and a crisp Geist / Switzer sans pairing carry the precision. Implemented on ShadCN + Tailwind. This supersedes the v0.1 Linear/lavender skin (kept in git history); the locked value decisions below (14-status palette, observability, form validation, ShadCN mapping) carried over unchanged and were re-skinned, not re-opened.
Status: refined start, still in motion. The token values, the Geist/Switzer pairing, and the warm-OS shell (§ App shell) were settled in live design review on 2026-05-31 against the working preview at docs/design/warm-os-preview.html. Strong base, not a frozen spec — active-tab contrast, pills-vs-rect for actions, and the KPI-numeral face are still being tuned.
Why warm, not cool. Notion proves warmth survives real density. The pen-and-paper feeling at ERP density comes from spacing, hairline restraint, and low contrast — not from loud cream or serif body. So we run the quiet version of warm: warm-white (not loud cream), crisp sans on every surface, tabular numerals on every data surface, one warm accent kept off the status hue-wheel. Decision record: CEO/design consultation 2026-05-31.
Fonts. All-free stack, tokenized as--font-head/--font-body/--font-mono/--font-brand. Geist is the heading face (page titles, section heads, KPI numbers) — a crisp neo-grotesque that gives the cockpit its Linear-grade edge. Switzer is the body face (tables, labels, nav, dense text) — a clean grotesque that stays calm at density. IBM Plex Mono handles SP, money, timestamps, trace IDs. Fraunces (serif) is brand-only — the Revido wordmark and client-facing marketing / portal hero, never the working UI. (We tried an all-IBM-Plex-Sans surface first; Plex's humanist softness read less crisp, so headings/body split to the Geist/Switzer pairing on 2026-05-31.) The @revido/design-system NPM exposes these as tokens; products override only the accent (see §11).
Principles
- Quiet warm, light is the hero. A warm light theme is the designed-for default; a warm dark theme is a first-class, hand-built alternate (never a cheap invert). Both user-togglable, persisted per user, applied to cockpit AND client portal. Rust-clay
#9c5b3bis the only brand accent in either theme. Use it sparingly — one or two spots per view (primary CTA + the notification dot + the ⌘K keycap is the typical budget). Active-nav is a neutral fill — no accent bar; avatars, chart bars, and icons stay neutral so the one warm accent actually reads — Apple/Notion-clean comes from this restraint. Never decorative, never a card fill. - Sans for focus; serif is brand-only. The working surface is a crisp sans pairing — Geist on headings (titles, section heads, KPI numbers) and Switzer on body (tables, labels, nav, sidebar) — so the tool reads as a calm, precise workspace, not a magazine. Fraunces is reserved for the Revido wordmark and client-facing marketing / portal hero moments. Warmth on the working surface comes from the paper palette, the rust accent, and spacing, never from editorial type. (Tested 2026-05-31: serif on cockpit headers read "magazine" and pulled focus; all-Plex-Sans read soft — the Geist/Switzer split gives crisp without going cold.)
- Surface ladder + hairlines, not shadows — tuned for crispness. Hierarchy comes from a warm surface ladder and razor 1px hairlines. Crispness is a contrast-at-boundaries job: push the desk back (deeper) so the page sheet lifts off it; recess a strip (e.g. the tab bar to
sunken) so the active, content-colored element reads as lifted-and-joined; keep every hairline deliberate; use tight contact shadows (--card-shadow, the page--ring+ a low drop), never diffuse halos. Hues stay warm; only the edges sharpen. Big floating layers (dropdowns, toasts) keep low-spread, low-opacity shadows — never harsh, never colored. - Air does the calming. Generous spacing and restraint are what make density feel like paper instead of a spreadsheet. When a surface feels busy, remove, don't decorate.
- Density is the cockpit default; comfortable is the portal default. The internal cockpit runs dense (36px rows). The client portal uses the same tokens at comfortable density (44px rows, more whitespace, friendlier labels) — same system, softer rhythm.
- The data is the protagonist. Chrome is a warm frame; the tree / board / portal content leads.
- The content page is a sheet on a desk. The **nav rail sits directly on the
deskbackdrop (no card of its own — it's part of the desk); the main content area is the page** — acanvassheet with roundedlg(10px) corners, ahairline-strongedge, and the--ring+ a low warm shadow, floating on the (deepened) desk to the right of the rail. It reads like a sheet of paper on a desk — the core pen-and-paper move. The desk shows behind the rail and in the gap between rail and page. On<768the page goes near full-bleed (margin to 8px, cornersmd) and the rail becomes a drawer. - Tabular numerals everywhere numbers line up. SP, money, dates, and table figures use
font-variant-numeric: tabular-nums(Geist, Switzer, and Plex Mono all supply it) so columns never jitter.
Color tokens (→ ShadCN CSS variables) — WARM LIGHT + WARM DARK
Both themes share token names and the same rust-clay accent. ShadCN convention: :root = warm light (default), .dark = warm dark. Lift = brighter/cleaner (a sheet of paper on a desk); recede = warmer/deeper.
| Token | ShadCN var | LIGHT | DARK | Use |
|---|---|---|---|---|
| desk | --desk | #e3dccd | #121009 | the warm backdrop the nav rail + page rest on (body bg); deepened so the page sheet lifts |
| canvas | --background | #f6f3ec | #1b1815 | the app page sheet + tab/content surface (warm near-white, never pure white/black) |
| surface-1 | --card,--popover | #ffffff | #211e1a | cards, search field (crisp white sheets on the warm page) |
| surface-2 | --secondary,--muted | #f0ece4 | #27231e | hovered row, hovered tab |
| surface-3 | — | #ffffff | #2e2924 | floating dropdown / popover |
| sunken | — | #ebe6dd | #151210 | panels behind cards; the recessed tab bar |
| selected-nav | — | #efeae2 | #2c2820 | active nav fill (neutral; no accent bar) |
| hairline | --border,--input | #e6e0d6 | #38322b | default 1px border (razored for crispness) |
| hairline-strong | — | #d6cfc3 | #473f35 | emphasized divider; page edge; active-tab border |
| ink | --foreground | #1f1d1a | #ece6db | headings, body |
| ink-muted | — | #5c574e | #b3ab9c | secondary text |
| ink-subtle | --muted-foreground | #6b6358 | #908877 | captions, meta (≥4.5:1 verified) |
| ink-tertiary | — | #a59c8d | #635c50 | disabled, placeholder, faint labels |
| primary | --primary,--ring | #9c5b3b | #b06440 | accent, CTA, focus (lifts on dark, white text ≥4.5:1) |
| primary-hover | — | #875030 | #c4774f | hover (darken light / lighten dark) |
| accent-soft | — | #f1e7df | #382820 | accent-tinted surface (active nav icon, selected) |
| on-primary | --primary-foreground | #fffcf8 | #fffcf8 | text on accent |
| success | — | #2e7d4f | #4fa56b | Deployed / paid |
| warning | --warning | #c0772b | #d08a45 | warn (warm, distinct from accent) |
| danger | --danger | #b4493c | #d26456 | error / destructive |
Elevation / crisp-edge tokens. --ring = 0 0 0 .5px rgba(45,36,26,.07) (light) / rgba(0,0,0,.45) (dark) — the hairline ring under the page sheet. --card-shadow = 0 1px 2px rgba(45,36,26,.06) / …rgba(0,0,0,.28) — a tight contact shadow giving cards a paper-on-sheet lift. Page-sheet shadow = 0 1px 1px rgba(45,36,26,.07), 0 5px 16px rgba(45,36,26,.10) — a low, warm, defined lift, not a diffuse halo. These replace the old soft 0 8px 24px glow.
Accent vs status orange — resolved. The brand accent (rust-clay) is a deep, brown-leaning clay used only on chrome (CTA, focus ring, active nav, brand mark). The status palette's orange (#f2994a Revise) and red (#eb5757 Blocked) are small dots with text labels. They coexist the way lavender already double-served as both accent and the Client-Review dot in v0.1: different surface, different size, always paired with a label. The accent sits browner and darker than any status hue, so it never reads as a status.
Status-color palette (ERP extension — carried over unchanged from v0.1)
The 14-status board needs an in-product priority palette. Each status keeps its dot color; columns group by the 6 status groups. These hues are theme-independent — same dot on warm light and warm dark. Verified for ≥3:1 against both canvases; slate/gray dots always carry a text label (never color alone).
| Status | Group | Dot |
|---|---|---|
| Triage | Triage | #8a8f98 slate |
| Backlog | Backlog | #62666d gray |
| To Do | To Do | #4ea7fc blue |
| In Progress | In Progress | #f2c94c amber |
| Blocked | In Progress | #eb5757 red |
| PR Open | In Progress | #9b8afb violet |
| Revise | In Progress | #f2994a orange |
| QA Review | In Progress | #bf5af2 purple |
| To Merge | In Progress | #2dd4bf teal |
| Final Review | In Progress | #7a7fad indigo |
| Client Review | In Progress | #6d8ad6 blue-violet |
| To Deploy | In Progress | #22d3ee cyan |
| Deployed | Done | #27a644 green |
| Cancelled | Cancelled | #62666d gray (strike) |
v0.1 used#5e6ad2(the old lavender accent) as the Client-Review dot. With the accent now rust-clay, Client Review takes its own **blue-violet#6d8ad6** so the status palette stays independent of the brand accent.
Group chips (column headers / rollups): Triage #8a8f98 · Backlog #62666d · To Do #4ea7fc · In Progress #f2c94c · Done #27a644 · Cancelled #62666d.
Semantic UI accents (reused from the status palette): gate-blocked = #f2994a orange · AI-proposed = #9b8afb violet · hidden-from-client = #bf5af2 purple tint badge · success/paid = green.
Observability palette (A15 — System Activity surfaces)
The logging surfaces (information-architecture.md §9) reuse the palette above — no new hues. Outcome/severity is always dot + text label (never color alone — colorblind-safe).
| Log severity | Dot | Run outcome (IntegrationRun/JobRun/AiWorkflowRun) | Dot | |
|---|---|---|---|---|
| debug | #8a8f98 slate | success | #27a644 green | |
| info | #4ea7fc blue | running (live) | #4ea7fc blue, pulsing | |
| warn | #f2994a orange | retrying | #f2994a orange | |
| error | #eb5757 red (--danger) | failed | #eb5757 red | |
| fatal | #bf5af2 purple | skipped / excused | #8a8f98 slate |
- **
trace_idchip:**bg-surface-2,rounded-pill, IBM Plex Mono 13,ink-subtle; hover lifts tosurface-3; click opens the correlated Trace drawer (IA §9.3). Same chip everywhere a row carries a trace. - Degraded-state banner (Tier-3 aggregator unreachable, IA §9.4): full-width
--warningbanner at the top of the Logs tab — "Log backend unreachable; Activity & Alerts still live." Activity/Alerts tabs (Postgres-backed) stay fully functional — never blank the whole screen. - Log/event rows + payloads: monospace (
monotoken); tables/streams, never card mosaics.
Typography (Geist headings · Switzer body · IBM Plex Mono data · Fraunces brand-only)
Headings are crisp Geist (--font-head); body and dense text are Switzer (--font-body); numbers, money, and timestamps are Plex Mono (--font-mono). Fraunces (--font-brand) appears only in the two brand rows (wordmark + marketing hero), never in the working UI.
| Token | Font | Size / Weight / Tracking |
|---|---|---|
| headline (page title / greeting) | Geist | 16 / 600 / -0.25px |
| kpi-number (big stat) | Geist | 25 / 700 / -0.85px (tabular) |
| card-title / section | Geist | 13.5 / 600 / 0 |
| subhead | Switzer | 16 / 500 / -0.1px |
| body-lg | Switzer | 16 / 400 / 0 |
| body | Switzer | 14 / 400 / 0 |
| body-sm | Switzer | 13 / 400 / 0 |
| caption | Switzer | 12 / 400 / 0 |
| button | Switzer | 13 / 500 / 0 |
| eyebrow | Plex Mono | 11 / 500 / +0.4px (uppercase — taxonomy) |
| mono | Plex Mono | 13 / 400 / 0 |
| wordmark (brand-only) | Fraunces | 17 / 500 / -0.3px |
| marketing-hero (client-facing only) | Fraunces | 52–72 / 500 / -0.8px |
- Body line-height 1.6; heading line-height 1.2. Real italics where used (Fraunces ships true italics).
- Body text never below 13.5px; never below 4.5:1 contrast (see §10).
Spacing (4px base) & Radii
- Spacing: 4 · 8 · 12 · 16 · 24 · 32 · 48 · 96(section). Card padding 20–24; section gap 24.
- Radii: xs 4 · sm 6 · md 8 · card 8 · lg 10 · xl 16 · pill/full 9999. Cards/inputs/search = md (8). Buttons and chips (status, role, engine, "spaces" overflow) = pill (full radius) — the status bar already spoke pill, so actions and chips unified to it (2026-05-31; this reverses the earlier "no pills as primary action" rule). Tabs are the exception: connected browser/file tabs (top-rounded
md, bottom-joined to the content), not pills. Never sharp 0 except full-bleed.
Motion
Calm and functional. Motion clarifies hierarchy and state; it is never decorative.
- Durations: instant 80ms · short 160ms · medium 240ms · long 360ms.
- Easing: enter
cubic-bezier(0.16,1,0.3,1)(soft ease-out) · exitease-in· moveease-in-out. - Patterns: card hover
translateY(-1px)+ hairline darken (160ms, no scale) · dropdown/popover fade +translateY(4px)(160ms) · toast slide-in from bottom-right (240ms) · row select background fade (80ms) · skeleton shimmer 1.4s loop · live run-status dot pulses (2s). - **
prefers-reduced-motion:** drop all transforms and the shimmer; keep instant opacity changes only. Honored globally.
Interaction states (centralized — every data surface reuses these)
| State | What the user SEES |
|---|---|
| Loading | Skeleton blocks at surface-2 with a slow warm shimmer. No layout shift — skeleton matches final dimensions. |
| Empty | One Geist semibold sentence + one-line context + a single ghost button. Never "No items found." e.g. "No projects yet." + "Create your first project." |
| Error (card-local) | The card shows an inline error + Retry; the rest of the page still renders. One failed card never blanks the screen. |
| Error (route) | Only when the whole route fails: full-surface error + Retry + "contact" path. |
| Degraded | The --warning banner pattern (logs §9.4) — name what's down, keep the rest live. |
| Success | Inline confirmation in success color; no modal for routine saves. |
| Partial | Show what loaded + an inline "couldn't load X — retry" row for the missing piece. |
| First-run | New project / no data → guidance and a primary action, never a blank box. |
| Zero-permission | The element is absent (hidden ≠ disabled), consistent with nav-as-permission-projection. |
- Toast: bottom-right,
surface-3, hairline, auto-dismiss 5s,aria-live=polite. Errorsaria-live=assertive.
Responsive
Breakpoints: sm 640 · md 768 · lg 1024 · xl 1280 · 2xl 1536.
- Cockpit (internal): optimized for
≥1024. At768–1024the sidebar collapses to a 56px icon rail and wide tables gain horizontal scroll with a sticky first column. Below768the cockpit is read-mostly: primary nav becomes a drawer, the 14-status board collapses to a single-column, status-grouped list. Editing-heavy flows prompt "best on a larger screen" rather than cramming. - Client portal: mobile-first, comfortable density. Cards stack to one column; 44px touch targets; the bottom-nav pattern on small screens. Same tokens, softer rhythm.
- Never "just stacked" — each viewport gets an intentional layout, not a reflow.
Accessibility
- Contrast: body text ≥ 4.5:1, large text + UI affordances ≥ 3:1. Verified for the warm-light and warm-dark token pairs above.
ink-subtleis for meta ≥13px only; never small essential text. - Focus:
focus-visibleshows a 2px--ring(rust-clay) ring + 2px offset on every interactive element. Outline is never removed. Error fields focus in--danger, not the accent. - Keyboard: everything reachable and operable by keyboard; board cards are arrow-navigable; a command palette covers power use. Visible focus order matches visual order.
- Touch: 44px minimum target on portal/mobile.
- Color is never the only signal: status and run outcomes are always dot + text label.
- Reduced motion: honored (see §8).
- Screen readers: ARIA landmarks (
nav/main/complementary), table semantics for data grids,aria-liveregions for toasts and live run-status. Icon-only buttons carryaria-label.
App shell (warm OS) — design-only direction, in progress
The cockpit is framed as a calm "operating system" you live in all day. Capability via UI patterns only — no platform machinery (no shell SDK / plugin API / cross-product SSO; CEO decision 2026-05-31). Settled layout (reference build: docs/design/warm-os-preview.html):
- Desk + page. The nav **rail sits on the
deskbackdrop (no card of its own); the content is acanvassheet** with--ring+ a low warm shadow, a thin (~9px) gutter, filling the viewport. - Sidebar (left rail): Revido wordmark + collapse/expand control at top; nav grouped (workspace nav, then "Run the business"); engine-health status row + profile (avatar · name · role · account chevron) pinned at the foot. Collapses to a 56px icon rail.
- One top bar (no second row): tabs (spaces) on the left; global ⌘K search + notifications on the right. Engine health, profile, and role live in the sidebar foot, not here — the global search/notifications/account are account-wide, so the OS bar stays above the spaces.
- Spaces = connected tabs (see Tabs in Component conventions): persistent, open/closable workspaces; the active tab is the page's persistent label, so the page greeting lives in content rather than a separate band.
- ⌘K command palette (jump-to + actions) · peek drawer / Quick Look (click an at-risk row, the engine pill, or System Activity → right-hand drawer) · warm light / warm dark toggle.
Still tuning: active-tab contrast, pills-vs-rect for action buttons, KPI-numeral face (Geist vs mono), persistent-vs-scrolling page greeting, collapse-control placement (top vs foot of sidebar).
Component conventions (ShadCN)
- Card:
bg-surface-1,border-hairline,rounded-[8px], tight--card-shadowcontact shadow, padding 16–20. Hover (interactive cards only):translateY(-1px)+ hairline darken. - Button primary:
bg-primary text-on-primary rounded-pill, 8×17; hoverprimary-hover. - Button secondary:
bg-surface-1 border-hairline-strong text-ink rounded-pill. Ghost: transparent, accent text. - Input / search (⌘K):
bg-surface-1 border-hairline-strong rounded-md; focus ring = rust-clay; label always visible (never placeholder-as-only-label). - Status / role / engine pill:
rounded-pill,bg-surface-1, dot + label. - Tabs (spaces): connected browser/file tabs in a **recessed (
sunken) bar; the active tab is content-colored (canvas) +hairline-strongborder + 600 weight**, bottom-joined to the page; parked tabs sit muted in the bar. (Pill spaces were tried 2026-05-31; connected tabs read the open-workspace metaphor better.) - App shell / nav: see § App shell (warm OS) below.
- Tooltip (gate "QA only" / blocked):
surface-3, hairline, caption type, low-spread shadow.
Form validation (LIGHT / DARK)
| State | Token | LIGHT | DARK | Treatment |
|---|---|---|---|---|
| error | --danger | #b4493c | #d26456 | 1px border + ring in danger; helper caption below in danger; optional ⚠ icon |
| warning | --warning | #c0772b | #d08a45 | warm border + helper caption |
| success | --success | #2e7d4f | #4fa56b | green border + helper caption ("Looks good") |
- Error input: border + focus ring
--danger(not accent); helper text in--dangercaption. - Field helper / hint (neutral):
ink-subtlecaption below the field. - Required marker: rust-clay
*orink-subtle"(required)". - Same pattern powers the workflow precondition pop-up ("Missing: Loom, preview link") — missing required artifacts render as
--dangerrows until satisfied.
NPM token layering (@revido/design-system)
The portal is the proving ground; the system extracts to @revido/design-system so Finlek, PartsIQ, Abodio look like siblings — shared warm base, one accent each. Three layers:
- Primitives (product-agnostic): raw scales —
warm-50…900, theclayaccent scale, the 14 status hues, space, radii, the Geist/Switzer/Plex-Mono/Fraunces type tokens. No semantics. - Semantic (theme-switchable):
--canvas,--surface-*,--ink-*,--accent*,--status-*map from primitives.:root= warm light,.dark= warm dark. **--accentis sourced from a single per-product primitive** — that is the only thing a product overrides. - Component (ShadCN): component vars consume semantic vars. Untouched per product.
Per product: set --accent (+ -hover, -soft, -on) to its hue and ship. Revido ERP = **rust-clay #9c5b3b. The package exposes CSS vars + a Tailwind preset + the ShadCN config**; the ERP imports the preset and sets product=erp.
ShadCN setup
components.json: base color stone (warm neutral), CSS variables on, dark mode viaclassstrategy.- Define BOTH token sets in
globals.css::root= WARM LIGHT,.dark= WARM DARK. Theme is user-togglable (toggle + system-preference default), persisted per user; applies to cockpit AND portal. - Fonts via
next/font: Geist (--font-head), Switzer (--font-body), IBM Plex Mono (--font-mono), Fraunces (--font-brand, brand-only). Switzer ships via Fontshare; the rest are on Google Fonts. - Tailwind: consume the
@revido/design-systempreset; it maps token names (canvas, surface-1..3, sunken, hairline, ink-, primary/accent-soft, status-, danger/warning/success) and the type scale.
Known gaps / notes
- Real seed-data brand imagery and the Revido logo/wordmark still pending (Rodrig to provide).
- Fraunces optical-size stops for the wordmark + marketing hero to be tuned against the live build.
- Shell still in refinement (design review 2026-05-31, to resume): active-tab contrast, pill-vs-rounded-rect for action buttons, KPI-numeral face (Geist vs Plex Mono), persistent-vs-scrolling page greeting, collapse-control placement. Reference build:
docs/design/warm-os-preview.html(includes a live H/B font switcher + warm light/dark toggle for continued auditioning). - v0.1 (Linear/dark/lavender) lives in git history if a cool variant is ever wanted.
Revido ERP — Information Architecture & Navigation (DRAFT v0.1)
How the 16+ modules (system-map.md§5) collapse into a navigable shell, how each of the 8 roles (permission-matrix.md) sees a different cut of it, and how you move through the project tree. This is the structural spec the vision prototype is built on. Visual language lives indesign-system.md; what each screen contains lives in the module docs.
0. Principles
- Two shells, one data model. The internal cockpit (dense, Linear-style) and the client portal (comfortable density, friendlier labels) are different navigation shells over the same entities. A client never sees the cockpit rail; an internal user can preview the portal.
- Nav is a projection of permissions. Every rail item has a visibility rule derived from
permission-matrix.md. Hidden ≠ disabled: if a role can't access a module at all, the item is absent from their rail (not greyed). Within a module, capabilities are gated per the matrix. - List → peek → detail. Linear's three-zone pattern is the backbone: a list/board, a right-side peek panel (slide-over) for an entity, and a full detail route when you need the whole thing. This is what lets one shell carry 16 modules without losing context.
- The tree is the spine, but it's not the only lens.
Project → Milestone → Module → Feature → Taskis navigable as a tree, a board (grouped by status group), a list, and a timeline. - Everything is reachable in two keystrokes. Cmd-K (command palette) navigates, searches, creates, acts, and triggers AI — so the rail can stay lean.
- Role-switching is a first-class prototype surface. Switching effective role re-renders the rail, the dashboard, and gate states — it is the demo's whole point.
1. The internal cockpit shell
Three zones plus global chrome (sizes from design-system.md):
┌───────────────────────────────────────────────────────────────────────┐
│ TOP BAR (56px) — breadcrumb · view controls (filter/sort/group) · + New │
├──────────────┬──────────────────────────────────────┬───────────────────┤
│ │ │ │
│ PRIMARY │ CONTENT AREA │ PEEK PANEL │
│ RAIL │ (active module; may have its own │ (slide-over; │
│ (~240px, │ sub-nav: tabs or secondary list) │ entity detail │
│ collapsible│ │ without leaving │
│ to icons) │ │ the list) │
│ │ │ │
├──────────────┴──────────────────────────────────────┴───────────────────┤
│ RAIL FOOTER — role switcher (prototype) · theme toggle · user · AI chat │
└───────────────────────────────────────────────────────────────────────┘
Global chrome (present on every cockpit screen):
- Command palette (Cmd-K) — navigate / search / create / act / AI. See §6.
- Global search — entity search, auth-scoped; surfaced inside Cmd-K and as a top-bar field.
- AI chatbot (Notion-style) — invokable panel that pulls from anywhere via MCP and cites sources (project, task, doc…). Lives in the rail footer; also openable from Cmd-K.
- Notifications — the Inbox is the notification home (no separate bell); unread count on the Inbox rail item. Slack/email are parallel channels (
system-map.md§8). - + New — context-aware create (new task in a project, new deal in CRM, new doc in KB).
1.1 Primary rail — grouped
The rail groups the modules so 16 items read as ~6 sections. Order = daily-frequency, top to bottom.
| Section | Items |
|---|---|
| HOME | Dashboard (role-specific landing) |
| FOR YOU | Inbox · My Work |
| DELIVERY | Projects · Planning · QA & Testing · Client Updates |
| GROWTH | Sales CRM · Estimates |
| FINANCE | Invoicing · Profitability |
| WORKSPACE | Knowledge Base · People & Capacity · AI Workflows · Settings & Admin |
Notes:
- My Work absorbs Time & SP tracking (
system-map.md§5) — it's the personal task list + SP/time logging, not a separate destination. - Client Portal is not in the internal rail — it's the other shell (§3). Internal users reach a read-only preview of it from a project's Updates/Portal tab or Cmd-K ("Preview client portal").
- A user's favourited / pinned projects appear as a flat list under DELIVERY → Projects when expanded (Linear-style), so frequent projects are one click deep.
- System Activity (A15 observability, Owner-only) is NOT a default rail item — it lives under Settings & Admin → System (§9), but the Owner can pin it to WORKSPACE (same favourite pattern).
1.2 Rail visibility by role (internal)
Clients (C-A, C-S) never see this rail — they get the portal shell (§3). This table is the 6 internal roles. ●=visible & primary · ○=visible (view/limited) · —=absent from rail. Capabilities inside a visible module are still gated per permission-matrix.md.
| Rail item | OWN | PO | DEV | PB | QA | SAL |
|---|---|---|---|---|---|---|
| Dashboard | ● | ● | ● | ● | ● | ● |
| Inbox | ● | ● | ● | ● | ● | ● |
| My Work | ● | ● | ● | ● | ● | ● |
| Projects | ● | ● | ○ | ● | ○ | ○¹ |
| Planning | ● | ● | ○ | ● | ○ | — |
| QA & Testing | ● | ○ | ○ | ○ | ● | — |
| Client Updates | ● | ● | — | ● | — | ○² |
| Sales CRM | ● | ○ | — | ○ | — | ● |
| Estimates | ● | ● | — | ● | — | ● |
| Invoicing | ● | ○³ | — | ○³ | — | ○³ |
| Profitability | ● | — | — | — | — | — |
| Knowledge Base | ● | ● | ● | ● | ● | ● |
| People & Capacity | ● | ● | ○ | ● | ○ | ○ |
| AI Workflows | ● | ○ | — | ○ | — | ○⁴ |
| Settings & Admin | ●⁵ | ○⁵ | ○⁵ | ○⁵ | ○⁵ | ○⁵ |
¹ Sales: view-only, own projects only (projects they brought in) — permission-matrix.md RESOLVED #3. ² Sales: comment-level visibility on drafts (matrix §5). ³ Invoicing: view own-client invoices only; only Owner generates/pushes to Xero. PO/PB never see profit (RESOLVED #4) — Invoicing shows amounts billed, not margin. ⁴ Sales sees only the CRM-scoped workflow/lead automations. ⁵ Settings is visible to all internal roles, but sections are gated: everyone gets Personal (profile, theme, notifications) + CLI/MCP token; only Owner gets Roles & permissions, integrations, rate cards, workflow/cascade global config + the cadence/check-in template builder (Engine 6) (matrix §11). PO/PB get project-level overrides + workflow config on their projects.
Profitability is Owner-only as a module. The "view own utilization/billable" that all internal roles get (matrix §7) renders inside Dashboard / My Work, not the Profitability module.
2. The project workspace (drill-in)
Opening a project switches the content area into a focused project workspace with its own sub-nav. The primary rail stays; a breadcrumb shows Projects / Acme Redesign / ….
Project sub-nav (tabs):
| Tab | Contains | Visible to |
|---|---|---|
| Overview | health, velocity/burn, milestone timeline, at-risk | all (money hidden for PO/PB/DEV/QA/SAL) |
| Tree / Board | the spine; switch tree ↔ board (by status group) ↔ list ↔ timeline | all internal |
| Planning | this project's cycles (commit next 1–2 wks), backlog → to-do | OWN/PO/PB edit; DEV/QA view |
| QA | test suites & runs for this project; runs drive workflow | QA/DEV/PB primary; others view |
| Estimates | the linked, client-signable estimate(s) | OWN/PO/PB/SAL |
| Updates | client updates + check-in digests (Engine 6) for this project + portal preview | OWN/PO/PB |
| Docs | project KB pages + issue/feature/module MD docs, backlinks | all internal |
| Billing | invoices for this project (amounts only) | OWN/PO/PB/SAL view; OWN generate |
| Profitability | project + team-member margin | OWN only |
| Settings | project meta, role overrides, cascade/workflow template | OWN; PO/PB on their project |
2.1 The tree, four ways
The same Project → Milestone → Module → Feature → Task data renders as:
- Tree — collapsible outline; SP and % roll up at each node (pure computation,
system-map.md§1). - Board — columns = the 6 status groups (
status-model.md); cards carry status dot colors. - List — flat, filterable/groupable table (Linear "view" semantics: filter/sort/group/display).
- Timeline — milestones across time.
2.2 Entity detail = the peek panel
Click any Task / Feature / Module / Milestone → it opens in the right peek panel (slide-over) so the list stays put; a full-page route exists for deep work / sharing a link. The panel holds:
- Description — TipTap rich text (same editor as KB;
system-map.mdround 3). - Comments — threaded, with per-comment "hide from client" toggle.
- Background MD doc + backlinks (the Obsidian-like graph).
- Workflow control — status selector with gate tooltips ("QA only", "Missing: Loom + preview link") rendered as the danger-row precondition pop-up (
design-system.mdform-validation pattern). - SP / time, assignee, links to GitHub PRs, attachments.
- History tab — the per-object audit trail (A15 Tier 1), filtered to this entity (internal-only entries never reach clients). Each row's
trace_idchip opens the correlated Trace drawer (§9.3).
3. The client portal shell (separate)
Comfortable density, friendlier labels, same tokens (design-system.md principle 5). Mobile- friendly (clients check on phones). Clients log in "all the time" — this is a primary surface, not an afterthought. No primary-rail sections; a simpler top nav.
| Portal nav | Contains | C-A | C-S |
|---|---|---|---|
| Home | progress at a glance, recently shipped, what needs you | ● | ● |
| Progress | friendly project view (SP + % done, milestones, what shipped) | ● | ● |
| Approvals | estimates to sign, items to approve start, cycle scope | ● approve | ○ view |
| Requests | request a new feature / reprioritize | ○ create | ● create |
| Invoices | invoices, payment status | ● pay | ○ view |
| Updates | received AI-drafted client updates + answer check-in prompts (Engine 6 portal form) | ● | ● |
| Docs | client-visible KB docs published to them | ● | ● |
| Team | manage their org's client users | ● | — |
Key client interactions (from permission-matrix.md RESOLVED):
- Sign the Estimate (Modules + Features, no tasks; SP + cost ranges shown per rate card) → can auto-generate first invoice per billing mode.
- Drag Backlog → To Do = approval to start work.
- Client Staff requests features; Client Admin approves them to be started.
4. Dashboard — role-specific landing
/dashboard is the home for every role; content differs. Fixed, curated layout per role (not user-editable) — full widget-level spec for all 8 roles + the shared card library in dashboards.md. Summary of what each role leads with:
| Role | Dashboard leads with |
|---|---|
| Owner | utilization, profit, velocity trends, at-risk projects, employee cost/profit |
| PO / PB | project health, cycle commitments, items awaiting their approval, client-update queue (no money) |
| Dev | my tasks by status, my SP this cycle, blocked items, PRs awaiting action |
| QA | QA queue (items in QA Review), cycle-time KPIs, escaped-bug rate (qa-module.md) |
| Sales | pipeline, deals by stage, estimates awaiting PO, own-project delivery (view) |
5. Route map (for the prototype build)
Cockpit (/):
/dashboard
/inbox
/my-work
/projects · list
/projects/:projectId → /overview (default tab)
/projects/:projectId/tree · board?view=board|list|timeline
/projects/:projectId/planning | /qa | /estimates | /updates | /docs | /billing | /profitability | /settings
/projects/:projectId/t/:taskId · deep-link to an entity (also openable as peek over any list)
/planning · cross-project planning
/qa · cross-project QA hub
/crm · /crm/:dealId
/estimates · /estimates/:estimateId
/invoicing
/profitability
/kb · /kb/:docId
/people
/ai-workflows · /ai-workflows/:templateId
/settings/{profile|notifications|tokens|roles|integrations|rate-cards|workflow|cascade}
/settings/system/{activity|logs|alerts} · Owner-only observability (A15, §9); pinnable to rail
/settings/system/trace/:traceId · correlated Trace drawer (also a slide-over over any list)
Client portal (separate shell — subdomain portal.* or /portal/* in the prototype):
/portal/home
/portal/progress · /portal/progress/:projectId
/portal/approvals
/portal/requests
/portal/invoices
/portal/updates
/portal/docs · /portal/docs/:docId
/portal/team (C-A only)
Role switcher is prototype state (effective role + sample user), not a route — it re-resolves the 4-layer auth (architecture-decisions.md A1) and re-renders rail, dashboard, and gates.
6. Command palette (Cmd-K)
One surface, mode-detected from input:
| Mode | Does | Examples |
|---|---|---|
| Navigate | jump to any module, project, view, doc | "Acme planning", "Inbox" |
| Search | auth-scoped entity search across projects/tasks/docs/deals/people/invoices | "login bug" |
| Create | new task / feature / project / deal / doc / estimate (context-aware) | "New task in Acme" |
| Act | change status (gate-aware), assign, log SP, move stage | "Move to QA Review" |
| AI | trigger AI flows contextually + ask the chatbot | "Draft client update", "Research this lead", "Generate plan from transcript" |
| System | theme toggle, switch role (prototype), settings | "Switch to QA", "Dark mode" |
Gate-aware = if a status move needs a precondition (Loom + preview link, or QA-only), Cmd-K shows the same missing-artifact pop-up rather than failing silently.
7. Responsive behavior
- Cockpit = desktop-first, dense. Below ~1024px the rail collapses to an icon rail; the peek panel becomes a full-screen sheet; boards scroll horizontally.
- Client portal = responsive/mobile-friendly at comfortable density — clients may approve an estimate or read an update on a phone.
- Cmd-K is keyboard on desktop, a search affordance in the mobile top bar.
8. Prototype navigation surfaces to build (maps to Phase 0)
The shell + nav are part of Phase 0 (architecture-decisions.md D1 / STATUS.md step 7):
- App shell — rail (grouped, collapsible) + top bar + peek panel scaffold.
- Role switcher — re-resolves effective role; drives §1.2 rail visibility + gate states.
- Routing — the §5 map with the cockpit/portal shell split.
- Cmd-K — at least Navigate + Create + Switch-role for the demo.
- Project workspace tabs + the four tree views (tree/board/list/timeline) over seed data.
- Client portal shell — even if thin, it must exist so the two-audiences story is demonstrable.
Open follow-ons (tracked in STATUS.md): per-module screen detail (Staff, Billing screen, dashboards per role, capacity, onboarding/invites, AI workflow builder), and the light-theme status-gray contrast tweak.
9. System Activity — the A15 observability surfaces (Owner-only)
How "everything is logged" (architecture-decisions.md A15) becomes navigable. Owner-only (permission-matrix.md §12); absent from every other role's rail (principle 2).
9.1 Placement (design review D2 = hybrid)
- Home: lives under Settings & Admin → System. Routes:
/settings/system/activity·/settings/system/logs·/settings/system/alerts. - Rail: NOT a permanent rail item (keeps the rail at 6 sections, principle 5). But the Owner can pin "System Activity" to the WORKSPACE group (Linear-style favourite) — a prototype affordance that demonstrates the pin pattern and gives the demo one-click reach.
- Per-object History is NOT a separate screen — it's the existing peek-panel activity/audit trail (§2.2), surfaced as a History tab on any Task/Feature/Module/Milestone/Project peek, showing the same audit feed filtered to that object (internal-only entries never reach clients).
9.2 Structure — one System area, three tabs
| Tab | Shows | Pattern |
|---|---|---|
| Activity | Unified feed: audit events (Tier 1) + operational run records (IntegrationRun/JobRun/AiWorkflowRun/WebhookReceipt, Tier 2) + recent errors. Filter by actor / object / source / provider / outcome / time. | Dense table/stream, NOT cards. Mono ids, status-dot outcomes. |
| Logs | Tier-3 live-tailing system log + trace stream (from the external aggregator). | Live-tail stream, mono, severity-colored, pausable. |
| Alerts | Owner-defined alert rules (e.g. "notify #eng when IntegrationRun.xero fails 3×"). | Config-table editor reusing the CQ2 pattern (same as cascade/workflow rules), not cards. Each rule: trigger + threshold + window + channel. A mock "Test rule" affordance. |
9.3 The Trace drawer — the correlation payoff (design review D4)
Every row that carries a trace_id renders it as a monospace chip. Clicking it (anywhere — System Activity, a peek History tab, an error) opens a correlated Trace drawer (reuses the peek-panel slide-over): a single chronological timeline assembling every event sharing that trace_id across all three tiers — click → audit_event → JobRun → IntegrationRun → error/log line — each row deep-linking to its source object. This is the demo's concrete proof that one user action is traceable end-to-end. Deep-link: /settings/system/trace/:traceId (also openable as a drawer over any list).
9.4 States (design review D3 — full matrix, two named cases)
| Surface | Loading | Empty / first-run | Error | Overflow |
|---|---|---|---|---|
| Activity | skeleton rows | "No activity yet" + what will appear here | row-level error chip | "Showing last N — older events in the warehouse" (never silent truncation), paginate/virtualize |
| Logs (Tier 3) | "connecting to log stream…" | "No logs in range" | aggregator-down banner: "Log backend unreachable — Activity & Alerts (from the DB) still work"; Tier-1/2 tabs stay functional | live-tail paused/reconnecting state with a Resume control |
| Alerts | skeleton | "No alert rules — create one" + primary CTA | rule-validation = danger rows (form-validation pattern) | — |
| Trace drawer | "assembling trace…" | "No correlated events" (rare) | "partial trace — log tier unavailable" (shows DB tiers) | — |
| All (zero-permission) | a non-Owner deep-linking any /settings/system/* route hits the standard 403 / not-found (the surface is absent from their nav, never greyed). |
9.5 Responsive & a11y
- Desktop-first (Owner admin, like the cockpit). Below ~1024px: wide tables + live-tail scroll horizontally; Trace drawer becomes a full-screen sheet.
- Outcome/severity conveyed by text + dot, never color alone (colorblind-safe). Trace timeline is keyboard-navigable; the live-tail uses a polite
aria-liveregion but is pausable so a screen reader isn't flooded.
Revido ERP — Status Model (PROPOSAL v0.2 — needs Rodrig's approval)
Two layers: Status Groups (coarse buckets — used by cascade + client views + rollups) and Statuses (fine-grained Kanban columns, per-type, Owner-editable). Cascade rules can target a group OR a specific status (F2). v0.2: Modules & Features now use Rodrig's unified 13-status lifecycle.
Layer 1 — Status Groups (shared across all tiers)
| Group | Meaning | Client sees as |
|---|---|---|
| Triage | Intake — not yet defined/estimated | (hidden / "Reviewing") |
| Backlog | Defined and estimated, not committed | "Planned" |
| To Do | Committed for a cycle, not started | "Up next" |
| In Progress | Active — build → review → deploy pipeline | "In progress" |
| Done | Deployed to prod | "Shipped" |
| Cancelled | Won't do / dropped | (hidden) |
Key shift: the entire pipeline from In Progress through To Deploy lives in the In Progress group. Only Deployed = Done. For an agency, "done" = live in prod.
Layer 2 — Statuses per Type
Modules & Features (Rodrig's unified lifecycle — both use the same set)
| # | Status | Group | Notes |
|---|---|---|---|
| 1 | Triage | Triage | intake, not yet defined/estimated |
| 2 | Backlog | Backlog | defined and estimated (SP range + cost range set) |
| 3 | To Do | To Do | committed to a cycle |
| 4 | In Progress | In Progress | active build |
| 5 | Blocked | In Progress | side-state |
| 6 | PR Open | In Progress | GitHub PR linked & open |
| 7 | Revise | In Progress | bounced back |
| 8 | QA Review | In Progress | QA-gated (only QA exits) |
| 9 | To Merge | In Progress | passed QA, ready to merge |
| 10 | Final Review | In Progress | in Staging — QA reviews features, PO reviews the whole module |
| 11 | Client Review | In Progress | client reviews in Staging — approves before Prod deploy |
| 12 | To Deploy | In Progress | approved, queued for Prod |
| 13 | Deployed | Done | live in Prod |
| 14 | Cancelled | Cancelled |
Task (APPROVED — simplified; devs manage their own)
Tasks are the dev's personal surface. The heavy QA/review/deploy ceremony lives on the Feature, not the task. PR open/merge shows as a badge (GitHub link), not a status.
| Status | Group |
|---|---|
| To Do | To Do |
| In Progress | In Progress |
| Blocked | In Progress |
| Done | (dev's "my part is finished") |
SP attribution (bonuses): SP is "claimed" when the dev marks the task Done, and flips to "earned" only when the parent Feature reaches "Deployed". On deploy, earned SP is credited to the Dev, QA, and PO involved (the delivery triad) — not the dev alone.
RESOLVED → staff-comp.md (A16): Dev earns the delivered SP directly; QA & PO derive bonuses from their own (parked) averaging formulas, not a per-feature split of this SP. Note: task "Done" maps to the In Progress group until its feature Deploys — the task isn't truly "done" (shipped) until the feature is live. (Confirm whether you want a task-level "Deployed" too, or keep tasks ending at "Done" with shipment tracked on the feature.)
Milestone (PROPOSAL — rolls up from Modules)
| Status | Group |
|---|---|
| Not Started | Backlog |
| In Progress | In Progress |
| Deployed / Done | Done |
| Cancelled | Cancelled |
Project (APPROVED — simplified; rolls up from Milestones)
| Status | Group | Notes |
|---|---|---|
| Draft | Triage | created, estimate not signed |
| Planned | Backlog | estimate signed & scoped, delivery not started |
| Active | In Progress | running |
| On Hold | In Progress | paused |
| Completed | Done | proposed when all milestones Done — Owner confirms, never auto |
| Archived | Cancelled |
How cascade uses this
- Default rules on groups:
IF ANY child = Revise → parent = Revise;IF ALL children in Done-group (Deployed) → parent = Deployed. - Cross-tier safe because every status maps to a group (a Task "To Merge" and a Feature "Final Review" are both In Progress).
- Owners may also target a specific status (e.g.
IF ALL children = "To Deploy" → Module = "To Deploy").
Resolved
- Project completion: all milestones Done → system proposes Completed; Owner confirms, never auto.
APPROVED (Rodrig) — status model locked
- 6 groups: Triage · Backlog · To Do · In Progress · Done · Cancelled. Only Deployed = Done.
- Module/Feature = the 14-status lifecycle above.
- Task = simplified (To Do · In Progress · Blocked · Done); devs manage their own; shipment tracked on the feature.
- Milestone = Not Started · In Progress · Done · Cancelled (simplified, approved).
- Project = Draft · Planned · Active · On Hold · Completed · Archived (simplified, approved).
- SP earned at Deployed, credited to Dev + QA + PO (delivery triad).
Resolved → comp engine
- Bonus SP distribution is resolved in
staff-comp.md(A16): Dev earns delivered SP directly; QA/PO derive from their own averaging rules (QA & PO formulas still parked).
Revido ERP — Automation Engines (DRAFT v0.1)
The system runs on configurable engines. Humans declare intent + rules; the system does the bookkeeping. Guiding principle (from Rodrig): remove any manual work that can be automated.
There are (so far) six engines, each configurable, each with its own editor:
| # | Engine | What it governs | Who configures |
|---|---|---|---|
| 1 | Permissions | Who can see/do what (object-level) | Owner |
| 2 | Workflow | Stage-gated manual transitions (e.g. QA-only out of "QA Review") | Owner / PO |
| 3 | Cascade | Automatic bottom-up status roll-up across the tree | Owner |
| 4 | AI Recommendations | Transcript/event → proposed changes for human approval | Owner |
| 5 | Event Automations | System events → automatic status transitions (PR merge, test run result) | Owner |
| 6 | Cadence & Check-ins | Time-triggered scheduled/conditional check-ins (standups, project updates) → collect → compile to digests + client-update drafts | Owner |
Engine 3: Cascade Rules (status roll-up)
Automatic parent status derived from children, evaluated bottom-up across the whole tree:
Task → Feature → Module → Milestone → Project
Rule format (Owner-editable)
Each tier has an ordered list of rules. Format:
SCOPE: <ChildType> → <ParentType>
RULE: IF <quantifier> children match <status> → set parent <status>
Example (Task → Feature), as given:
Task → Feature
R1: IF ANY child = Revise → Feature = Revise
R2: IF ALL children = Done → Feature = Done
Same pattern, Feature → Module:
Feature → Module
R1: IF ANY child = Revise → Module = Revise
R2: IF ALL children = Done → Module = Done
…and onward Module → Milestone → Project (confirm you want it all the way up).
Design subtleties to lock down (these are the real decisions)
A. Rule precedence. Rules are evaluated top-to-bottom; first match wins. That's why ANY Revise must sit above ALL Done — a single Revise should override even if everything else is Done. The editor must let Owners reorder.
B. Fallthrough / partial states. What happens when no rule matches — e.g. some children Done, some In Progress, none Revise? Options:
- (a) Leave parent at its current status (no auto-change), or
- (b) A default rule like
IF ANY child = In Progress → parent = In Progress.
Recommend (b) with an explicit, editable default rule at the bottom of each list.
C. Cascade vs. Workflow gating. A cascade sets status automatically — does it bypass the workflow stage gates (e.g. can cascade push a Feature to Done even though "QA-only" guards that transition)? Recommend: cascade is system-driven and bypasses manual gates, but every auto-change is logged with "set by cascade rule R2" so it's auditable. Confirm.
D. SP / progress roll-up (separate from status). Story points and % complete should also roll up automatically (sum of child SP; % = done SP / total SP). This is computation, not a rule — always on. Status cascade and SP roll-up are independent.
E. Re-evaluation triggers. Cascade re-runs whenever a child's status changes, a child is added/removed, or a rule is edited. Whole affected branch re-evaluates upward.
RESOLVED (Rodrig)
- Cascade to Project — yes, all the way up. Each tier has its own statuses (see status-model.md), so cascade keys on status groups, with the option to write rules against specific statuses too.
- Fallthrough — if no rule matches, leave parent untouched (no auto-change).
- Cascade vs. workflow gates — no conflict. QA performs the gated Task transition (→ Revise / → Ready); cascade simply rolls up the resulting child statuses. Cascade reads, doesn't fight gates.
- Manual pin — none for now. Cascade just runs; overrides considered later.
- Templates — cascade rules live in templates; a template is applied per project. Start with one default template; a custom project = new template.
Engine 2 detail: Workflow transition preconditions (gates carry required artifacts)
A workflow transition is allowed iff (role permitted) AND (all required preconditions satisfied). Preconditions are configurable per transition — they require specific fields/artifacts to be present before the move is permitted. The UI blocks the transition and names what's missing.
| Transition | Trigger / Role | Mandatory preconditions |
|---|---|---|
| → QA Review (feature/module) | cascade auto-advance on last child task Done (A4) | feature- or module-level Loom (required), preview link (required unless auto from PR/Railway), estimate updated — enforced via blocking pop-up, empty = button unclickable |
| → Client Review | PO | client-facing Loom (required) |
Implications:
- The workflow-gate layer (Issue 1, layer c) evaluates role + preconditions, not role alone.
- Artifacts (Loom URL, preview link) are first-class fields on the issue; "preview link" can be auto-filled from the PR/Railway deploy (Event Automations) — only required-manual when absent.
- "Estimate updated" couples task progress to estimate accuracy — a re-estimation checkpoint at QA handoff.
- Prototype spine should demonstrate a blocked transition with a checklist ("Missing: Loom, preview link") — this is more instructive to devs than a role-only greyed button.
Engine 5: Event Automations (system-driven transitions)
Automatic status transitions triggered by external/system events. Owner-configurable mappings.
| Trigger | Effect |
|---|---|
| GitHub PR opened | Linked Task/Feature/Module → "PR Open" |
| GitHub PR merged (deploys to staging) | Linked item → "Final Review" (in Staging) — confirm vs. "To Merge" |
| Test run passes (QA module) | Drives the QA transition forward (e.g. QA Review → To Merge) |
| Test run fails | Blocks the forward transition / bounces to Revise |
Lifecycle reference (Modules/Features): Triage → Backlog → To Do → In Progress → Blocked → PR Open → Revise → QA Review → To Merge → Final Review (Staging) → Client Review (Staging) → To Deploy → Deployed. PR merge lands in Final Review (now in staging); QA/PO review, then client approves at Client Review before To Deploy → Prod.
These feed the same status fields cascade reads, so an automated transition naturally rolls up the tree. Test runs auto-drive workflow + cascade (Rodrig) — QA's pass/fail is an active signal, not just a record.
Engine 6: Cadence & Check-ins (scheduled updates → collect → compile)
The first time-triggered engine. Engines 2–5 react to events (a transition, a PR, a transcript); this one runs on a schedule (plus optional events/conditions) to ask people for updates, collect their answers in-app, and compile them into digests and client-update drafts. Built to remove the manual chase — nobody should be DMing devs "what's your status?".
The four parts
- Templates (Owner creates many). The reusable unit — like cascade templates, you build several versions and assign one (or more) per project. Each template defines:
- Audience — which role(s) it targets (e.g. Devs, PO, PB).
- Cadence — a recurring schedule (daily / weekly / biweekly; day + time + timezone), plus optional event triggers (e.g. milestone Deployed → ask PO for a wrap-up) and optional conditions on project status (e.g. only while Active; pause on On Hold).
- Form — the structured fields to fill (e.g. Shipped · In progress · Blockers · Needs-from-client) + an optional freeform note (same TipTap field component used elsewhere). Yes, forms get properly planned: each field is typed (short text · long text/TipTap · single-select · multi-select · number · checklist · date) with a label, help text, required flag, and (for selects) options. This is a small reusable form-schema / field-builder primitive — not bespoke per template — so the same builder later serves QA defect forms, client intake, and any other structured input. The check-in Response stores values keyed to the template's field IDs (so a template edit never orphans old answers). *(Field-schema ERD →
architecture-decisions.mdA14.)* - Channels — where the nudge is sent (Slack DM · email · in-app). Responses are always answered in-app (Inbox / quick form) so everything lands in one place to compile.
- Compile targets — internal project digest (Owner/PO) and/or AI-drafted client update (always human-approved before send), selectable per template.
- Escalation — re-nudge after a configurable delay, then notify Owner/PO and mark missed.
- Assignment. Select template(s) per project — a project can run several (e.g. a daily Dev standup + a weekly PO client-update check-in). Cadence lives on the template, not hand-set per project.
- Collection. The nudge fires at the template's send-time in the recipient's own timezone (a 9am standup means 9am for each person, not one global clock). Each check-in carries a response deadline defined relative to its own send (e.g. due 4h after sent / by end of that day), set on the template — so the SLA is "you had X to answer," not a fixed wall-clock that's unfair across timezones. The person fills the structured form in-app (Inbox); each response is stored against (project, period, person, role) with its sent-at and due-at stamps. Past due-at with no response → overdue → missed (escalation per the template).
- Compile. AI rolls the period's inputs into the selected outputs — an Owner/PO project digest and/or a client-update draft → human approves (never auto-sent; reuses the proposal-as-diff + approve flow from engine 4).
- Sources (what the compile reads). Two layers: (a) the collected check-in responses (self-reported), and (b) the project's own activity feed — the issues/tasks themselves and their status changes, which already pull from integrations (GitHub commits/PRs/merges, recall.ai call notes, Slack, Ybug — to be replaced by a custom in-house bug-reporting tool later). So a digest cross-checks "what people said" against "what actually moved in the tree" — richer and harder to game than text alone. The check-in form is the human layer on top of the always-on activity layer; the project tree stays the spine both read from. (Integration→issue ingestion lives in engine 4 / the Inbox; the compile just consumes the resulting tree + responses.)
- AI quality check (against our update standards). On submit, AI rates each response for substance vs. a house rubric (specific · names real work · flags blockers honestly). It never blocks the submit, but does two things:
- (a) Author-facing nudge — on submit-attempt, soft, once. When the writer hits Submit on a thin entry, a single inline suggestion appears with **two one-tap choices: Add detail or *Submit anyway*** — no modal, no second nag, fires at most once per response. It reads as a teammate's nudge, not a gate (Submit always wins). Dismiss = submitted.
- (b) Owner-facing signal — a per-response chip on the digest only. Each digest row carries a quiet chip (needs detail / fine) — no numeric score, no ranking, no per-person history. The Owner sees which updates are thin, never a leaderboard of people. (Trust-first: the signal flags an entry, not a person.)
- Pairs with the activity-feed cross-check above — quality is judged against what actually moved, not
- just word count. A single thin entry is coaching-only (the nudge); a chronic pattern of
- needs-detail flags eventually carries a small bonus weight (see Accountability) — substance
- over time, never a one-off ding.
Accountability (ties to bonuses)
Missed check-ins count negatively toward the bonus structure for all roles (Dev / QA / PO), coupling Engine 6 to the SP/bonus model (status-model.md: SP earned at Deployed feed bonuses) — the bonus signal now reflects update discipline, not just shipped SP. The penalty is automatic and weighted small, surfaced on a transparent running tally so it never surprises anyone. **Mechanism (A16, staff-comp.md): each miss subtracts effective SP before the bonus ladder runs** (effective_SP = earned_SP − misses × SP_PER_MISS); because the ladder steps every 20 SP, a miss only costs money when it crosses a tier — naturally "weighted small". Excused/leave never count.
- Excuse path (so the penalty stays fair). A missed check-in is not always a real miss. The Owner can one-tap "excuse" any missed item (reason + audited) for one-offs (sick day, emergency, laptop died); for multi-day absence the person files a retroactive Leave record (request → Owner approves) that excludes the whole range. Excused ≠ missed; neither dings the bonus.
- Chronic thin entries carry a small weight too. A single thin update is coaching-only (the AI nudge), but a pattern — repeated needs-detail flags across a window — eventually carries a small bonus weight, so the lever rewards substance over time, not just submitting something. One-offs never count; only a sustained pattern does, and it shows on the same transparent tally.
CEO note (flagged — your call stands): tying updates to pay is a strong lever but can be gamed (low-effort "done" entries) or dent morale if it dings someone who shipped but forgot a form. Suggestion for the real build: weight it small, count only missed (not "imperfect") check-ins, and show a transparent running tally so it never surprises anyone.
Time off never counts as missed. A person who is OOO for a period is not nudged and that period is excluded from the missed tally / bonus penalty — being on approved leave can never ding you. This needs a source of truth for who's away, so the ERP gets a lightweight Leave record now: anyone can request time off → Owner approves; the approved date range is what Engine 6 reads at fire time (a fired Instance whose recipient is on leave is skipped, not marked missed, and is shown as "on leave" in the digest — never blank, never amber). A future Deel integration can become the upstream source of leave, but is deferred (architecture-decisions.md A14). *(The Leave request/approve surface lives in two places: a person requests from My Work / profile, the Owner approves it as an actionable Inbox item (reuses the check-in/approval pattern), and a dedicated People → Leave admin screen is the management view — all requests, statuses, and the team's upcoming-away calendar in one place. See system-map.md.)*
Who configures
Owner only — builds/edits templates and all cadence. PO/PB consume them: they receive the nudges and see the digests on their own projects.
Where it plugs in (Inbox + clients)
- Inbox is the home for check-ins. Each nudge is an actionable Inbox item for its target (internal or client); they fill the form right from the Inbox; missed / overdue check-ins surface as Inbox items too. Responses are stored against (project, period, person, role).
- Client-directed check-ins & updates. A template's audience can be a client role (Client Admin / Staff) — e.g. a recurring "anything you need from us?" or the compiled progress update.
- Multi-channel out, portal in. Outbound nudges/updates ride each recipient's preferred channel — internal: Slack / in-app / email; clients: Slack / email / WhatsApp — but every message deep-links into the portal, where the form is filled and approvals happen. The standing goal is to build the portal habit (see
system-map.md§8 — client-channel principle). - Compile → Client Updates. The AI client-update draft (engine 4 / the Client Updates module) is the richest consumer of collected responses; Engine 6 is its main input source.
- The template-builder + apply-per-project UX is shared with Cascade — one pattern, learned once.
Real-build notes — period & idempotency (correctness, not infra)
(Prototype is non-functional; these pin the semantics so the real build can't get "missed" wrong.)
- Period & timezone. A check-in belongs to a period (ISO week for weekly, day for standups) computed in the recipient's timezone; the nudge fires at the template's send-time in that recipient's tz, and the response deadline is stored relative to the actual send (
sent_at+ the template's response window →due_at), not a global wall-clock — fair across timezones. - Idempotency. Exactly one Instance per (template, project, period) — scheduler retries or overlapping runs must never double-create or double-nudge (that triple is the idempotency key).
- Conditions evaluated at fire time. Status/conditions (e.g. only while Active) are checked when the period fires, not when the template was assigned to the project.
- Missed = a derived, auditable event, not a mutable counter — so the bonus penalty is reconstructible and disputable (who, which period, when re-nudged, when escalated). An approved Leave record covering the period suppresses the nudge and excludes it from "missed" — checked at fire time, same as status conditions. An Owner "excuse" action (one-off) and a retroactive Leave record (multi-day) both flip a missed event to excused — also a derived, audited event (who excused, when, why), never silently mutated.
- Chronic-thin weight = a derived signal over a window, not a per-entry flag: count needs-detail flags per person over a rolling window; only a sustained pattern crosses the (Owner-set) threshold into a small bonus weight. One thin entry never counts — reconstructible from the stored per-response quality flags.
- Scheduler, the full ERD, and the WhatsApp Business API constraints are real-build decisions — see
architecture-decisions.md→ A14.
Surfaces & states (design review)
- Where it lives (nav). The template builder + cadence config sit in Settings/Admin → Automations, beside Cascade/Workflow (shared builder pattern). Nudges + the check-in form live in the Inbox — an inline structured form in the peek panel (
information-architecture.md§2.2). Digests render on the project workspace → Overview/Updates tab. Clients answer via a portal form deep-linked from their channel nudge (comfortable density, mirrors the internal form). The form-field builder (typed fields: add / reorder / set label·help·required·options) is part of the template builder in the same Automations surface. Leave: requested from My Work / profile, approved as an Inbox item, managed on a People → Leave admin screen (requests + team away-calendar). - Missed is never silent. A missed check-in surfaces in three places — an Inbox overdue badge, the project digest, and a transparent per-person tally in My Work / profile — so the person who can be penalized always sees it first. Uses the warning/orange status color (escalated = danger). This transparency is the fairness mechanism behind the bonus tie-in. **OOO periods render as a neutral "on leave" state — never amber, never counted as missed.**
- State matrix (what the user sees, not backend behavior):
| Surface | Loading | Empty | Error | Missed / Overdue |
|---|---|---|---|---|
| Check-in form (Inbox peek) | skeleton form | "You're all caught up — no check-ins due" + warmth | "Couldn't load this check-in — Retry" | amber "Overdue since {date}" banner, still submittable |
| Project digest (Updates tab) | skeleton rows | "No responses yet this period" + who's still pending | partial: show received, flag the gaps (never blank) | amber row per missed person + a count chip; neutral "on leave" row for OOO (not amber) |
| Per-person tally (My Work) | inline spinner | "No missed check-ins" (positive empty state) | omit (don't alarm on a fetch blip) | amber count + the list of missed periods, each linking to its check-in |
| Leave request (My Work) | skeleton row | "No time-off requests" + Request time off CTA | "Couldn't submit — Retry" (keeps draft) | pending = neutral "Awaiting approval" pill; approved = green; declined = explains why |
| Leave admin (People → Leave) | skeleton table | "No requests yet" + away-calendar empty | "Couldn't load — Retry" | pending requests pinned to top as actionable rows |
Two cross-cutting states-within-a-surface (not their own row): the AI quality nudge is a transient state of the check-in form — appears once on submit-attempt for a thin entry (inline, two one-tap choices), absent otherwise; the thin-quality chip is a per-row state of the project digest (needs detail / fine), never a score. A missed item the Owner has excused (or that a retroactive leave covers) shows as a neutral "excused" state on the digest + tally — visually like on leave, never amber, never counted.
Prototype must show (vision)
The template builder (audience + cadence + form fields + channels + compile targets), assigning a template to a project, a sample nudge, the in-app form a dev fills, a compiled project digest, a generated client-update draft awaiting approval, a missed check-in marked against the bonus tally, the AI quality nudge on a thin response, a person on leave whose check-in is skipped (not missed), and an Owner excusing a missed check-in (one tap → neutral state).
Engine 4 detail: AI Recommendation sources (Inbox ingestion)
The Linear-style Inbox is the ingestion point. Sources feed it; AI proposes changes; humans approve (always — never auto-applied):
- recall.ai — call transcripts
- Slack — @Revido bot mentions
- Ybug — bug reports
- (extensible)
AI proposes: new milestones / modules / features, edits to existing items, new tasks. AI does not estimate — humans set story points.
Billing modes (per Estimate/Proposal — Rodrig F3)
Each estimate/proposal selects how it bills:
- Auto-bill — signing the estimate auto-generates & sends the invoice (Xero).
- Draft for approval — generates a draft invoice an Owner approves before send.
- Manual — Owner bills by hand.
Rate cards (Rodrig F4)
- Rate card can be per story point or per hour, with an explicit currency.
- Default scope: per client → propagates to deal → project.
- Overridable at the deal or project level when needed.
- Drives the cost range shown to clients (SP range × rate).
AI workflows / templates (Rodrig F5)
- Owner-editable templates that run an AI orchestration for a given flow.
- Examples: ERP planner, Startup planner — generate a project's milestone/module/feature tree.
- Different templates for different flows (project planning, etc.); extensible over time.
- Output is always a proposal → human approves.
Revido ERP — Role × Capability Matrix (DRAFT v0.1)
Collaborative working doc. Edit cells directly. Add// noteafter any cell to leave me a comment. This defines the global default per role. Two things sit on top of it: 1. Project-level overrides — a user's effective role on a project can differ from their global role (e.g. a Dev assigned as PO on Project X). 2. Workflow engine — stage-gated transitions (e.g. only QA can move a task out of "QA Review"). See bottom section. Cells marked*are further constrained by workflow stage.
Legend
- F = Full — create / edit / delete / configure
- E = Edit — create + edit (own or assigned), no delete/config
- A = Approve — can sign-off / approve (in addition to view)
- C = Comment — can comment / contribute, not edit the object
- V = View only
- — = No access
*= additionally constrained by workflow stage
Roles
| Code | Role | Audience |
|---|---|---|
| OWN | Owner | Internal — leadership |
| PO | Product Owner | Internal — owns scope & client comms |
| DEV | Dev | Internal — builds tasks |
| PB | Product Builder | Internal — Dev + PO hybrid |
| QA | QA | Internal — testing/sign-off |
| SAL | Sales | Internal — pipeline & SOWs |
| C-A | Client Admin | Client — manages their org |
| C-S | Client Staff | Client — limited view/comment |
1. Projects & Work Tree (Project → Milestone → Module → Feature → Task/Issue)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Create project | F | E | — | E | — | E | — | — |
| Edit project meta (name, client, dates) | F | E | — | E | — | C | — | — |
| Delete/archive project | F | — | — | — | — | — | — | — |
| Create/edit Milestones | F | E | — | E | — | — | — | — |
| Create/edit Modules | F | E | — | E | — | — | — | — |
| Create/edit Features | F | E | C | E | C | — | C | — |
| Create/edit Tasks | F | E | E | E | E* | — | — | — |
| Assign tasks to people | F | E | — | E | — | — | — | — |
| Move task across workflow stages | F* | E* | E* | E* | E* | — | — | — |
| Log story points / time on tasks | F | E | E | E | E | — | — | — |
| View full project tree | F | F | V | F | V | V | V | V |
| View profitability on project | F | V | — | V | — | — | — | — |
2. Sprint / Retainer Planning (scope + velocity, weekly/biweekly commit)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Set velocity per project (e.g. 30 SP/wk) | F | E | — | E | — | — | — | — |
| Define cycle scope (commit next 1–2 wks) | F | E | C | E | — | — | A | — |
| Provide SP estimate ranges | F | E | E | E | C | — | — | — |
| Approve committed scope for cycle | F | A | — | A | — | — | A | — |
| View velocity / burn charts | F | F | V | F | V | V | V | V |
3. Inbox (Linear-style — every new thing lands here)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Personal inbox (assigned, mentions, updates) | F | F | F | F | F | F | F | F |
| Triage / route items to project tree | F | F | C | F | C | C | — | — |
| Snooze / mark done | F | F | F | F | F | F | F | F |
4. AI Recommendations (transcript → proposed task/scope changes)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| See AI-proposed changes | F | F | V | F | V | V | — | — |
| Approve / reject proposals | F | A | — | A | — | A(CRM) | — | — |
| Edit a proposal before applying | F | E | — | E | — | E(CRM) | — | — |
| Configure AI sources (recall.ai etc.) | F | — | — | — | — | — | — | — |
5. Client Updates (AI-drafted, recurring schedule)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| See drafted updates | F | F | — | F | — | C | — | — |
| Edit draft | F | E | — | E | — | — | — | — |
| Approve & send to client | F | A | — | A | — | — | — | — |
| Configure update schedule | F | E | — | E | — | — | — | — |
| Receive update (as client) | — | — | — | — | — | — | V | V |
6. Invoicing (Xero sync — biweekly retainer + fixed scope)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Generate invoice / push to Xero | F | — | — | — | — | — | — | — |
| Edit invoice before send | F | C | — | — | — | — | — | — |
| View invoices (own client) | F | V | — | V | — | V | V | V |
| Pay / mark paid | F | — | — | — | — | — | A | — |
7. Profitability (project + team-member grain)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| View project profitability | F | V | — | V | — | — | — | — |
| View team-member profitability | F | — | — | — | — | — | — | — |
| View own utilization/billable | F | V | V | V | V | V | — | — |
| Configure cost rates | F | — | — | — | — | — | — | — |
8. Sales CRM (own workflow engine)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Manage pipeline / deals | F | V | — | V | — | F | — | — |
| Move deal across CRM stages | F* | — | — | — | — | F* | — | — |
| Create SOW / scope proposal | F | C | — | C | — | E | — | — |
| Convert won deal → project | F | C | — | C | — | E | — | — |
9. Knowledge Base (Obsidian-like, MD, backlinks)
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Read internal KB | F | F | F | F | F | F | — | — |
| Write/edit KB pages | F | E | E | E | E | E | — | — |
| Client-visible KB / docs | F | E | C | E | — | C | V | V |
| Issue-attached MD doc (background) | F | E | E | E | E | — | C | — |
10. People / Capacity
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| View team capacity / allocation | F | F | V | F | V | V | — | — |
| Assign people to projects | F | E | — | E | — | — | — | — |
| Manage internal users | F | — | — | — | — | — | — | — |
| Manage own org's client users | — | — | — | — | — | — | F | — |
11. Admin / Config
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| Configure roles & permissions | F | — | — | — | — | — | — | — |
| Set project-level role overrides | F | E | — | E | — | — | — | — |
| Configure workflow engine (stages/gates) | F | E | — | E | — | E(CRM) | — | — |
| Manage integrations (Xero, recall.ai) | F | — | — | — | — | — | — | — |
| CLI / MCP access token | F | F | F | F | F | — | — | — |
12. Observability / System Activity (A15 — "everything is logged")
Owner-only. The audit/change floor captures everything regardless of these cells; this row governs who can read the System Activity surfaces (System Activity screen, per-object History, live log viewer, alerting config). Per-object History on a project/feature/task is governed by that object's normal view permission + visibility primitive — internal-only audit entries never reach clients.
| Capability | OWN | PO | DEV | PB | QA | SAL | C-A | C-S |
|---|---|---|---|---|---|---|---|---|
| View System Activity (audit + run history + errors) | F | — | — | — | — | — | — | — |
| View per-object History timeline | F | V | — | V | — | — | — | — |
| Live-tailing log viewer | F | — | — | — | — | — | — | — |
| Configure alerting rules | F | — | — | — | — | — | — | — |
Workflow Engine (stage-gated transitions) — example
Project task workflow (configurable):
Backlog → Scoped → In Progress → QA Review → Client Review → Done
| Transition | Who can perform |
|---|---|
| Backlog → Scoped | PO, PB, Owner |
| Scoped → In Progress | Dev, PB, assignee |
| In Progress → QA Review | Dev, PB, assignee |
| QA Review → In Progress (reject) | QA only |
| QA Review → Client Review | QA only |
| Client Review → Done | PO, PB, Owner (or Client Admin approves) |
CRM workflow (configurable, separate):
Lead → Qualified → Proposal → Won/Lost
| Transition | Who can perform |
|---|---|
| any stage move | Sales, Owner |
RESOLVED (Rodrig's answers)
- Client approvals — clients approve specific things, not "scope" generically:
- Sign/approve the Estimate (Estimate = Modules + Features only, no tasks). Signing can auto-generate the first invoice (per billing mode).
- Move items Backlog → To Do — this drag is an approval to start work.
- Story-point ranges show on all backlog items; depending on the client's rate card, a cost range is also shown to inform them.
- Roles are uniform across projects. The role in the user table is just the default when adding a user to a project; it can be changed within any project (project-level override).
- Sales can see the projects they brought in, but view-only.
- PO/PB do NOT see money. They see efficiency metrics, project health, etc. — never profitability or costs.
- Client Staff can request new features; only Client Admin can approve them to be started.
- Project-level override = full role powers. A Dev made PO on Project X gets full PO powers on that project.
Net change to the matrix above: money columns close to PO/PB (replace project-profit "V" with "—", add an "efficiency/health metrics" capability they DO get); QA treated per-project like everyone else (no global QA); Sales delivery view is V on their own projects only; Client Staff gets a "request feature" capability (Admin approves).
Carried into the data model
- Effective role = global default role unless a project-level override exists → resolve per (user, project).
- A project override grants the full target role, not a subset.
- Estimate is a first-class, client-signable object (Modules + Features + SP ranges + cost ranges).
Revido ERP — QA / Testing Module (DRAFT v0.1)
Source: Rodrig ↔ Bernadette (QA) call transcript, 2026-05-29.
Purpose
The QA module is an active part of the system (test runs drive workflow/cascade), plus the home for QA performance metrics that feed bonuses.
Core entities
- Test case — a single check (manual or automated). Authored by QA (often AI-generated).
- Test suite — a group of test cases.
- Test run — execute a suite; one click runs all automated (Playwright) E2E; reports pass/fail.
- Linked to Features / Tasks / Modules and PRs (attach freely).
- Environments to test — per task/feature: list of environments (e.g. native iOS, native Android, web, BubbleGo). Multi-environment tasks get a larger QA time budget.
Test runs drive workflow (Event Automation)
- Test run passes → advances the QA transition forward.
- Test run fails → blocks forward transition / bounces to Revise.
- QA creates Playwright E2E tests (AI-assisted: record screen → generate test → runs automatically). Creating tests is rewarded (bonus lever) because it prevents escaped bugs.
QA gate (confirms A3 / A4)
- Entering QA Review triggers a blocking pop-up: paste Loom link + preview link. Empty → button unclickable. Loom is at feature/module level (A4).
QA performance metrics (bonus drivers)
- QA cycle time — time an item sits in QA Review.
- Target: 1 business day average (small bugs ~1d; multi-environment tasks ~2d).
- Hard limit: 4 business days. Exceeding 4d = a problem → signals the task was too big (product side must split tasks; "how big a task can be" is a product-side limit, not QA's fault).
- Business days only — no weekends. (Don't set a client expectation of weekend work.)
- Escaped-bug rate — bugs caught in production by the client (or anyone) on items that passed QA. Target: < 10% of passed tasks.
- E2E tests created — count of automated Playwright tests authored (reward for building coverage).
Bug dispute / escalation path
- When a dev claims "not a bug — fixed on another branch," QA should NOT spend time arguing.
- QA moves the item to Revise AND escalates to the lead/PO (Rodrig) to adjudicate.
- Rationale: QA protects the client's interest over Revido's; QA jumps to the next task.
- Branch-specific bugs must not unfairly penalize QA's KPIs — escalation removes the dispute from QA's cycle-time/escape metrics while it's adjudicated.
Open / to design
- Exact bonus formula combining cycle-time, escape-rate, and tests-created.
- How "environments to test" budget adjusts the 1-day target (e.g. +1 day per extra environment).
- Where escalations live (a queue for the lead) and how they pause the QA clock.
- Per-project testing constraints (e.g. BubbleGo-only) captured as a project/feature attribute.
Revido ERP — Dashboards (role-specific landing)
/dashboard is the home screen for every internal role; /portal/home for clients. This doc specs the fixed, curated dashboard per role (decided 2026-05-30 — not user-editable). It expands the one-line sketch in information-architecture.md §4 to widget level.
Principles
- Fixed & curated per role. Each role lands on a hand-designed layout; users can't rearrange it. Chosen for clarity and a clean role-switcher demo — flipping the switcher visibly swaps the whole dashboard, which is the prototype's core proof. (If personalization is wanted later, it becomes a widget-grid feature — not now.)
- Cross-project home. The dashboard aggregates across a person's projects; the per-project modules live inside
/projects/:id. The dashboard answers "what needs me, everywhere?" - Every card is a drill-through. Clicking a card opens the relevant module pre-filtered (e.g. Blocked items → My Work filtered to Blocked; At-risk projects → Projects filtered to health=at-risk). No dead stats.
- Auth-aware by construction. Cards render through the same 4-layer auth resolver (
architecture-decisions.mdA1) + visibility primitive (CQ1). A card never shows data the role can't access. Money cards exist only for Owner — PO/PB/Dev/QA/Sales never see project or employee profit (permission-matrix.md§7); they get efficiency/health instead. - Effective-role aware. A Dev who is PO on Project X (project override, A1) sees PO-flavored cards scoped to X. The dashboard reflects effective roles, not just the global role.
- States on every card. Each card specs loading / empty / error / zero-permission / first-run (new project, no data yet → guidance, not a blank box). Reuses the states matrix.
- Not the audit surface. The Owner's System Activity observability screen (
A15, Owner-only) is a separate admin surface, not a dashboard card.
Card library (reused across every fixed layout)
Fixed layouts still compose from one shared card library — build the card once, place it per role.
| Card type | Shows | Drill-through target | Money-gated |
|---|---|---|---|
| KPI stat | one number + delta vs prior period | the source module, filtered | depends |
| Trend sparkline | a metric over recent cycles (velocity, utilization) | full chart | no |
| List / queue | top-N items needing action (tasks, PRs, approvals) | the list, filtered | no |
| At-risk list | projects/features flagged by health rules | Projects, health filter | no |
| Approval queue | items awaiting this user's sign-off | the item peek | no |
| Pipeline funnel | deals by CRM stage + value | CRM, stage filter | value = Sales/Owner |
| Capacity heatmap | who's allocated/free next cycle | People / Capacity | no |
| Profit tile | period profit, per-project / per-employee | Profitability | Owner only |
| Velocity chart | committed vs delivered SP per cycle | Planning | no |
| Check-in digest | Engine 6 compiled updates + missed tally | project Updates tab | no |
| Inbox summary | unread/assigned count from the Inbox | Inbox | no |
| Recently shipped | features Deployed in the last period | project tree, Deployed filter | no |
The fixed layouts, role by role
Order = top-to-bottom priority. The role-switcher demo should make the difference obvious: flip Owner→QA and the money/profit cards vanish, the QA queue takes the top.
Owner — "the whole business at a glance" (everything, incl. money)
- KPI row: Active projects · Team utilization % · This-cycle velocity vs target · Retainer/MRR burn · Outstanding invoices (value).
- Profit tiles (Owner-only): Company profit this period · Per-project profit leaderboard · Per-employee cost & profit.
- At-risk projects (health rules) · Velocity trends (sparkline) · Capacity heatmap (who's free next cycle).
- Approvals awaiting me · Check-in digest roll-up (all projects, Engine 6) · Escaped-bug rate (
qa-module.md) · Inbox summary.
PO / Product Builder — "are my projects healthy and unblocked?" (NO money)
- My projects health (status + at-risk) · Cycle commitments vs progress (velocity chart).
- Items awaiting my approval: cycle scope · Client-Review sign-offs · client feature-requests to start.
- Client-update queue (Engine 6 drafts to approve & send) · Check-in compile per project.
- Efficiency metrics (velocity, cycle time, throughput — not profit) · At-risk features · Blocked items needing unblock.
Product Builder = the above plus the Dev "My work" cards for tasks they personally own.
Dev — "what do I work on next?"
- My tasks by status (mini board) · My SP this cycle (claimed vs earned).
- Blocked items · PRs awaiting action · QA bounce-backs (Revise assigned to me).
- My check-in nudges (Engine 6, due/overdue) · Upcoming due · My utilization (own only, no project money).
QA — "what needs testing, and is quality holding?"
- QA Review queue (cross-project, the only role that can exit it — workflow gate).
- Failing test runs (drive workflow/cascade) · Re-test / Revise queue · Items in Final Review awaiting QA.
- Cycle-time KPI · Escaped-bug rate trend · My check-ins.
Sales — "where's the pipeline and what's stuck?"
- Pipeline funnel (deals by stage + value) · Deals needing action / follow-ups due.
- Estimates awaiting PO estimation · Estimates awaiting client signature.
- Won this period · AI lead-research suggestions (Inbox) · Own-project delivery (view-only health on projects they brought in).
Client Admin — /portal/home (friendly, approval-centric)
- Recently shipped (what moved, plain language) · Progress overview (their projects — SP and friendly % per transcript).
- Approvals waiting: sign estimate · move Backlog→To Do · approve Staff feature-requests.
- Invoices (due / paid) · Request a feature CTA · Latest update · Up next / planned.
Client Staff — /portal/home (view + request only)
- Recently shipped · Progress (view).
- Request a feature CTA (routes to Client Admin to approve) · Latest update · Comment activity.
- No invoices, no approvals (Admin-only).
States & first-run
- Loading: skeleton card (no layout shift). Empty/zero-data: guidance ("No projects yet — create one" / "No items awaiting you"), never a blank box. Error: card-local error + retry, the rest of the dashboard still renders (one failed card never blanks the page). Zero-permission: the card is absent (hidden ≠ disabled), consistent with nav-as-permission-projection.
Cross-refs & ownership
- This repo (prototype): the depicted dashboards — all 8 fixed layouts + the shared card library.
- Money gating follows
permission-matrix.md§7; client views follow §1/§5 visibility. - Engine 6 (
automation-engines.md) feeds the check-in digest/update cards;qa-module.mdfeeds the QA/escaped-bug cards; A15 System Activity stays a separate Owner-only admin surface. - Real-build owns the metric queries, health-rule definitions, and caching for the aggregations.
Revido ERP — Staff / People: Compensation Engine (DRAFT v0.1)
Source: Rodrig, contract clause + CEO review 2026-05-30. The QA quality side draws on the Rodrig ↔ Bernadette (QA) call, 2026-05-29 (see qa-module.md).
Purpose
Turn delivered story points into dollar bonuses on a fixed 4-week calendar, per person, auditable enough to settle a payday dispute. This is a compute engine over existing data, not a new tracking system: it reads the SP-earned-at-Deploy events the status model already produces (status-model.md: SP "claimed" on Done → "earned" when the parent Feature reaches Deployed, credited to the Dev + QA + PO delivery triad) and the Engine-6 missed-check-in tally (automation-engines.md), and prices them.
Contract requires auditable records ("Company shall maintain accurate records … available to Contractor upon reasonable request"). That makes immutable period snapshots a hard requirement, not a nice-to-have.
0. First principles (locked)
- A story point is value, never time. SP measures what work is worth — the client-accepted expertise value — deliberately decoupled from how many hours Revido spends. Example: work other agencies would call 40 SP, Revido may do in 5 hours and still bill 30 SP, because the client pays for expertise, not time. No time-tracking ever touches SP work (time tracking lives only on legacy hourly projects,
system-map.md). "A story point is a story point" = a consistent value scale across the company, anchored to one average rate card. - Delivered = accepted + deployed. Only SP that is accepted and live in prod is bonus-eligible (
status-model.md). Phantom points never pay. - Bonus is an output piece-rate, not a base multiplier. Equal delivered points earn equal dollars regardless of base salary. Seniority shows up as more / harder points, not a bigger per-point payout. (Decision D8, 2026-05-30.)
1. The Dev ladder (LOCKED)
Base
- Dev base example: USD 350/week, paid biweekly → USD 1,400 per 4-week period.
- Actual dev bases span ~$300–$600/week. Base sets floor pay only; it does not scale the bonus (see §0.3).
- Config (LOCKED): base is Owner-configurable per person, with a company default that pre-fills new-hire contracts;
TIER_VALUEis Owner-configurable company-wide (default $210). The contract snapshots both values at signing — so the contract always matches the engine and changing a default never retroactively alters a signed contract.
Bonus ladder
Per 4-week period, on delivered (accepted + deployed) SP:
tiers = SP >= floor ? floor((SP - floor) / step) + 1 : 0 # Dev default: floor 80, step 20
bonus_$ = tiers * tier_value # per-role config (see "Ladder config — per role" below)
| Delivered SP | Tiers | Bonus | Total (base $1,400) |
|---|---|---|---|
| 0–79 | 0 | $0 | $1,400 |
| 80–99 | 1 | $210 | $1,610 |
| 100–119 | 2 | $420 | $1,820 |
| 120–139 | 3 | $630 | $2,030 |
| +20 SP | +1 | +$210 | … |
Canonical rule: reaching the 80-SP threshold earns the first $210; each further complete 20 SP earns another $210. The example table above is the source of truth. The original contract prose ("each additional complete 20 SP above 80") reads one tier stingier and gets reworded to match the table when contracts are refreshed.
Ladder config — per role (LOCKED, refines D5/D7/D8 + D13)
The ladder is **{floor, step_SP, tier_value}, Owner-configurable separately per role** (Dev / QA / PO):
| Role | floor (default) | step | tier_value (default) | notes |
|---|---|---|---|---|
| Dev | 80 SP | 20 SP | $210 | snapshot to each dev's contract at signing |
| QA | 80 SP | 20 SP | Owner-set (default $210) | runs on QA_effective_SP (§7) |
| PO | summed expected velocity of active projects | 20 SP | Owner-set (default $210) | runs on PO_effective_SP (§8) |
tiers = effective_SP >= floor ? floor((effective_SP - floor) / step_SP) + 1 : 0
bonus_$ = tiers * tier_value # floor, step_SP, tier_value all per-role config
- **Flat within a role — every dev shares the dev ladder; seniority shows up as more points, never a bumped ladder. The "flat company-wide $210" from D8 is now the Dev default; QA and PO ladders are independently tunable** by the Owner (D13, 2026-05-31).
- For a $350-base dev, $210 = 15% of the 4-week base, so the Dev default matches existing contracts — the "15% of base" language is the dev instance of
$210/tierand is reworded to$210/tiergoing forward. - Rejected: 15%-of-own-base (higher-base people earn more for identical output — double-rewards seniority, which points already reward).
- The contract snapshots the person's role-ladder at signing, so retuning a role default never retroactively changes a signed contract (§1 config rule).
2. Period calendar & proration (LOCKED)
- 4-week periods are company-wide and calendar-aligned (e.g. a cycle ending Jun 7 → next starts Jun 8). 13 periods/year. Bonus is calculated and paid at each period close, with the base.
- Leave / partial availability does NOT scale thresholds — it pushes the window. Approved leave pauses the person's bonus clock: the period-close is pushed out by their leave days, so they keep the full 80-SP floor and the full equivalent working-time to hit it.
- Consequence (spec'd, accepted): a person on leave has a bonus window that shifts off the company calendar — their payout date and period boundary move, and SP delivered in the pushed-out days count to this period, not the next.
- Implements the existing promise "approved leave never dings a bonus" (
automation-engines.md,system-map.md). The leave/excuse model already knows the days (Engine 6 + Owner excuse path).
- Mid-period new hires (e.g. Bernadette starting Jun 8) and part-timers are the same case: the window is the available working-time, not the calendar.
3. Missed check-ins (LOCKED)
- Engine 6 missed check-ins subtract effective SP before the ladder runs (decision D10): ``
effective_SP = earned_SP (± adjustments §5) − (misses × SP_PER_MISS) SP_PER_MISS= 10 (LOCKED default, D13; Owner-configurable). Excused misses and approved-leave periods never count (automation-engines.mdexcuse/leave model).- Property: because the ladder is lumpy in 20-SP steps, a miss only costs real money when it pushes someone across a tier or below the floor — naturally the "small bonus weight, transparent, auditable tally" the docs already describe. Reconstructible from the stored per-response data, never a mutable counter.
4. Ledger: live preview + immutable snapshot (LOCKED, D1)
- In-period: a live preview card shows each person their running SP vs. the next tier ("claimed vs earned",
dashboards.md) — the anti-surprise mechanism that keeps the scheme fair. - At period close: snapshot every person's delivered SP, effective SP, tier, penalties, modifiers (§6), and dollar payout into immutable rows, audited via A8/A15.
- Closed periods are never mutated. Every later correction is an append-only adjustment in the current open period (§5) — the same way real payroll handles a prior-period error. This is what makes a snapshot dispute-proof.
5. Corrections & adjustments (LOCKED)
An append-only adjustment ledger, typed, all entries landing in the current open period, audited (A8/A15). Closed snapshots stay frozen.
| Type | Trigger | Effect |
|---|---|---|
| clawback | A genuine escaped bug — work that passed QA and the client/anyone hit in prod — on SP credited in a prior period. Adjudicated via the existing bug-dispute escalation path (qa-module.md); branch-disputed or trivial bugs do not trigger it. | Negative SP/$ adjustment on the responsible person, current period. (Decision D2.) |
| transfer | Dev B fixes Dev A's shipped feature. | Paired −X% off Dev A / +X% onto Dev B, capped at the feature's original credited SP. Default 50%, configurable, Owner override per case, logged. (Decision D3.) |
| correction | Owner fixes a data/entry error. | Signed adjustment, logged. |
Mechanism is uniform: never edit a closed period; post the dated, audited delta in the open one. Forgive-everything (pay twice for the same feature) and always-claw-back (punishes honest misses) were both rejected.
6. Anti-inflation: estimation governance (LOCKED, D11)
The bonus pays dollars per delivered SP, so whoever sets the points is setting payroll. Both the dev (earns the task SP) and the PO (bonus derives from project SP) are beneficiaries, so neither can be the independent check. The control is data + Owner-by-exception + a structural counter-incentive, not a conflicted human.
Anchor = reference-class value + client acceptance (NEVER time). Comparing claimed SP to Revido's actual hours would punish speed — Revido's core edge — so it is forbidden. Instead:
- Bonus pays only on accepted + deployed SP — to game it you must over-point work that genuinely ships and the client/QA accept, a narrow target.
- Reference-class calibration (the data watchdog). Each estimate is scored against a library of what comparable shipped work was worth + team-level drift (points-per-shipped-thing creeping up). Deviations auto-flag. Comp-neutral — the data earns no bonus.
- Client cost-range signing brakes client work: SP × rate card = the client's cost range, which the client signs (
system-map.md, estimates are client-signable). Inflate → the bill rises → the client pushes back. A free, value-based external check. - Capped consistency modifier (the counter-incentive, applies to dev AND PO). An estimate-consistency metric (claimed vs. reference-class value — an efficiency/health metric POs are allowed to see,
permission-matrix.md#4, not money) modifies the bonus within a capped band. Chronic over-pointing measurably costs you; one miss can't gut your pay. Because it hits the PO too, it neutralizes dev↔PO collusion. - Workflow: dev proposes the estimate (their expertise) → PO locks it at scope/commit time (workflow gate, not the inflation control) → frozen number drives the bonus → post-lock changes audited + Owner-approvable → Owner reviews flags by exception, not every ticket.
End-state (real build): once the reference library is trusted, pay on calibrated SP directly, so inflation literally cannot move the payout. The capped modifier is the bridge to that.
bonus = ladder( effective_SP ) × consistency_modifier(capped)
where effective_SP = accepted+deployed SP ± adjustments − missed-checkin SP
7. QA bonus (LOCKED, QA live session 2026-05-30)
QA does not ship SP directly; the bonus derives from the devs they oversee. Per-project QA (no global QA, permission-matrix.md #2), so "overseen" = the devs whose work this QA gated this period.
QA_effective_SP = mean over overseen devs of ( devDeliveredSP × coverage_of_that_dev )
QA_bonus = ladder( QA_effective_SP ) × quality_modifier(capped)
coverage_of_that_dev = SP of that dev's features this QA gated / that dev's total delivered SP
Run through the same $210 ladder, 80 floor, window-push proration, and pre-ladder missed-check-in SP penalty as devs — QA_effective_SP is deliberately dev-comparable, so a QA whose devs average ~100 effective SP earns ~one dev tier.
**Why x coverage, not / coverage (finding QA-1).** The original / coverage is algebraically degenerate: reviewedSP / coverage = devTotal, so coverage cancels and does nothing. To make partial review actually reduce credit, the operation is x coverage. This is bounded (QA never structurally out-earns devs) and auto-partitions a dev across multiple QAs by coverage (70% + 30% = 100%, no double-count — kills the overlap problem).
Why mean, not sum (QA-1). Mean keeps QA dev-comparable on the shared ladder ("similar bonus to the devs"). A QA's realistic capacity is ~one dev-load of careful review, so mean is the right grain; breadth gaps are caught by the quality modifier (spread thin -> escapes rise -> modifier docks).
Quality modifier (QA-2). A single capped modifier on the bonus, from qa-module.md metrics: escape-rate (<10% target) and QA cycle time (1-day target / 4-day hard limit, business days) pull it down when breached; E2E-tests-created pulls it up — all within a band so no single metric nukes the bonus. Same shape as the §6 consistency modifier and §3 penalty (one consistent mechanism). The bug-dispute escalation path (qa-module.md) shields QA from branch-specific bugs. This rewrites qa-module.md's open "exact bonus formula" item — the metrics are the modifier, not the driver.
Data note: coverage is computable from the QA-Review transitions already recorded — no new tracking.
8. PO bonus (LOCKED, QA live session 2026-05-30)
PO owns project outcomes, so the bonus is project-grain: the total delivered SP across the projects they oversee.
PO_effective_SP = sum over the PO's projects of ( delivered project SP )
PO_bonus = ladder_PO( PO_effective_SP ) × consistency_modifier(capped)
ladder_PO = the same $210 tier steps, above an Owner-configurable PO FLOOR
(default = summed expected velocity of the PO's active projects)
Why sum, not average (PO-1). Any average penalizes breadth — adding a below-average project drags the mean down, silently telling POs to drop small clients (bad for a retainer agency). Sum is count-neutral: a fifth small project just adds its delivered SP; you're never punished for taking it, and you can't game it by hoarding projects you can't ship (only delivered, accepted SP counts, §0.2/§6, and a PO's capacity is fixed). The cost is calibration — a PO's floor can't be a single dev's 80, so it's Owner-configurable, defaulting to the summed expected velocity of their active projects so the bar tracks their real portfolio.
Why PO sums but QA averages. Different grain: a PO orchestrates multi-dev projects (several dev-loads), a QA gates individual-dev work (~one dev-load). The asymmetry also tracks influence over assignment — a PO can push back on taking projects (so the incentive matters), a QA mostly receives whatever flows to them.
Anti-inflation: PO is subject to the §6 capped consistency modifier — a PO who lifts their own sum by waving inflated estimates through pays for it in the modifier. Same window-push proration, missed-check-in penalty, and adjustment ledger as everyone.
9. Payout & Deel integration (boundary)
Deel is the system-of-record for contracts + contractor payment. This engine computes; Deel pays.
- Upstream (Deel → ERP): per-person base and contract terms are authored in Deel; the ERP base config (§1) syncs from Deel as the upstream source (was flagged "deferred" in
system-map.md; now in-scope as the comp source-of-truth). The ERP owns the bonus computation (SP ladder, modifiers, adjustments) that Deel does not model. - Downstream (ERP → Deel): at period close, the immutable snapshot (§4) + open-period adjustments (§5) are the export of "what to pay this period." Disbursement happens in Deel.
- Idempotency (NON-NEGOTIABLE, mirrors A10/Xero): each payout export carries an idempotency key per (person, period); a retry must never double-pay. Adjustments export as their own keyed lines. Surfaced as a Deel
IntegrationRunin A15 Tier 2 (adddeelto the provider list). - Ownership: the engine + snapshot/adjustment ledger = this repo's spec; the Deel sync + disbursement = real build.
10. Bonus transparency surface (REQUIREMENT — design in review 2026-05-31)
Rodrig: "It should be super clear to every team member where they stand with the bonus right now and what they can do to get to the next level — and the Owner should have an overview for all."
Principle: the bonus only motivates if it is legible in real time. The §4 live preview is the seed; this is the full surface.
CEO scope (LOCKED 2026-05-31):
- CEO-T1 — Visibility: self-private + Owner-sees-all. Each member sees only their own standing; the Owner sees the roll-up for everyone. Pay stays private (tier→$ is a known formula, so any peer view of SP would leak relative pay — a one-way-door we don't open). Optional non-pay team velocity board (points only, no names/dollars) is a separate future surface, not this one.
- CEO-T2 — Fully actionable (not just status). The member view includes: current tier + distance to next tier, the highest-leverage lever for their role, a live what-if simulation ("finish these specific open tasks = N SP → cross into the next tier (+$tier_value)"), projected end-of-period payout, trajectory vs last period, and a transparent drag breakdown (exactly how much the consistency/quality modifier and missed-check-in SP penalty are costing them — fairness is the point). Per-role levers: Dev = "ship N more SP"; QA = "raise coverage on dev X / reduce escapes"; PO = "portfolio is N SP under floor".
- Owner view: roll-up across everyone — current standing, projected payout, at-risk/under-floor flags, and aggregate modifier/penalty drags. Owner-only (
permission-matrix.md; PO/PB still see no project profit, but every member sees their own pay).
Design (LOCKED — /plan-design-review 2026-05-31)
IA / placement (D4). Reuse the established card-drills-to-page convention (dashboards.md):
- Member: the existing "My SP this cycle (claimed vs earned)" dashboard card is upgraded into the bonus summary (tier bar + projected payout) and drills to a full **
/me/bonus** page (the what-if simulation + trajectory + drag breakdown). No new top-level nav item (subtraction default); promote to a nav entry later only if members can't find it. - Owner: a roll-up card on the Owner dashboard drills to People → Compensation (Owner-only, money-gated) — sits alongside where comp config already lives.
Distance-to-next-tier visualization (D2). A segmented horizontal tier-progress bar — each segment = one tier_value ($210) tier, filled to current effective-SP — with the hero line "N SP → +$210" above it. Lavender (--primary #5e6ad2) active segment; a tier locks to success-green (#27a644). Calm, scannable in <3s, no gamified decoration (stays Linear-native).
Pace-forward framing (D3) — the load-bearing states decision. SP accrues over the 4 weeks, so early-period everyone is under-floor. The view leads with the projection, not the raw number: primary line = projected end-of-period payout + an on-pace / behind-pace pill; current SP is the secondary line. Under-floor early reads "on pace"; under-floor late flips the pill red with an honest "behind pace — ship N SP to clear the floor." New hires (pushed window, §2) get the same pace framing, never a scary empty wall.
States table (member view):
| State | What the member sees |
|---|---|
| Loading | Skeleton bar + skeleton stat (no layout shift). |
| Early-period (under-floor, on pace) | Projected payout + green "on pace" pill; bar shows progress; "ship as usual." |
| Late-period, behind floor | Red "behind pace" pill + "ship N SP to clear the floor ($210)"; bar shows the gap. |
| In a tier, climbing | "Tier 2 · $420 secured · 8 SP → +$210"; filled segments green, active segment lavender. |
| First-run (new hire) | "Your first period: M business days, started {date}" + pace framing on the pushed window. |
| Empty / no data yet | Guidance ("Your bonus updates as work deploys"), never a blank box or a bare $0. |
| Error | Card-local error + retry; the rest of the dashboard/page still renders. |
| Post-payout (period closed) | Frozen snapshot summary ("Last period: $1,820 paid") + the new period's fresh bar. |
| Zero-permission | Card absent (hidden ≠ disabled) — but every member always has their own bonus view. |
What-if simulation interaction. On /me/bonus: a list of the member's open/in-flight work with SP estimates; selecting items advances a ghost fill on the same tier bar and updates the hero line ("finish these 2 (14 SP) → cross into Tier 4 (+$210)"). Read-only projection — selecting never changes real state. Empty case (no open work): "No open work to simulate — your projection is your current pace."
Drag breakdown (transparency). An expandable row under the bar: "Base from delivered SP: …", "Missed check-ins: −20 SP (2 × 10)", "Consistency/quality modifier: ×0.96" — each line links to its source so it's auditable, not a mystery deduction.
Money-gating. A member sees their own dollars (it's their pay). PO/PB still see no project profit elsewhere (permission-matrix.md #4) — this surface only ever shows self comp. The Owner roll-up is Owner-only.
Mobile + a11y. People will check this on a phone: the tier bar reflows full-width, hero line stacks above it, what-if list is a tap-to-toggle sheet. Touch targets ≥44px. The on/behind-pace state is never color-only — the pill carries a text label ("On pace" / "Behind") so it survives colorblindness and grayscale. Bar segments meet ≥4.5:1 against the surface ladder in both themes.
Design-system fit. All tokens from design-system.md (lavender accent, surface ladder, hairlines, Inter + Geist Mono for the SP/$ figures). No new component vocabulary beyond the tier bar, which is a segmented variant of the existing progress/velocity patterns.
Per-project SP visibility (LOCKED — CEO review 2026-05-31)
A breakdown of SP by project/client so each person feels their personal stake in each client. "Acme is 40% of my points" is a sharper retention nudge than any team-level total (see §11).
**Visibility model (consistent with CEO-T1 self-private pay + permission-matrix.md money-gating):**
| Viewer | Sees |
|---|---|
| Owner | Everyone's SP per project, all projects, + company per-client totals (revenue concentration — Owner-only). |
| PO / PB | Full project for projects they oversee — every contributor's SP on those projects (already visible via the project tree; no money/profit, #4). Own SP per project elsewhere. Effective-role aware: a Dev who is PO on Project X sees full Project X. |
| Dev / QA / Sales / Client | Their own SP per project only. No one else's. |
- Company per-client totals stay Owner-only — broadcasting "Acme = 40% of delivery" to all 14 people exposes revenue concentration that can walk out the door. Easy to open later if wanted.
- **Per-role "my SP per project" = the SP that feeds your bonus, attributed by project:**
- Dev = your delivered (earned ± adjustments §5) SP on each project.
- PO = each of your projects' total delivered SP (your
PO_effective_SPbasis, §8, already per-project). - QA = your coverage-weighted gated SP (§7) grouped by the project the devs worked on.
- Reflects accepted+deployed SP post-adjustment (clawbacks/transfers §5 included), same currency as everywhere else. Shows a per-project trend across recent periods, so a client ramping up or shrinking is visible (ties to §11's ramp mechanism).
- States: a project you're on with 0 SP so far → "0 this cycle" (not hidden). Clients never see any of this (internal comp;
permission-matrix.md§1/§5 visibility). - Surfaces: member breakdown on **
/me/bonus("My SP by client"); Owner matrix + per-client totals on People → Compensation**; optional dashboard card "My SP by client this cycle" (dashboards.mdreuses velocity/recently-shipped data).
11. Why the model aligns the team (retention & usefulness rationale)
Captured from the office-hours stress-test (2026-05-31). This is the narrative for why the comp model points the team at the right behavior, and where it doesn't.
- A delivered SP is client-gated value, not vanity throughput. It passes three client checkpoints: the client selects it (Backlog → To Do = "spend my budget on this",
permission-matrix.md), price-agrees it (signs the cost range), and accepts it (Client Review before deploy). So "ship more SP" = "convert more of the client's budget into accepted value" = revenue. An obnoxiously padded scope dies at selection. - **Delivered ≠ useful, and the gap lives in selection.** SP measures delivered-as-scoped, not did-we-scope-the-right-thing. The "useful" judgment is the client's at selection time, plus Revido's advisory judgment in shaping the backlog. The dev SP bonus is correctly blind to selection quality.
- Retention makes "build the right thing / steer clients well" self-interested — via continuity + ramp. The same team continues with a client, so churn dries up their own future SP stream. And because client SP volume is a ramp, not a step (clients start small and grow as trust builds), losing a mature/ramped client and backfilling with a new small one costs the team real SP-earning capacity even when Revido is supply-constrained — you trade a high-SP book for a low-SP ramp. This closes the demand/supply gap: retention pressure is real on individuals even with a waitlist. The per-project SP visibility (§10) makes this stake concrete and personal.
- Open levers (not solved by the comp math, written down honestly):
- Advisory steering (talking a client out of a bad 400-SP request into a sharper 120-SP one) lowers this period's SP while raising renewal odds. It's rewarded only indirectly, through retention/continuity. Acceptable as long as that loop is felt; revisit if it isn't.
- Attractiveness to work inside is separate from the metric being sound. The penalty/surveillance gestalt, the floor-as-wall risk, and the missing mastery/growth layer are untouched by any of the above. Assignment (open): show the full surface — ladder + penalties + drag breakdown — to one real dev and one real PO and watch their unguided reaction ("I can see how to hit the next tier" vs "you're tracking and docking me"). That observation is the only thing that answers it.
Locked decisions (CEO review 2026-05-30)
| # | Decision |
|---|---|
| D1 | Live in-period preview + immutable snapshot ledger at 4-week close; closed periods never mutated. |
| D2 | Post-close defects → gated clawback (genuine escaped bugs only, via escalation path). |
| D3 | Fix-up = paired −X%/+X% transfer, default 50% configurable + Owner override, logged, capped. |
| D5/D7/D8 | Flat company-wide tier value (default $210), equal for all; seniority = more points. |
| D9 | Leave pushes the bonus window by leave days (floor stays 80); clock pauses on approved leave. |
| D10 | Missed check-ins subtract effective SP before the ladder; excused/leave excluded. |
| D11 | Anti-inflation = reference-class value + client acceptance + drift flags + Owner-by-exception + capped consistency modifier on dev & PO; SP is value, never time; end-state = calibrated-SP payout. |
| QA-1 | QA = mean over overseen devs of (devDeliveredSP × coverage) on the dev ladder. (÷ coverage was degenerate; × coverage is bounded + overlap-proof.) |
| QA-2 | QA quality metrics (escape-rate, cycle-time ↓ / E2E ↑) = a single capped modifier, not the driver. |
| PO-1 | PO = sum of delivered project SP (count-neutral) on an Owner-configurable PO floor (default = summed expected velocity of active projects). |
| Config | Base = per-person config (Deel upstream) w/ company default; tier value = company-wide config ($210); contract snapshots both at signing. |
| Deel | Deel = system-of-record for contracts + payment; ERP computes, exports keyed/idempotent payouts to Deel (§9). |
| D13 | Ladder {floor, step, tier_value} is Owner-configurable per role (Dev/QA/PO); flat within a role. SP_PER_MISS = 10. |
| D14 | Per-project SP visibility (§10): non-Owners see own SP per project; PO/PB see full project for projects they oversee (no money); Owner sees all + company per-client totals (Owner-only). |
| §11 | Retention rationale + client-ramp baked in: continuity + client-SP-as-a-ramp makes retention self-interested even when supply-constrained; SP = client-gated value; open levers (advisory steering, attractiveness) logged. |
Captured facts
- QA base $200/wk (bumped from $175; ≠ dev $350). Dev bases $300–$600/wk.
- One average rate card. 4-week periods company-wide calendar-aligned. All contracts in Deel.
Still open (config defaults + real-build)
- Config default values: consistency/quality-modifier cap bands, PO floor formula tuning.
- Reference-class library bootstrap (needs historical data — real build).
- Deel sync + disbursement integration (real build).
- Bonus transparency surface (§10) — design in review (
/plan-ceo-review+/plan-design-review, 2026-05-31).
Cross-refs
status-model.md— SP claimed→earned at Deployed, delivery triad (the bonus input).automation-engines.md— Engine 6 missed-check-in tally + leave/excuse model (§3, §2).qa-module.md— QA quality metrics that become the §7 modifier.architecture-decisions.md— A16 (this engine), A8/A15 (audit of the ledger), CQ3 (reopen SP).permission-matrix.md— money gating (#4 PO/PB no money), consistency metric as an efficiency metric.dashboards.md— "My SP this cycle (claimed vs earned)" live preview card; "My SP by client" card (§10 per-project visibility).permission-matrix.md— #2 per-project roles, #4 PO/PB no money, §1 PO/PB "view full project tree" (basis for PO/PB full-project SP visibility, §10).