Domain expertise and coding conventions for the HomeOwner Guardian construction protection app
You are an expert in:
guide/00-APP-MEMORY.md first — This is the app's persistent memory with current state, completed work, gaps, and roadmapguide/05-COMPONENT-STATUS.md for what's working vs broken00-APP-MEMORY.md Section 4 for priority orderguide/00-APP-MEMORY.md:
npx next build to verifyguide/05-COMPONENT-STATUS.md if component statuses changed| State | Regulator | Insurance | Tribunal |
|---|---|---|---|
| NSW | NSW Fair Trading | HBCF (icare) | NCAT |
| VIC | VBA → VBPC (Apr 2025) | DBI | VCAT / DBDRV |
| QLD | QBCC | QBCC Insurance | QBCC Dispute Resolution |
| WA | DMIRS Building Commission | HII | SAT |
| SA | Office of Technical Regulator | Builder's Indemnity | SACAT |
"use client") for interactive UI — marked explicitlysrc/app/guardian/actions.ts — all mutations@/lib/supabase/server for server, @/lib/supabase/client for browsersrc/types/guardian.ts — always extend, never duplicatesrc/lib/guardian/calculations.ts — pure functions, no Reactuser_id foreign key with RLSproject_id foreign keycreated_at columns{projectId}/{category}/{timestamp}.{ext}{Component}PropscreateClient() from appropriate supabase libsubscription_tier from profiles tableuseState + useEffect for data fetchingsetError() state + inline error displaysetLoading(true/false) around async operationsconst timestamp = Date.now();
const ext = file.name.split(".").pop() || "jpg";
const filePath = `${projectId}/${category}/${timestamp}.${ext}`;
await supabase.storage.from("bucket_name").upload(filePath, file);
const { data: urlData } = supabase.storage.from("bucket_name").getPublicUrl(filePath);
try {
const url = new URL(photoUrl);
const pathMatch = url.pathname.match(/\/object\/(?:public|sign)\/bucket_name\/(.+)/);
if (pathMatch?.[1]) {
await supabase.storage.from("bucket_name").remove([decodeURIComponent(pathMatch[1])]);
}
} catch {
console.warn("Could not parse storage URL for cleanup");
}
// Exclude multiple statuses
.not('status', 'in', '(verified,rectified)')
// Count only
.select('id', { count: 'exact', head: true })
// Maybe get one result (no error if missing)
.maybeSingle()
const { data: profile } = await supabase.from("profiles").select("subscription_tier, trial_ends_at, is_admin").eq("id", user.id).single();
const tier = profile?.subscription_tier || "free";
const trialActive = tier === "trial" && profile?.trial_ends_at && new Date(profile.trial_ends_at) > new Date();
const hasPro = tier === "guardian_pro" || profile?.is_admin || trialActive;
if (!hasPro) {
// Apply free tier limits: 1 project, 3 defects, 2 variations
}
The file src/data/australian-build-workflows.json contains:
buildCategories: new_build, extension, granny_flatstates[]: code, name, regulator, insuranceScheme, insuranceThreshold, warrantyPeriods, approvalAuthoritiesworkflows.{category}.{state}: approvalPathways, stages (with inspections, certificates, dodgyBuilderWarnings, checklists, paymentMilestones)site_start → slab → frame → [roof (WA only)] → lockup → [pre_plasterboard (NSW)] → fixing → practical_completion → warranty
npx playwright test (Playwright, e2e/ directory)npx jest (Jest, src/components/guardian/__tests__/)npx next build (must pass with exit code 0)e2e/guardian-smoke.spec.ts — basic page loadse2e/guardian-full-workflow.spec.ts — per-state testing