Servixo Roadmap

v4 · last review 2026-06-25 · data 2026-09-02 04:40 UTC
1 parse warning(s):
  • Duplicate item number(s) in both files — archive wins (resolved): 105, 147
54 open 130 resolved 1 deferred · 185 total items
1 Must-have 24 Secondary 18 Low-priority
# Title Status Priority Wave Effort Source
Recently shipped (last 0 sessions)

No session history found.

Strategic Roadmap — v4
Last manual review 2026-06-25 · data 2026-09-02 04:40 UTC
~249.5–322h
remaining to Phase 3 launch

Locked rules

  • New pages = DS v1 from day one
  • Restyle drops are sliced between waves
  • Spec-first discipline
  • Impact analysis before every spec
  • All native rebuild items ship in the initial Phase 3 scaffold
  • Every AI feature respects an admin toggle. ANTHROPIC_API_KEY server env only
  • Claude Code handoff protocol (5-field header)
  • All dev migrations via db:push, never Supabase Studio

Current status snapshot

✓ Wave 1
Stabilization done (22h)
90%
Wave 2 done · 30h burned
52h
Total effort burned
54
Open backlog items

Recently shipped

No recent sessions found.

Core wave plan

1 Stabilization sweep
Done22h (burned)
completed 2026-06-19 · 22h burned
Type-safety audit + 3 cast sweep slices
2 Operational carve-outs
In progress135.5–164.5h
90% done · 30h burned
Done:
#5 2–3h#6 8–11h#33 4h#34 4h#35 4h#44 0.5–1h#45 0.08h#46 1h#47 0.5h#51 1–2h#52 4–6h#53 2–3h#54 8–12h#58 3–5h#60 1–2h#61 2–3h#62 4–6h#67 0.5h#68 0.5h#69 1–2h#71 1–2h#77 1h#87 4h#97 4h#98 2–3h#103 0.17h#107 0.25h#110 4h#116 4h#117 4h#56 1–2h#57 0.5–1h#78 2–3h#79 1–2h#80 0.5h#109 48h
Remaining:
#30 2–4h#76 2–3h#81 2–3h#95 1h
3 DS v1 expansion
Done17.25–18.25h
100% done
Done:
#31 5–6h#82 0.25h#108 4h#124 4h
Remaining:
LoyaltyBadge primitive 4h
4 Quote flow completion (v1.4)
In progress46.5–57.5h
71% done
Done:
#20 1h#21 4h#24 0.5h#48 4–6h#163 4h
Remaining:
#96 4h#126 4h
🎨 Drop A — Bookings module + responsive shell 25–34h
Done:
#65 6–8h#79 1–2h#105 2–4h
Remaining:
#63 8–12h#106 4h#115 4h
5 Invoices & Payments
Planned3.08–5.08h
🎨 Drop B — Services + Settings 3.08–5.08h
Done:
#3 0.08h#4 0.5h#18 0.5h#114 0.5–1h
Remaining:
#2 1–2h#64 0.5–1h
6 Missing admin pages
Next11.5–13.5h
planned
Done:
specialties + tech_specialties tables 4h
Remaining:
profiles.address column 4h
🎨 Drop C — Techs + Customers lists 3.5–5.5h
Done:
#41 2–3h#42 1–2h#80 0.5h
7 Auth, signup, roles
In progress19.5–28h
67% done
Done:
#49 3–5h#152 4h
Remaining:
#43 1–2h
🎨 Drop D — Blackouts + Payouts + Dashboard 11.5–17h
Done:
#65 6–8h#69 1–2h#83 1–2h#84 1.5–2h
Remaining:
#85 2–3h
8 Customer portal UI alignment
In progress10.5–12.5h
75% done
Done:
#26 0.5h#50 4h#148 4h
Remaining:
#30 2–4h
9 AI infrastructure AI
Planned24h
planned
Remaining:
ANTHROPIC_API_KEY 4happ_settings AI toggle columns 4hai_usage_log table 4hclaude-client.ts helper 4hSettings AI tab 4hmarket_baselines table 4h
10 AI features — pre-mobile pack AI
Planned20h
planned
Remaining:
Intake summary (3-5h) 4hTech-matching (5-7h) 4hNo-show risk (5-7h) 4hFAQ chat (6-10h) 4hDynamic pricing (9-13h) 4h
11 Notifications + email hardening
Done5–6h
100% done
Done:
#13 4h#99 1–2h
12 Hardening
In progress17.5–22.5h
40% done
Done:
#119 1–2h#120 0.5h
Remaining:
#66 8–12h#118 4h#127 4h
CI CI pipeline
Planned4h
0% done
Remaining:
#123 4h
13 Realtime
Planned9–12h
0% done
Remaining:
#72 3–4h#73 3–4h#74 3–4h
14 Chat (Phase 2b)
Planned30–40h
14.5 AI sentiment flag on chat AI
Planned3–5h
15 Next.js 15 → 16 upgrade
Done4h
100% done
Done:
#32 4h
16 Phase 3 prep (admin + tooling)
Planned12–18h

AI infrastructure & features

