| # | Title | Status | Priority | Wave | Effort | Source |
|---|
No session history found.
No recent sessions found.
| Feature | Location | When | Effort | Priority | Native |
|---|---|---|---|---|---|
| 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 |
| Item | Effort |
|---|---|
| AI photo-to-quote (Haul Pros first) | 25-40h |
| AI receipt scan | 4-6h |
| Intake photo-upload question type | 6-10h |
| Dynamic pricing engine (full) | 30-50h |
| Per-service service areas | 15-25h |
| General support chat | 10-15h |
| Admin Dashboard expansion | 15-25h |
| Reporting suite | 25-40h |
| Invoice PDF download | 8-12h |
| #105B — Auto no-show policy | TBD |
| Item | Effort |
|---|---|
| Stripe in-app payment (SDK pre-installed) | 15-25h |
| Settings → Integrations tab | 6-10h |
| Loyalty discount at checkout | 4-6h |
| Wave | Title | Status | Effort | Cumulative (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 |
| 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 |
Ordered so each layer unblocks the next. Items inside a layer can run in parallel unless an edge in §2 says otherwise.
revalidatePath), #111 (stray PNGs).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.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.DsBadge / DsInput.DsDialog, DsSelect, DsTextarea, DsSwitch, DsTable slices 1/2a/2b. DsTable slices 3/4/5 superseded — re-file explicitly if wanted.docs/archive/phase-2a-backlog-resolved.md.send_quote RPC + Quote panel + Scope panel + approved-amount display fix.docs/archive/phase-2a-backlog-resolved.md.approve_quote reserves the quoted duration on the booking block. #162 folded in.docs/archive/phase-2a-backlog-resolved.md.apps/admin/lib/conflicts/booking-conflicts.ts). #79 and #175 folded in.docs/archive/phase-2a-backlog-resolved.md.docs/archive/phase-2a-backlog-resolved.md.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.starts_at confirm guard on update_booking_status, plus the Needs-Attention Confirm group that inherits it.docs/archive/phase-2a-backlog-resolved.md.docs/archive/phase-2a-backlog-resolved.md.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.DsSlotPickerdocs/archive/phase-2a-backlog-resolved.md.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.PUBLIC EXECUTE grant on public-schema functions (Medium). LAUNCH-GATED.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).DinviteTech, 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.Dapps/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.docs/archive/phase-2a-backlog-resolved.md.lib/tech-count.ts owns the number and the strings.docs/archive/phase-2a-backlog-resolved.md.docs/archive/phase-2a-backlog-resolved.md.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.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.Dissue_invoice rewrite, mark_invoice_paid + void_invoice, down-payment credit, admin panel + customer card. Web push split out to #186.docs/archive/phase-2a-backlog-resolved.md.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.Ddb:gen-types run): #90, #93, blackouts-loader.tsx casts were stale, no regen needed), #91, #92. Docker.Dbase / 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.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.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).next lint → ESLint CLI) → baseUrl) do not ride #32 — there was no TypeScript bump; they are independent, see the dependency-edge table.next/font (Low).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.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.docs/archive/phase-2a-backlog-resolved.md. No longer blocks Phase 3 submission.services.price/duration — after all reads confirmed on base_*; irreversible; standalone, late.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.Ddocs/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: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.assigned → in_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.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).DOnly items that will break or double-build if mis-ordered. A → B = A depends on B (B first).
| Item | Depends on / must follow | Why |
|---|---|---|
| #73, #74, #75 | #72 (Realtime Slice A) | Consume the shared useRealtimeSubscription hook. #75 is Phase 3. |
| #126 | #48 | Needs the quote-flow backend (send_quote, quote data layer). |
| #163 ✅ done | #48 | Copies quotes.duration_minutes into the reserved block; part of quoting. Shipped with #162 folded in. |
| #183 ✅ done | #48 | Down-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 ✅ done | Reused #65's helper; shipped in the same PR. |
| #84 ✅ done | #83 ✅ done | Confirm path inherits the past-starts_at guard. Both shipped together in Slice 2b. |
| #95 | #123 | Can't add a lint gate to a CI pipeline that doesn't exist yet. |
| #123 | gen-zip-seed.mjs lint | Was "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) | #128 | Confirmation tokens were deferred to the DS-v1 customer-portal redesign. |
| #130 ✅ done | Was the "2nd consumer" that triggers primitive extraction. Never applied to DsTable (9–10 consumers); the gate was about DS sheet/dialog primitives. | |
| #119 | assign_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. |
| #17 | Read surfaces on base_price/base_duration | Irreversible 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. |
| #193 | Supabase-plan answer + Hostinger sharp answer (neither exists yet) — or neither, under Option C | Scope 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. |
| #118 | First auto-assign feature (post-launch) | Backstop only needed when code writes assignments without a human. |
| #182, #75 | Phase 3 mobile kickoff | Native-only / pre-native-rebuild work. |
| #127 | #66 (soft) | Thin data until audit coverage is wired; likely UI-only over existing data otherwise. |
Same file / same RPC → open it once. This is where correct order and anti-proliferation meet.
| Surface | Items |
|---|---|
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 |
| Change | Detail |
|---|---|
| #21 closed | Wizard order already service→address→intake→datetime→confirm (verified booking-wizard.tsx buildStepOrder). Moved to archive. |
| #64 ← #114 merged | Both were only the min_techs_required services-list column. Kept #64; #114 archived as merged. |
| #109 repurposed | Original 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 ✅ resolved | Down 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. |
Grouped by layer. D = Docker required for the fix. dep = must follow. Standalone = no ordering constraint.
/mobile-only holding page is now /no-access in both apps) · show=attention left · booking-tech-counts.ts not tech-count.ts) · weight_threshold_lbs, migration 20260813000001) · #80 · 20260814000001; #168 pulled out — not grant-only) · #195 (HIGH, pre-launch blocker; deps: #188 ✅ / #192 ✅; D likely) · next/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 ·