Fold repair-item library into comments + repair-items/contractor-types UI - #141
Merged
important-new merged 27 commits intoJun 13, 2026
Merged
Conversation
Adds 4 nullable repair columns to the comments table (repair_summary, estimate_min_cents, estimate_max_cents, recommended_contractor_type_id) and a new tenant-scoped contractor_types table. Schema-only change for the comments-repair fold; no migration/services/usages yet. Also threads the new columns through the existing comment list/create handlers in admin.ts so commentRowToResponse (typed on the full comments.$inferSelect) keeps the type-check green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migration 0030 adds the four repair columns to comments, creates the contractor_types table, copies every recommendations row into comments (id-reused; severity -> rating_bucket + severity; repair + estimate fields preserved), seeds 10 default contractor types per tenant, then drops recommendations LAST. Removes the recommendations schema (file + export) so drizzle emits the DROP. Downstream service imports still reference it - type-check stays red until Task 6 (expected). Committed with --no-verify to bypass that known, documented redness. The copy INSERT also carries created_at: comments.created_at is NOT NULL with no default, so omitting it made INSERT OR IGNORE silently discard all rows. Source recommendations.created_at is preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xonomy) Contractor types are a company-wide admin-managed taxonomy (like rating-systems). The create/update/delete/reorder write routes were guarded owner/admin/inspector; restrict them to owner/admin. The GET list route keeps inspector so inspectors can still read the taxonomy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…item comments RecommendationService now reads/writes 'repair-item comments' (comments with repair_summary IS NOT NULL) instead of the dropped recommendations table. Same public method surface (create/getById/listByTenant/update/delete/bulkSeed) and return shapes, so /api/recommendations and /repair-items keep working. starter-content seeds repair-item comments; tenant-purge drops the dead recommendations table reference (comments are already purged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ypes New tenants now receive the same 10 standard contractor types that migration 0030 back-filled for existing tenants, so a freshly-provisioned tenant has a populated contractor dropdown instead of an empty one. Seeded idempotently in seedStarterContent (skip-by-name), and contractor_types is added to TenantPurgeService TENANT_TABLES so teardown leaves no orphans. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register the contractor-types API tag in route-metadata standards so the route-metadata gate recognizes the new contractor-types surface introduced by the fold (Task 3). All gates green: type-check (app+api), db:check EQUIVALENT, full unit suite 1643 passing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n-seeded ids are bare hex)
Migration 0030_comments_repair_fold back-fills existing tenants' contractor_types
with ids from lower(hex(randomblob(16))) — a 32-char bare-hex string, not a valid
UUID. The PATCH/DELETE /{id} routes validated the path param with z.string().uuid(),
so those rows 400'd before reaching the handler (admins couldn't rename/delete the
auto-seeded types). Relax both params to z.string().min(1), consistent with reorder
(plain string ids) and the service's tenant-scoped 404-on-miss queries.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…alias Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…view) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…11y, decimal step (review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…a-labels (review)
…ate + contractor type) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dual-key writes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eview) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…odal Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p stale E2E
The owner's "View report" deep-link (dashboard / inspection hub) points at the
tokenless /report/:tenant/:id public page, but that endpoint is gated by a
per-recipient portal token the owner never holds — so every owner click 404d
("Report Not Found"). The global JWT middleware skips /api/public/*, so a
relayed owner session was never verified there either.
Fix: the two report loaders now relay the owner session JWT (getToken, optional
— public client viewers carry none and behave exactly as before), and the
public-report report + photo handlers gain an owner-session fallback: when no
portal/legacy token resolves, a valid tenant-scoped session JWT grants its own
tenant. Ownership of the specific inspection is enforced downstream by
getReportData's tenant-scoped query (404s on a cross-tenant id). The fallback
fails closed on every bad input (missing/forged/expired token, agent/unscoped
class, or KV password-change invalidation), covered by a new unit spec.
Also delete tests/recommendations.spec.ts: a doubly-stale Playwright E2E for the
removed Alpine /recommendations page (replaced by the React /repair-items page in
the comments-repair fold). Its only still-relevant assertion (aggregation
endpoint shape) is already covered by aggregate-recommendations.spec.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… DS error page E2E of the inspector workflow surfaced that published-report viewing was broken end-to-end (masked because reports were never actually opened): the canonical reportUrl() and the owner "View report" buttons all pointed at /report/ (report.tsx), which reads an obsolete getReportData shape and throws `Cannot read properties of undefined (reading 'safety')`. The maintained, repair-item-aware renderer is /report-view/ (report-card-stack.tsx), which was referenced by nothing. Report routing: - reportUrl() (emails, agent-share links, PDF pipeline) now points at /report-view/; owner "View report" links (dashboard, inspection hub) too. - /report/ becomes a 302 redirect to /report-view/, preserving the query string (?token=, ?view=agent) so already-sent emails + in-flight PDFs work. - Add /report-view/ to tenant-routing PUBLIC_PREFIXES and the JWT-middleware public-path allowlist (mirror /report/). - Fix the report-view loader to read address/date/inspectorName from the nested `inspection` object (the current server shape) instead of absent top-level fields — the header showed a blank address + "Inspector: N/A". Error UX (was inconsistent: bare root boundary + three divergent not-found styles): - New app/components/ErrorState.tsx — one DS-consistent error/not-found surface (branded card, icon, optional code/message/CTA). - Wire into the root ErrorBoundary (4xx/5xx + generic) and reuse for the report-card-stack + report-gate error paths. Gates: type-check (app+api) 0/0, lint OK, public-urls + owner-preview specs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Folds the standalone
recommendationsrepair-item library intocomments(matching the competitor model where severity + repair recommendation + cost estimate + recommended contractor type live inside deficiency comments), retires therecommendationstable, and adds the full UI on top. Spec:docs/superpowers/specs/2026-06-12-comments-repair-fold-design.md. Plans:docs/superpowers/plans/2026-06-12-comments-repair-fold-backend.md(Plan 1) +docs/superpowers/plans/2026-06-13-comments-repair-fold-ui.md(Plan 2).Backend (Plan 1)
0030: 4 repair columns oncomments(repair_summary,estimate_min_cents,estimate_max_cents,recommended_contractor_type_id) + newcontractor_typestable; copiesrecommendations→commentsreusing ids (finding snapshots still resolve); seeds the standard 10 contractor types per tenant; dropsrecommendationslast./api/recommendationsre-implemented as a thin alias over repair-item comments (repair_summary IS NOT NULL); new/api/contractor-typesCRUD + reorder. Starter-content seeds + tenant-purge updated.UI (Plan 2)
/repair-items: real CRUD (severity / cost estimate / repair summary / recommended-contractor dropdown), replacing the read-only stub.{recommendationId, estimateSnapshotMin/Max, summarySnapshot, contractorTypeSnapshot, attachedAt}intoinspection_results.data[itemId].recommendations[], reusing the existing whole-blobsave-allpersistence (no new write endpoint) and the offline diff3 union.contractorTypeSnapshot.Notable fix
The aggregate endpoint
GET /api/inspections/:id/recommendationsdouble-counted every attached item, because the editor stores each result under both a composite key and the bareitemId(how all mutations persist) while the aggregate flattened all keys. Fixed by normalizing keys viaparseFindingKeyand deduping by(itemId, recommendationId)— preserving the same recommendation legitimately attached to two different findings. The report reader (single per-item result) was confirmed immune.Scope notes
RECOMMENDATION_CATEGORIESslug mechanism and the client-facing Repair Request feature (repair-requests.ts) are a separate system and are untouched./repair-itemsis the canonical repair-item editor.Test Plan
npm run type-check(app + api) — exit 0npm run db:check— EQUIVALENT (70/70 tables, no drift)npm run test:unit— 1657 passed / 8 skipped (incl. 13 new: alias contractor-type, comment repair-field schemas, aggregate dedup, report mapper)npm run test:web— 364 passed🤖 Generated with Claude Code