FeatureLocationWhenEffortPriorityNative
Intake summary Admin bookings list Wave 10 3-5h LOW web
Tech-matching suggestion Assign-tech modal Wave 10 5-7h MED web
No-show risk flag Booking detail Wave 10 5-7h MED web
Customer FAQ chat Widget pre-booking Wave 10 6-10h MED web
Dynamic pricing Quote flow + admin price input Wave 10 9-13h HIGH web
Sentiment on chat Customer ↔ tech chat Wave 14.5 (after Chat) 3-5h LOW web
Photo-to-quote Quote flow Post-Phase 3 OTA 25-40h HIGH native scaffold
Receipt scan Tech completion Post-Phase 3 OTA 4-6h LOW native scaffold

Phase 3 — native scaffold

Permissions

  • Camera
  • Photo library (read)
  • Push notifications
  • Location (when in use)
  • Microphone
  • File picker
  • Background fetch
  • Universal / app links

SDKs

  • expo-camera + expo-image-picker
  • expo-notifications + Expo Push token registration
  • expo-location
  • expo-secure-store
  • expo-document-picker
  • expo-linking
  • expo-av
  • @stripe/stripe-react-native (feature-gated for v2.0)
  • Sentry crash reporter
  • react-native-reusables

Phase 3 — mobile build

Effort: 90–130h

Customer flows

HomeBookMy BookingsPackagesProfileInvoicesChat

Tech flows

Today's JobsWeek scheduleJob detailStatus transitionsExpense submissionOpen in Maps deep linkChat

Post-Phase 3 OTA roadmap

v1.6

ItemEffort
AI photo-to-quote (Haul Pros first)25-40h
AI receipt scan4-6h
Intake photo-upload question type6-10h
Dynamic pricing engine (full)30-50h
Per-service service areas15-25h
General support chat10-15h
Admin Dashboard expansion15-25h
Reporting suite25-40h
Invoice PDF download8-12h
#105B — Auto no-show policyTBD

v2.0

ItemEffort
Stripe in-app payment (SDK pre-installed)15-25h
Settings → Integrations tab6-10h
Loyalty discount at checkout4-6h

Future

Customer tech preferenceReviews / ratingsRecurring bookingsGeo-routed availabilityReferral codesVoice notes in chat

Deferred / parked

#131 Rule-based tech matching in AssignTechsM

Effort summary

WaveTitleStatusEffortCumulative (remaining)
1 Stabilization sweep Done 22h
2 Operational carve-outs In progress 135.5–164.5h 135.5–164.5h
3 DS v1 expansion Planned 17.25–18.25h 152.75–182.75h
4 Quote flow completion (v1.4) Planned 46.5–57.5h 199.25–240.25h
5 Invoices & Payments Planned 3.08–5.08h 202.33–245.33h
6 Missing admin pages Planned 11.5–13.5h 213.83–258.83h
7 Auth, signup, roles Planned 19.5–28h 233.33–286.83h
8 Customer portal UI alignment Planned 10.5–12.5h 243.83–299.33h
9 AI infrastructure Planned 24h 267.83–323.33h
10 AI features — pre-mobile pack Planned 20h 287.83–343.33h
11 Notifications + email hardening Planned 5–6h 292.83–349.33h
12 Hardening Planned 17.5–22.5h 310.33–371.83h
CI CI pipeline Planned 4h 314.33–375.83h
13 Realtime Planned 9–12h 323.33–387.83h
14 Chat (Phase 2b) Planned 30–40h 353.33–427.83h
14.5 AI sentiment flag on chat Planned 3–5h 356.33–432.83h
15 Next.js 15 → 16 upgrade Planned 4h 360.33–436.83h
16 Phase 3 prep (admin + tooling) Planned 12–18h 372.33–454.83h

Backlog coverage map (wave → items)

Wave 2#5 #6 #30 #33 #34 #35 #44 #45 #46 #47 #51 #52 #53 #54 #56 #57 #58 #60 #61 #62 #67 #68 #69 #71 #76 #77 #78 #79 #80 #81 #87 #95 #97 #98 #103 #107 #109 #110 #116 #117
Wave 3#31 #82 #108 #124
Wave 4#20 #21 #24 #48 #63 #65 #79 #96 #105 #106 #115 #126 #163
Wave 5#2 #3 #4 #18 #64 #114
Wave 6#41 #42 #80
Wave 7#43 #49 #65 #69 #83 #84 #85 #152
Wave 8#26 #30 #50 #148
Wave 11#13 #99
Wave 12#66 #118 #119 #120 #127
Wave CI#123
Wave 13#72 #73 #74
Wave 15#32
Anti-proliferation rules — check for changes
Anti-proliferation rules (resolve-1-file-3)

