Phase 3 of the sub-property hierarchy feature.
- New/edit forms grow a "Parent property" select. Edit-side options
exclude the current property and its descendants so the picker
itself can't create a cycle; service-layer assertNoCycle is the
belt-and-braces guard if a malicious form bypasses the dropdown.
- New form accepts ?parent=<id> as a preselect so "Add sub-property"
links from the parent's tab land in a pre-wired form.
- Property detail layout: breadcrumb (Parent › Child) when parent
is set, plus a new "Sub-properties (N)" tab.
- Dedicated Sub-properties tab lists direct children with a
+ New sub-property button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2. Each service grows a `*ForProperties` (plural) variant that
takes a resolved property id array; the existing single-property
functions now delegate to them with `[propertyId]`. The route layer
will compute the descendant set via getDescendantIds when the user
toggles "include sub-properties" — this commit only adds the readers
behind the scenes.
Also extends the checklist_scope enum with 'property' so checklists
can attach directly to a property and the rollup query has a
well-typed scope to filter on.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of the parent/child rollup feature. Self-FK on properties
with ON DELETE RESTRICT, plus a CHECK that blocks self-reference at
the DB level. Service-layer helpers (getDescendantIds,
getAncestorIds, assertNoCycle) walk the tree via recursive CTEs and
guard against cycles and cross-company parents. softDeleteProperty
now refuses to delete a property with live children.
No UI yet — readers and roll-up routes land in Phase 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- service monthlySeriesForProperty now accepts months: number | 'all';
'all' derives the span from min(incurred_at) for the matching kinds,
capped at 120 months so the SQL stays bounded
- page load reads ?range= query param (default 12; invalid values fall
back silently)
- chart header has a segmented control linking to ?range=6|12|24|all
with active state highlighting; title updates to match
- lib/server/csv-parse.ts: RFC 4180 parser (quoted fields, embedded commas
and quotes, CRLF/LF, BOM), no extra dep
- services/expenses.ts: importExpenses() is transactional + all-or-nothing —
if any row fails Zod-style validation, nothing is inserted
- /properties/[id]/expenses/import: upload page with per-line error list,
sample CSV download at /template.csv, 5 MB cap
- 'Import CSV' button wired on the expenses tab
Without min/max, native <input type="date"> lets you type arbitrarily-many
year digits (YYYYYY-MM-DD). Add min="2000-01-01" max="2099-12-31" to every
date input (and bounded range on the single datetime-local) across expenses,
projects, tasks, assets, decisions, and maintenance.
- expense_kind enum (utilities + maintenance/repair/cleaning/insurance/tax/rent/other)
- property_expenses table with optional link to a property_accounts row
(preserves history via ON DELETE SET NULL)
- services/expenses.ts: CRUD + 12-month monthly series aggregation +
year-to-date summary by kind
- /properties/[id]/expenses tab: inline SVG line chart for electricity +
water last 12 months (no chart library), summary card, add/edit/delete
inline with account linking when kind matches
Without dotenv/config the SvelteKit dev server SSR sees only the
ambient shell env, so the Zod validator rejected all four required
variables with Required. drizzle.config.ts already does this; match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack matches sibling buildfor_life_* apps: SvelteKit 5 with adapter-node,
Svelte 5 runes, TypeScript, Tailwind v4 with @theme inline tokens,
PostgreSQL via Drizzle ORM, Argon2id sessions via @node-rs/argon2 and
@oslojs/crypto, EasyMDE ready for wiki/decision markdown, Sharp for
thumbnails.
Included in this commit:
- Config: package.json, svelte.config.js, vite.config.ts, tsconfig.json,
drizzle.config.ts, .gitignore, .env.example, .gitattributes, .npmrc
- Tenancy schema: companies, users, company_users, sessions
(10 enums pre-declared for the full domain so downstream migrations
don't re-diff them; decision_scope widened to include asset +
work_package per product decision)
- Auth: password hashing + SHA-256-hashed session cookies,
session lifetime 30d with sliding renewal at T-15d,
login + logout + session refresh in hooks
- Storage: StorageAdapter interface + LocalDiskStorage with HMAC-signed
URLs served by /api/files, S3 drop-in with zero schema change
- UI shell: dark-mode bootstrap in app.html identical to siblings,
sidebar (w-64, h-14 header, amber attention band pattern from repair),
topbar with breadcrumbs, theme toggle with cross-tab sync via
storage event, blue-600 primary, responsive drawer
- Routes: (app) authed group with auto-redirect to /login,
(auth) login group, dashboard placeholder, error page, signed-file API
- Scripts: create-user script for bootstrapping first admin user
- Drizzle: initial migration generated (0000_init.sql)
- Shared agents and skills committed under .claude/; per-user
permissions gitignored
Typecheck: 0 errors / 0 warnings across 555 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>