The data shows the source: #98 spawned #100/#101/#102; #62 spawned ~7; the tech feature spawned ~14 (#175#182 etc.). Six rules kill most of it:

  1. Fold edge-case siblings into the same slice. If a discovered gap hits the same file + same risk class as the current item, fix it now — don't file it.
  2. Scope by surface, not symptom — use the §3 clusters. One file open = everything queued for it.
  3. Per-slice scratch parking lot, reconciled at close (you did this on #61). Collect gaps during the slice; at close, triage fold-in / same-wave-sibling / genuine-new. Only the last gets a number.
  4. Merge overlaps as they surface (e.g. #64/#114 done; the five cast items → one sweep).
  5. Decision-items don't count as open work. #154, #161, #177, #181, #105-B, #96, #131, #149, #153, #155, #170, #172 → a separate Decisions list; they re-enter as scoped items only once decided. (#147 is the worked example: decided, re-entered as scoped slices, and shipping.)
  6. Batch hygiene into one sweep per wave boundary#88, #94, #104, #112, #113, #120, #140, #168 are one sweep, not eight tickets.

Corrected build sequence (the spine)

Ordered so each layer unblocks the next. Items inside a layer can run in parallel unless an edge in §2 says otherwise.

Layer 0 — Now, no blockers, high value-per-hour
Do any time; good fillers between bigger slices.
#164 — Payouts wrong booking reference (money screen, ~10 min). No Docker.
#174 — Widget deep-link entry (live site drives bookings from per-service pages). No Docker.
Hygiene fold-ins: #129 (dup Dashboard heading), #178 (missing revalidatePath), #111 (stray PNGs).
#188 — role gating (High, pre-launch security). ✅ done 2026-08-17, PR #220. Filed 2026-08-15 out of #32's smoke pass; pre-existing, not caused by the upgrade. No blockers, no Docker. Two independent gates: apps/admin/lib/auth/require-admin.ts (resolveAdminRedirect never verifies the tech role, so a customer lands on /jobs) and apps/web/proxy.ts (checks only that a user exists, so a tech can use the customer portal). Depends on: nothing. Downstream: #176 (tech web portal scope) closed by decision 2026-08-31 — the tech web portal stays as shipped, so no rebuild of this check is coming; #151e's TECH_PATHS allow-list, whenever it lands, must build on #188's role check rather than beside it.
#192 — admin-as-tech switch link (Low). ✅ done 2026-08-29, PR #228 (squash a04da14). Filed 2026-08-17 out of #188 and deferred by decision; closed 2026-08-29. Display-only — no DB change, no migration, no Docker. The shared NavSidebar gained an optional switchLink between Help and Sign out — AdminSidebar → “Switch to Tech” (/jobs), TechSidebar → “Switch to Admin” (/bookings) — rendered only when the account holds both admin and tech, computed in both layouts from a new non-throwing currentUserRoles(). The role / hourly-rate / schedule setup for the admin accounts is data entry done outside the repo and is complete. Depends on: #188 (merged). Downstream: #195 (staff / admins management) surfaces /account for admin-only accounts — the gap this link deliberately does not cover.
Layer 1 — Design-system foundation (unblocks the most)
Everything restyled after this avoids the build-twice tax that created #128 in the first place.
#128 — Unify design tokens (this is a wave, one slice per surface).
#108 — Promote DsBadge / DsInput.
LoyaltyBadge DS primitive.
#130 ✅ 2026-07-25 (PRs #165#169) — extract deferred DS primitives: DsDialog, DsSelect, DsTextarea, DsSwitch, DsTable slices 1/2a/2b. DsTable slices 3/4/5 superseded — re-file explicitly if wanted.
Full record: docs/archive/phase-2a-backlog-resolved.md.
Responsive admin shell (part of Restyle Drop A) — admin is mobile-primary; also unblocks the #61 380px re-verify.
Layer 2 — Quote-flow keystone (high business value)
#48 ✅ DONE (2026-07-20) — send_quote RPC + Quote panel + Scope panel + approved-amount display fix.
#183 ✅ DONE (2026-07-22) — down payment, shipped fast-follow across PRs #147/#148/#149. Invoice-side credit deferred to #165 (Layer 6). See docs/archive/phase-2a-backlog-resolved.md.
#163 ✅ 2026-08-04 (PR #205) — approve_quote reserves the quoted duration on the booking block. #162 folded in.
Full record: docs/archive/phase-2a-backlog-resolved.md.
Remaining, now unblocked: #126 (Quotes admin list page — needs #48 backend).
Layer 3 — Bookings-ops correctness
#65 ✅ 2026-08-04 (PR #204) — conflict signal + shared overlap helper (apps/admin/lib/conflicts/booking-conflicts.ts). #79 and #175 folded in.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#79 ✅ 2026-08-04 (PR #204) — shipped inside #65; inactive assigned tech flagged on the Technician card.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#85 — Amber bookings surface in Needs Attention (after #65now unblocked; consume conflictLabel from lib/conflicts/booking-conflicts.ts, do not re-derive the rule). Fold in: no "short a tech" badge on the bookings list (StatusBadges covers Frozen / Not started / Needs reassignment / Company closed / Capacity alert / Unbilled only) — add understaffed + "N of M", button "Add tech" when partly staffed, Dashboard "View All" → filtered list. Slice 2a shipped 2026-08-06 (booking_tech_counts RPC + badge + action copy + View All/bookings); only show=attention remains.
after #65
#83 + #84 ✅ 2026-08-06 (PR #213) — past-starts_at confirm guard on update_booking_status, plus the Needs-Attention Confirm group that inherits it.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#105 Part A ✅ 2026-08-07 (PR #214) — overdue booking signal on the bookings list + Needs Attention. Display only. #105-B stays Decision-first.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#63 — Bookings-list UI. Carve out "+ New Booking" early — it blocks ops today. Dep note (2026-08-07): when "+ New Booking" is built, give admin the same exemption from min_booking_lead_hours that #109 + #119 gave reschedule — create_booking hard-rejects inside the lead window with no admin bypass today, which would block the short-notice job the office just took by phone.
Layer 4 — Reschedule / cancel policy ✅ COMPLETE
#119 ✅ 2026-08-07 (PR #215) — reschedule buffer gate.
#109 ✅ 2026-08-14 — Slice 1 policy rules (PR #215), Slice 2 DsSlotPicker
(PR #217). Full record: docs/archive/phase-2a-backlog-resolved.md.
Layer 5 — Ops automation & guards
#80 — block blackout creation for an inactive tech. The only one of this original five still in the active build order; #76, #59, #43 and #81 moved to OL on 2026-08-31.
#78 — capacity-setting validation. ✅ DONE 2026-08-25, both slices. Slice 1 (2026-08-24, PR #224) shipped the warn-not-block impact dialog for the three app-evaluable levers (max_techs_per_booking lowered, tech_buffer_minutes raised, max_pending_per_slot lowered) — app layer only, no Docker, no DB/migration/RPC change; rule in apps/admin/lib/settings-impact.ts, re-checked server-side with an echo-back of the affected booking ids. Slice 2 (2026-08-25) added weight_threshold_lbs — migration 20260813000001, three functions (compute_techs_required_at(uuid, smallint) holds the rule, compute_techs_required(uuid) CREATE OR REPLACEd as a thin wrapper so its OID and all eleven callers survive, batch sibling booking_techs_required_at(uuid[], smallint)), 21 new pgTAP assertions, pushed and verified live. Hard finding recorded: no overload and no DEFAULT parameter — a DEFAULT NULL second argument makes every existing one-argument call raise 42725, proven empirically against the live DB. Full record: docs/archive/phase-2a-backlog-resolved.md. #78 did NOT absorb the D-3 frozen-bookings capacity residual below — that is re-pointed to #109 and stays open.
#194 — close the PUBLIC EXECUTE grant on public-schema functions (Medium). LAUNCH-GATED. ✅ done (2026-08-29, migration 20260814000001 — 23 functions authenticated only, the 2 widget functions keep anon, _classify_capacity_failure gets no grant at all). #168 did NOT ride along and is still open — see its entry; it is not a grant-only change. Filed 2026-08-25 out of the #78 Slice 2 close-out. 26 functions in schema public carry a PUBLIC EXECUTE grant (23 SECURITY DEFINER + 3 SECURITY INVOKER), verified on the remote project 2026-08-25 — #78 Slice 2 took compute_techs_required off the list, the count was 27 before it. A missing outer layer, not 22 open doors: 10 of the 11 spot-checked already raise on an inner is_admin() / auth.uid() guard, and the 11th (_classify_capacity_failure) is read-only. The real work is the 15 functions nobody has examined. Depends on: nothing. #168 was pulled out and is NOT grant-only (tech_booking_view's table-level anon SELECT grant) — four RLS policies read the view in their USING clause, so revoking anon turns anon reads of profiles / addresses / booking_events / booking_question_answers into a hard permission denied and breaks rls_test.sql T3; it needs a policy change plus its own pgTAP. See its entry. Docker required (migration + pgTAP). Must close before public launch — the widget is WordPress-embeddable, so the anon key becomes public by design; get_available_slots and get_dates_with_capacity therefore keep anon and lose only the redundant PUBLIC grant. Any migration keeps , anon on every REVOKE: local and remote default privileges differ (#78 Slice 2 finding).D
#195 — staff / admins management section (HIGH). PRE-LAUNCH BLOCKER. Filed 2026-08-29. There is no admin management of any kind: no Admins page, no invite-admin action, no deactivate — both existing admin accounts were created by hand in the database, and when an admin leaves there is no way to revoke their access from inside the app. Three decided parts: invite-an-admin (mirrors inviteTech, writing role: 'admin'), a deactivate that really revokes (profiles.is_active is NOT enough — neither the login action nor resolveAdminRedirect reads it; removing the admin row from user_roles is the real revocation, mechanism decided at recon), and the existing /account screen linked from the admin sidebar for admin-only accounts. Placed in Layer 5 beside #147's slices 1–3 — same surface and same risk class (admin account ops: invite / deactivate / status). Depends on: #188 (role gating, merged) and #192 (merged — its switch link is what gives an admin+tech account /account today, which is exactly the gap an admin-only account still has). Needs a last-admin guard, same shape as can_deactivate_tech. Do not weaken the user_roles_admin_all RLS policy. Role changes do not bite until a fresh token (#173). Email change is out of scope (see #153). Docker: likely required (RPC + pgTAP for the guard) — confirm at recon.D
#189 ✅ 2026-08-17 (PR #221) — understaffed badge suppressed on terminal-status bookings. The filed entry's file name was wrong: the work is in apps/admin/lib/booking-tech-counts.ts (per-booking staffing from the booking_tech_counts RPC), not lib/tech-count.ts (service-level minimums), which was never involved. That error also invalidated the stated rationale — the "same surface as #100/#101/#102, open that file once" clause was false, and the item turned out to be standalone, sharing no file with them.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#100 + #101 + #102 ✅ 2026-08-05 (PR #210) — #98 staffability rule hardened for the weight bump; lib/tech-count.ts owns the number and the strings.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#147 (customer account lifecycle) — moved off Decision-first (decided 2026-07-27), re-entered as four scoped slices. Layer placement note: slices 1–3 are admin account ops, hence Layer 5; Slice 4 is the same surface as #171 (Layer 10, account deletion / store-submission blocker) — build them together or #171 will rebuild it.
Slices 1 → 4b-2 2026-07-27 → 2026-07-31 (PRs #180#191) — status model, admin/self activation RPCs, block rules, freeze + filter, deletion request, wipe engine + sealed identity, daily purge + deletion email.
Full record: docs/archive/phase-2a-backlog-resolved.md.
Still open from these slices: the D-3 capacity residual below, plus the post-push items 4b-1 and 4b-2 owed — the anon/authenticated/service_role grant probe on purge_deleted_customer + customers_due_for_purge, supabase functions deploy send-booking-email, a live end-to-end deletion email, and the pg_cron operator runbook.
Capacity release NOT done — deferred to #109 (D-3). (Re-pointed 2026-08-25: this was "#78/#109"; #78 closed without absorbing it, so it now hangs off #109 alone. Still open — not folded in, not resolved.) is_slot_available / get_slot_remaining_capacity still count frozen bookings as demand. Documented residual: capacity is under-reported, so the system never double-books — it only turns away a slot it could have sold. Fix belongs with the capacity work, not here.D
Layer 6 — Invoicing correctness (before invoicing goes live)
#165 ✅ 2026-07-26 (PRs #175/#176/#177) — invoicing end-to-end: issue_invoice rewrite, mark_invoice_paid + void_invoice, down-payment credit, admin panel + customer card. Web push split out to #186.
Full record: docs/archive/phase-2a-backlog-resolved.md.
#186 (web push subsystem) — NEW (filed 2026-07-26 from #165; full entry in docs/phase-2a-backlog.md). No push anywhere in the repo: web_push_subscription exists only in generated types.ts, notifications.channel accepts 'push' but nothing consumes it, and PRD §2 marks several events "Email + push". Needs a VAPID key pair + settings, a service worker + subscription-permission flow on web, a push_subscriptions table + RLS, and a dispatcher beside send-booking-email. Layer: 9 (realtime) — same infra class as #72#74, and pointless before them. Deps: #72 (realtime foundation) first; the customer notification-preferences toggle (already shipped) is the UI hook. Docker.D
Layer 7 — Type-safety close-out + CI
Cast sweep (one db:gen-types run): #90, #93, #175 (✅ done 2026-08-04 — folded into #65; the two blackouts-loader.tsx casts were stale, no regen needed), #91, #92. Docker.D
#190 — wire the Next ESLint plugin into the shared flat config (Medium). ✅ done (2026-08-17, PR #222). Filed 2026-08-15 out of #32. The shared config is split base / react / next; apps/web + apps/admin consume /next, packages/ui consumes /react (component library, no Next dependency), and the root config is deliberately untouched so shared / db / api-client / address / invoice-pdf stay on base. Any package with its own eslint.config.mjs also needs its own .lintstagedrc.json — see the rule in CLAUDE.md. Full record: docs/archive/phase-2a-backlog-resolved.md. Downstream — #95 IS NOW UNBLOCKED. The "land #190 BEFORE #95" dependency is satisfied. #95 inherits this baseline: 0 errors, 12 warnings — 6 react-hooks/exhaustive-deps, 6 @next/next/no-img-element. Updated 2026-08-17 by #191: was 14; the 2 @next/next/no-page-custom-font warnings are cleared, since both apps' <link> font stylesheets are gone. Whoever builds the lint gate must decide up front whether to allow those 12 (e.g. --max-warnings 12, which then only holds until someone adds one) or clear them first; gating at zero warnings without clearing them makes #95 red on day 1. Forward note 2026-08-18: #193 (Layer 10) would clear the 6 @next/next/no-img-element warnings, taking the baseline 12 → 6 — but it is recon-gated and unscoped, so do not assume it lands before #95.
CI chain: #123 (build CI) → #95 (lint gate). #95 cannot run before #123 — a pipeline must exist to gate. Ride #140 + #88 with #123. Correction (verified 2026-08-15): repo-wide pnpm lint is green, 8/8, zero warnings — the gen-zip-seed.mjs lint fix is no longer a prerequisite. The #123#95 ordering is unchanged. Superseded 2026-08-17 by #190: lint is still green 8/8 with 0 errors, but the React/Next rules now run and report warnings. Count corrected 2026-08-17 by #191: 12, not 14 — the 2 @next/next/no-page-custom-font warnings went with the <link> font stylesheets. "Zero warnings" is no longer the baseline — see the #190 entry above before scoping #95.
Layer 8 — AI infrastructure → AI features
AI infra wave (server-only ANTHROPIC_API_KEY, per-feature toggles, ai_usage_log, market_baselines, shared claude-client.ts) strictly before any AI feature (photo-to-quote, intake summary, receipt scan).
Layer 9 — Realtime (after UI alignment merges)
#72#73, #74. #72 ships first within the layer. (#75 is Phase 3.)
Layer 10 — Pre-mobile infrastructure
ESLint codemod patch (standalone)DONE 2026-08-15 (shipped as PR 218 — next lint → ESLint CLI) → #32 (Next.js 15→16)DONE 2026-08-15 (next 16.3.1 / react 19.2.8, PR 219). #12 + #15 (tsconfig baseUrl) do not ride #32 — there was no TypeScript bump; they are independent, see the dependency-edge table.
#191 — move Google Fonts to next/font (Low). ✅ done (2026-08-17, PR #223). Filed 2026-08-15 out of #32; it was filed into docs/phase-2a-backlog.md only and never given a layer here — added retroactively on close-out, placed in Layer 10 because it is the tail of #32's font work. Inter + Manrope via next/font/google; Material Symbols is absent from Next 16.3.1's bundled Google Fonts manifest, so it is vendored per app and served via next/font/local. Cleared both @next/next/no-page-custom-font warnings — #95's inherited baseline drops 14 → 12. Also fixed a cascade-order bug that made every ServiceMedia size="sm" icon render 50% oversized. Full record: docs/archive/phase-2a-backlog-resolved.md.
#193 — serve resized images instead of full-size originals (Medium). NOT SCOPED. Filed 2026-08-18. Storage is already correct (Supabase Storage, URL-only in Postgres, zero bytea repo-wide); what is wrong is the delivered size — every <img> pulls the original and the browser scales it down in CSS, so a 3000px phone photo paints a 64px avatar. 5 user-uploaded-raster call sites to convert, spanning packages/ui + apps/admin + apps/web; the 2 /logo.svg tags get a reasoned lint suppression, not a conversion. Placed here rather than Layer 7 because the surface is asset delivery across both apps and packages/ui (§4 rule 2 — scope by surface, not symptom) — the same shape as #191, which also cleared lint warnings but is asset work, not CI work. Gated on an unanswered question: whether Supabase Storage image transformation is included on the project’s current plan. Option A (Storage transform) keeps packages/ui free of Next, as #190 deliberately left it; Option B (next/image) needs sharp on the deploy target plus images.remotePatterns in both apps, and forces a Next dependency into packages/ui. Recon before scoping — do not slice this until the plan question is answered. Complication either way: the avatars bucket is private, so its URLs are signed and expire, and caching resized copies of expiring URLs is a decision someone has to make. Downstream — clears 6 of #95’s 12 inherited warnings (all 6 @next/next/no-img-element), taking that baseline 12 → 6.
#182 + #143 — extract shared decision logic / unify status labels before the first native screen rebuild.
#171 — account deletionRESOLVED 2026-09-01 — closed by decision; the in-app flow was already built and running, and the public route is satisfied by a "Deleting Your Account" section published in the marketing-site Privacy Policy (website content, outside this repo). Retention review done. Full record in docs/archive/phase-2a-backlog-resolved.md. No longer blocks Phase 3 submission.
Rolling / late / deferred
Hardening: #66 (audit wiring) → feeds #127 (audit-log page) + #180 (recent-activity labels).
#17 — drop legacy services.price/duration — after all reads confirmed on base_*; irreversible; standalone, late.
#118 — DB-level buffer constraint — gated by the first auto-assign feature (post-launch). No action now.
Hygiene sweep (batch, don't slice individually): #88, #94, #104, #112, #113, #120, #140, #168.
#168 — revoke anon's SELECT on tech_booking_view. NOT a grant-only change. Scoped into #194 on 2026-08-29 and pulled back out before merge. Four RLS policies read the view in their USING clause — profiles_read_tech_assigned, addresses_tech_via_booking, booking_events_read_tech, bqa_tech_read_assigned — and the executor checks SELECT on the view for whichever role runs the query, before is_tech() can short-circuit it to false. Revoking anon therefore turns every anon read of profiles / addresses / booking_events / booking_question_answers into permission denied for view tech_booking_view instead of the empty result RLS already returns; it breaks rls_test.sql T3 immediately. security_invoker=false on the view does not help — that governs the view body, not the caller's privilege on the view itself. Depends on: restricting those four policies TO authenticated, or routing the view read through a SECURITY DEFINER helper. That is a policy change, not a grant, and needs its own pgTAP coverage — so #168 no longer belongs in the grant-hygiene batch and cannot be co-built with #194 (now done). Docker required.D
OL — Optional / Later
Not now. Not blocking launch. Off the open count. Revisit deliberately. Item bodies stay in docs/phase-2a-backlog.md — this is an index, not a copy. Decided, waiting for a slot — what to build is settled, only timing isn't:
#43 — "No schedule set" warning on the Techs list and tech detail. Verified still open 2026-08-31: inviteTech creates zero tech_schedules rows, no DB trigger does either, and no warning exists on any admin surface, so a newly invited tech is silently unbookable. Option A (auto-create a default schedule) is dead — techs self-own their schedule via save_own_schedule. Option B (the warning) is the path. No Docker.
#59 — Availability pill beside each assigned tech on the booking detail page. Re-scoped 2026-08-31, smaller than filed. No Docker.
#76 — Automatic assignedin_progress at start time. Verified 2026-08-31: the MANUAL path works for both admin ("Start Job" on the booking detail bar) and tech ("Start job" on the job page), and cron.job holds only purge-deleted-customers-daily, so nothing auto-transitions. Half the filed problem is already solved — the In Progress tab pulls in assigned bookings whose window covers now, badged "Not started" / "Overdue" (#105 Part A). What remains overlaps #105 Part B's unresolved tension: a timer cannot tell a real no-show from a tech who worked but forgot to tap. Decide #105-B first.
Needs a decision first — cannot start until a call is made:
#96 · #105-B · #131 · #149 · #153 · #154 · #155 · #161 · #170 · #172 · #177 · #181 · #31b
Deferred on scale — correct today, revisit at a threshold, not a date:
#81 — Batch the per-tech get_tech_status calls. Verified 2026-08-31: techs-loader.tsx still fires one RPC per tech inside Promise.all, and no batched get_techs_with_status function exists in the database. Fine at ~5 techs. Trigger: revisit at ~20 techs, or fold in free the next time techs-loader.tsx is opened for another reason. Docker required when built (new RPC + pgTAP).D

Dependency edges (the map)

Only items that will break or double-build if mis-ordered. A → B = A depends on B (B first).

ItemDepends on / must followWhy
#73, #74, #75#72 (Realtime Slice A)Consume the shared useRealtimeSubscription hook. #75 is Phase 3.
#126#48Needs the quote-flow backend (send_quote, quote data layer).
#163 ✅ done#48Copies quotes.duration_minutes into the reserved block; part of quoting. Shipped with #162 folded in.
#183 ✅ done#48Down-payment section renders inside #48's customer view; modifies the quote RPCs. Both shipped.
#48#108, #128, #130 ✅ done (DS layer)Builds quote UI on DS primitives.
#85#65 ✅ done#65 defines the amber icon #85 surfaces — reuse conflictLabel, don't re-derive.
#79 ✅ done#65 ✅ doneReused #65's helper; shipped in the same PR.
#84 ✅ done#83 ✅ doneConfirm path inherits the past-starts_at guard. Both shipped together in Slice 2b.
#95#123Can't add a lint gate to a CI pipeline that doesn't exist yet.
#123fix gen-zip-seed.mjs lintCLEAREDWas "repo-wide pnpm lint is red today". Verified 2026-08-15: pnpm lint is 8/8 green, zero warnings. No prerequisite remains. Updated 2026-08-17 (#190): still 8/8 green with 0 errors, but now warnings — the React/Next rules run as of PR #222. 12 as of #191 (PR #223), down from 14.
#30 (visual)#128Confirmation tokens were deferred to the DS-v1 customer-portal redesign.
#130 ✅ doneEdit Profile modal sliceCLEARED (PR #61/#62)Was the "2nd consumer" that triggers primitive extraction. Never applied to DsTable (9–10 consumers); the gate was about DS sheet/dialog primitives.
#119assign_techs buffer check (shipped)Reuses the same buffer overlap logic.
#154#159#159 stamps started_at/completed_at — the groundwork.
#100, #101, #102#98 (shipped)✅ DONE (2026-08-05) — shipped as one slice.
#106#97 (shipped)Extends the reference-only search with customer-name join.
#17Read surfaces on base_price/base_durationIrreversible column drop; audit first.
#12, #15— none (independent)Corrected 2026-08-15: the Next 16 upgrade (#32 ✅) carried no TypeScript bump — ^5.6.3 already resolves above Next 16's 5.1 floor. The TS baseUrl cleanup never depended on it. #12 and #15 are now independent and can land in any order.
#193Supabase-plan answer + Hostinger sharp answer (neither exists yet) — or neither, under Option CScope is undetermined until recon answers two questions: is Storage image transformation on the current plan (Option A), and can the deploy target run next/image with sharp (Option B). Option C — resize on upload — depends on neither, so it is the no-dependency fallback if A and B both come back negative; it changes the upload path rather than the render path and needs a backfill for existing images. C shares scope with #138 (avatar uploads capped by the Next body limit) — resizing before upload relieves that cap, so if C is the path, C and #138 are one slice, not two. Nothing blocks #193 and #193 blocks nothing; it only lightens #95 — clears all 6 @next/next/no-img-element warnings, baseline 12 → 6. Land it before #95 to gate at a lower number, or after it and re-count.
#118First auto-assign feature (post-launch)Backstop only needed when code writes assignments without a human.
#182, #75Phase 3 mobile kickoffNative-only / pre-native-rebuild work.
#127#66 (soft)Thin data until audit coverage is wired; likely UI-only over existing data otherwise.

Co-resolution clusters — "one surface, one slice"

Same file / same RPC → open it once. This is where correct order and anti-proliferation meet.

SurfaceItems
reschedule_booking#109 (Finding 1) + #119
issue_invoice ✅ resolved#165 + #183 (both done)
booking-detail-view.tsx#30 + #48 (#79, #162 ✅ done)
dashboard needs-attention / RPC#85 (#83, #84 ✅ done)
type-safety casts (one db:gen-types)#90 + #91 + #92 + #93 (#175 ✅ done)
worked-hours chain#159#154#161
Next.js body-size limit#31b + #138
DS primitive layer#108 + #128 + #130 ✅ + LoyaltyBadge
Corrections applied (baked into this doc)
ChangeDetail
#21 closedWizard order already service→address→intake→datetime→confirm (verified booking-wizard.tsx buildStepOrder). Moved to archive.
#64#114 mergedBoth were only the min_techs_required services-list column. Kept #64; #114 archived as merged.
#109 repurposedOriginal premise (admin blocked by 48h guard) verified NOT reproducing; now tracks two findings — reschedule lead-time bypass (confirmed) + cancel-cutoff dormant (unconfirmed, needs trace).
#183 ✅ resolvedDown payment. Filed, built, and resolved 2026-07-22 across PRs #147/#148/#149. Invoice-side credit carried to #165.
Homes assigned#65 → bookings-ops (built first, functional not restyle); #79 → after #65; #80 → Layer 5; #30 (visual) → after #128.
Already-resolved, archive candidates✅ Cleared 2026-07-26 — #148, #167 and #166 all archived to docs/archive/phase-2a-backlog-resolved.md. Not build-order items.

Full open-item index

Grouped by layer. D = Docker required for the fix. dep = must follow. Standalone = no ordering constraint.

Layer 0 (now)
#164 · #174 · #129 · #178 · #111 · #188 ✅ done (2026-08-17, PR #220 — role gating; the /mobile-only holding page is now /no-access in both apps) · #192 ✅ done (2026-08-29, PR #228 — admin-as-tech switch link in the shared sidebar; display-only, shown only to accounts holding both roles)
Layer 1 (DS foundation)
#128 · #108 · #130 ✅ done (2026-07-25) · #30-visualdep: #128
Layer 2 (quote flow)
#48 ✅ done · #183 ✅ done · #163 ✅ done (2026-08-04) · #126dep: #48, unblocked
Layer 3 (bookings-ops)
#65 ✅ · #79 ✅ (shipped inside #65) · #85dep: #65 — unblocked; only show=attention left · #83 ✅ · #84 ✅ (shipped with #83 in Slice 2b) · #105 Part A ✅ (dep: #83 — shares the past-start boundary; #105-B stays Decision-first) · #63
Layer 4 (reschedule/cancel)
#119 ✅ · #109
Layer 5 (ops guards)
#189 ✅ done (2026-08-17, PR #221 — standalone after all; the filed "same file as #100#102" note was wrong, it is booking-tech-counts.ts not tech-count.ts) · #78 ✅ done (Slice 1 2026-08-24 PR #224 — three app-evaluable levers; Slice 2 2026-08-25 — weight_threshold_lbs, migration 20260813000001) · #80 · #194 ✅ done (2026-08-29, migration 20260814000001; #168 pulled out — not grant-only) · #195 (HIGH, pre-launch blocker; deps: #188 ✅ / #192 ✅; D likely) · #100 · #101 · #102 ✅ done (2026-08-05)
Layer 6 (invoicing)
#165 ✅ done (2026-07-26) — carried the invoice-side down-payment credit inherited from #183 (done). Layer 6 is now empty.
Layer 7 (type-safety + CI)
#190 ✅ done (2026-08-17, PR #222 — React/Next rules now run; #95 unblocked, inherits 0 errors + 12 warnings after #191) · #90D · #93 · #175 ✅ (folded into #65) · #91 · #92 · #123 · #95dep: #123 · #140 · #88
Layer 8 (AI)
AI infra wave (D) → AI features (photo-to-quote, intake summary, receipt scan)
Layer 9 (realtime)
#72 · #73dep: #72 · #74dep: #72 · #186 web push (D) (dep: #72) · [#75 Phase 3]
Layer 10 (pre-mobile)
ESLint codemod ✅ (PR 218) · #32 ✅ done (2026-08-15, PR 219) · #191 ✅ done (2026-08-17, PR #223next/font; Material Symbols vendored, lint baseline 14 → 12) · #193 (recon-gated on the Supabase-plan answer; clears 6 of #95’s 12 warnings) · #12 · #15 (both independent — no TS bump rode #32) · #182 · #143 · #171 ✅ done (2026-09-01 — closed by published policy)
Rolling/late
#66D · #127dep: #66 · #180D · #17D · #118D (gated) · #70 · #89 · #16 · #2 · #3 · #4 · #18 · #55D · #64 · #106 · #145 · #159D · #160 · #162 ✅ (folded into #163) · #168 · #138 · #94 · #104 · #112 · #113 · #120
OL — Optional / Later (off the open count)
decided-waiting #43 · #59 · #76 — needs-decision #96 · #105-B · #131 · #149 · #153 · #154 · #155 · #161 · #170 · #172 · #177 · #181 · #31b — deferred-on-scale #81D. See the OL section in §1.
Housekeeping (archive/close)
#148 ✅ · #167 ✅ · #166 ✅ — all archived 2026-07-26