Compare commits

..

7 Commits

Author SHA1 Message Date
grabowski 09e0fdc9ac feat(maintenance): reminders CLI + systemd timer drop-in
Deploy to LXC / deploy (push) Successful in 15s
Validate / validate (push) Successful in 35s
Phase 3-4 of maintenance reminders.

scripts/maintenance-reminders.ts: thin CLI that opens the same
pg pool as the app and calls runRemindersOnce. Flags:
--soon-days N (default 7), --company <id> (default all),
--dry-run (count without firing), --backfill (mark all currently
due as already-sent, mutually exclusive with --dry-run). Prints a
single-line JSON summary so journald/jq handles it cleanly.

package.json: + reminders:check script.

DEPLOYMENT.md: documents the systemd .service + .timer pair for
06:00 daily, with Persistent=true so a missed run during host
downtime still fires on next boot. Includes the first-deploy
protocol (--dry-run to scope, then either let day-one alert or
--backfill for a clean slate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:15:03 +07:00
grabowski f5e4743120 feat(maintenance): reminder service — findDueSchedules + runRemindersOnce
Phase 2 of maintenance reminders. Pure server-side, no UI changes.

findDueSchedules joins active time-based schedules to assets and
properties, computes the kind (overdue if past, due_soon if within
the warning window), and returns a flat shape ready for the
notification body. Usage-based schedules are intentionally excluded
— they need a different trigger (usage-reading crossover).

runRemindersOnce orchestrates: for each due schedule, look up the
company's admin+manager user_ids, atomically claim the
(schedule, kind, due_at) tuple via INSERT … ON CONFLICT DO NOTHING,
and only call notify() if we won the insert. The unique index on
maintenance_reminders_sent makes the dedup atomic across concurrent
runs and across re-invocations of the cron.

Two opt-in flags on the orchestrator: dryRun (count what would
fire without touching the DB or fanning out) and backfill (record
everything currently due as already-sent so day-one of the cron
is silent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:13:46 +07:00
grabowski b4108c5a36 feat(maintenance): schema for reminders dedup log
Phase 1 of the maintenance-reminders feature.

- notification_kind: + maintenance_due_soon, maintenance_overdue.
  These are distinct from maintenance_event_recorded (service
  performed) so the bell list can group/filter reminder vs service
  cleanly.
- New maintenance_reminder_kind enum: due_soon | overdue.
- New maintenance_reminders_sent table with UNIQUE(schedule_id,
  kind, due_at). The cron uses INSERT … ON CONFLICT DO NOTHING on
  this composite to make the fire-once-per-window guarantee atomic
  even under concurrent runs. Once the schedule's next_due_at
  advances after a service event, the tuple is fresh and a new
  reminder fires.

No service code yet — Phase 2 wires the readers and orchestrator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:11:34 +07:00
grabowski 435bcb981f docs(roadmap): rewrite README roadmap to reflect actual state
Deploy to LXC / deploy (push) Successful in 16s
Validate / validate (push) Successful in 31s
The original table marked Phase 1 as "next" but Phases 1-5a have
all shipped. Replaces the single table with three sections:
Shipped (now includes RBAC, rooms/floors/accounts/expenses, the
sub-property hierarchy, and the fnm/pnpm tooling switch — none of
which were on the original roadmap), 5b-pending (reports,
cross-app APIs to budget/repair), and a Phase 6 quality-of-life
backlog covering maintenance-reminder cron, OIDC handlers,
self-service password reset, permission inheritance, bulk CSV
property import, audit log viewer, and project↔property linking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:51:14 +07:00
grabowski 011e7a2165 chore(graph): refresh graphify after sub-property feature
Deploy to LXC / deploy (push) Successful in 15s
Validate / validate (push) Successful in 30s
Re-extracted 42 changed code files via AST and 3 changed docs
(README, DEPLOYMENT, drizzle/README) via one semantic subagent.
Merged into the existing graph: 453→555 nodes, 486→633 edges,
137 communities.

Top god nodes now reflect the new shape: load() at the center of
every page-server route, buildfor_life_ops as the doc-side anchor,
and Drizzle ORM + Zod as the bridge between expenses and the rest
of the service layer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:47:00 +07:00
grabowski c3aaf82642 feat(properties): warn when parenting exceeds depth cap of 5
Deploy to LXC / deploy (push) Successful in 16s
Validate / validate (push) Successful in 29s
Phase 5 polish. Soft cap — properties still save, but a console.warn
fires so journalctl picks up a clear signal when a tree grows
pathologically deep. Triggered on create + on parent reassignment
via updateProperty. Justification for the warning: getDescendantIds
runs an unbounded recursive CTE, and deep hierarchies are also
painful to navigate in the existing list view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:36:30 +07:00
grabowski 90207135c8 feat(properties): list view renders parent/child as a depth-first tree
Deploy to LXC / deploy (push) Successful in 16s
Validate / validate (push) Successful in 30s
The flat list ordered by updatedAt scattered apartments away from
their building. Server-side flatten now does a depth-first walk —
parents render immediately above their children, alphabetical at
each level — and tags each row with its depth. The UI indents the
name column by 1.5rem per level and prefixes children with "└" for
a visible parent/child line.

Orphan rows (parent_id pointing outside the live company set) fall
back to root depth so nothing is silently dropped, even though the
restrict-on-delete FK should prevent that case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:02:46 +07:00
62 changed files with 10578 additions and 2441 deletions
+60
View File
@@ -190,6 +190,66 @@ systemctl status buildfor_life_ops
journalctl -u buildfor_life_ops -f
```
### Maintenance reminders timer
The app records `next_due_at` on every time-based maintenance schedule but does not poll itself. A daily systemd timer runs `pnpm run reminders:check`, which scans for schedules entering the 7-day warning window or already overdue and fans out via the existing in-app + email + Matrix notifier. Re-runs are idempotent — `maintenance_reminders_sent` deduplicates per `(schedule, kind, due_at)`.
`/etc/systemd/system/buildfor_life_ops-reminders.service`:
```ini
[Unit]
Description=buildfor_life_ops maintenance reminder cron
After=postgresql.service network.target
Wants=postgresql.service
[Service]
Type=oneshot
User=ops
Group=ops
WorkingDirectory=/home/ops/buildfor_life_ops
EnvironmentFile=/home/ops/buildfor_life_ops/.env
Environment=NODE_ENV=production
ExecStart=/home/ops/.local/share/fnm/aliases/default/bin/pnpm run reminders:check
```
`/etc/systemd/system/buildfor_life_ops-reminders.timer`:
```ini
[Unit]
Description=Run buildfor_life_ops maintenance reminders daily
[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
Unit=buildfor_life_ops-reminders.service
[Install]
WantedBy=timers.target
```
Enable:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now buildfor_life_ops-reminders.timer
sudo systemctl list-timers buildfor_life_ops-reminders.timer
```
**First-run protocol** to avoid a deluge of stale alerts on day one:
```bash
# Inspect what would fire without notifying.
sudo -iu ops bash -lc 'cd ~/buildfor_life_ops && pnpm run reminders:check -- --dry-run'
# If the count is reasonable, run normally — the timer will pick up subsequent
# windows automatically. Or, if you want a clean slate, mark everything
# currently-due as already-notified (no fan-out), so day-one alerts only
# new breaches:
sudo -iu ops bash -lc 'cd ~/buildfor_life_ops && pnpm run reminders:check -- --backfill'
```
Logs end up in `journalctl -u buildfor_life_ops-reminders.service`. Each run prints a single JSON line (`{ ok, scanned, fired, skippedDedup, noRecipients, ... }`) so `journalctl --output=cat | grep '"ok":true' | jq` gives a clean trend view.
## 11. Reverse proxy (nginx)
`/etc/nginx/sites-available/buildfor_life_ops`:
+32 -8
View File
@@ -161,14 +161,38 @@ static/ public static assets (drop favicon here)
## Roadmap
| Phase | Scope | State |
| --- | --- | --- |
| 0 | Scaffold: stack wiring + auth + layout shell + storage interface + tenancy schema + git remote | ✅ shipped |
| 1 | Properties + Assets with typed custom fields + mobility history + asset logs + document upload per scope | next |
| 2 | Checklist templates + maintenance schedules (time + usage) + maintenance events + usage readings | |
| 3 | Projects + WorkPackages → Tasks → Subtasks + **structured decision events** (title, body, alternatives, cost_impact, approved_by, tags) | |
| 4 | Wiki (global + project + property) with EasyMDE + revisions + FTS | |
| 5 *(later)* | QR label generation, email/in-app notifications, reports, S3 storage adapter, cross-app APIs | |
### Shipped
| Phase | Scope |
| --- | --- |
| 0 | Scaffold: stack wiring + auth + layout shell + storage interface + tenancy schema + git remote |
| 1 | Properties + Assets with typed custom fields + mobility history + asset logs + document upload per scope |
| 2 | Checklist templates + maintenance schedules (time + usage) + maintenance events + usage readings |
| 3 | Projects + WorkPackages → Tasks → Subtasks + structured decision events (title, body, alternatives, cost_impact, approved_by, tags) |
| 4 | Wiki (global + project + property) with EasyMDE + revisions + FTS |
| 5a | QR label generation, in-app + email + Matrix notifications, S3 storage adapter |
| — | RBAC: user/company admin, role middleware, last-admin guards |
| — | Property structure: rooms + floors, utility account/meter records, expenses with CSV import + electricity/water chart |
| — | Sub-property hierarchy: parent_id self-FK, recursive-CTE descendant rollups, "Include sub-properties" toggle on Expenses/Assets/Maintenance/Todos tabs, depth-first nested list view, depth-cap warning at 5 levels |
| — | Tooling: switched to fnm + pnpm, Gitea CI deploy workflow with public-HTTPS clone, full `DEPLOYMENT.md` |
### 5b — pending Phase 5 items
- **Reports** — cross-cutting outputs (monthly building roll-up, annual asset summary). Per-domain CSV exports already exist; this is the aggregated/scheduled layer
- **Cross-app APIs** — wire `buildfor_life_ops` to siblings `buildfor_life_budget` and `buildfor_life_repair` so decisions can link to budget line items and repair tickets
### 6 — quality-of-life backlog
Things that came up after the original roadmap was written. Not prioritised — pull whichever the next stakeholder ask depends on.
- **Maintenance reminders** — cron-style trigger that fires `maintenance_event_recorded` / `task_assigned` notifications when `next_due_at` crosses a threshold. The notification fan-out (app + email + Matrix) is already there; only the scheduler is missing
- **OIDC SSO** — env vars + schema are wired (`OIDC_ENABLED`, `OIDC_ISSUER`, etc.), but the route handlers were never written
- **Self-service password reset** — currently admin-initiated only via `pnpm run create-user`
- **Permission inheritance for sub-properties** — RBAC is per-company today; "manager of Building A" does not imply "manager of Apt 3B"
- **Bulk property CSV import** — "create N apartments under this building" in one upload, mirroring the existing expenses CSV importer
- **"Move to parent" quick action** — UI sugar over the existing edit form for re-parenting
- **Audit log viewer** — `audit_action` enum + audit infrastructure exists, but no UI to browse it
- **Project ↔ property linking** — they're disjoint today; many real workflows want "this project is happening at this property"
## Key design decisions
+14
View File
@@ -0,0 +1,14 @@
CREATE TYPE "public"."maintenance_reminder_kind" AS ENUM('due_soon', 'overdue');--> statement-breakpoint
ALTER TYPE "public"."notification_kind" ADD VALUE 'maintenance_due_soon' BEFORE 'generic';--> statement-breakpoint
ALTER TYPE "public"."notification_kind" ADD VALUE 'maintenance_overdue' BEFORE 'generic';--> statement-breakpoint
CREATE TABLE "maintenance_reminders_sent" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"schedule_id" uuid NOT NULL,
"kind" "maintenance_reminder_kind" NOT NULL,
"due_at" timestamp with time zone NOT NULL,
"fired_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "maintenance_reminders_sent" ADD CONSTRAINT "maintenance_reminders_sent_schedule_id_maintenance_schedules_id_fk" FOREIGN KEY ("schedule_id") REFERENCES "public"."maintenance_schedules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "mrs_schedule_kind_due_uq" ON "maintenance_reminders_sent" USING btree ("schedule_id","kind","due_at");--> statement-breakpoint
CREATE INDEX "mrs_by_schedule" ON "maintenance_reminders_sent" USING btree ("schedule_id","kind");
File diff suppressed because it is too large Load Diff
+7
View File
@@ -134,6 +134,13 @@
"when": 1777268985448,
"tag": "0018_checklist_scope_property",
"breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1777281036233,
"tag": "0019_maintenance_reminders",
"breakpoints": true
}
]
}
-117
View File
@@ -1,117 +0,0 @@
{
"nodes": [
{"id": "readme_buildfor_life_ops", "label": "buildfor_life_ops", "file_type": "document", "source_file": "README.md", "source_location": "L1-L3", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_buildfor_life_budget", "label": "buildfor_life_budget (sibling)", "file_type": "document", "source_file": "README.md", "source_location": "L4,L178", "source_url": "https://git.b4l.co.th/B4L/buildfor_life_budget", "captured_at": null, "author": null, "contributor": null},
{"id": "readme_buildfor_life_repair", "label": "buildfor_life_repair (sibling)", "file_type": "document", "source_file": "README.md", "source_location": "L4,L179", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_stack_sveltekit5", "label": "SvelteKit 5 (adapter-node)", "file_type": "document", "source_file": "README.md", "source_location": "L8", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_stack_tailwind_v4", "label": "Tailwind v4 + @theme inline tokens", "file_type": "document", "source_file": "README.md", "source_location": "L9", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_stack_postgres_drizzle", "label": "PostgreSQL 16+ via Drizzle ORM + Zod", "file_type": "document", "source_file": "README.md", "source_location": "L10", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_auth_argon2id", "label": "Argon2id sessions (@node-rs/argon2 + @oslojs/crypto)", "file_type": "document", "source_file": "README.md", "source_location": "L11", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_easymde", "label": "EasyMDE markdown editor", "file_type": "document", "source_file": "README.md", "source_location": "L12", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_sharp", "label": "Sharp image thumbnails", "file_type": "document", "source_file": "README.md", "source_location": "L12", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_storage_adapter", "label": "StorageAdapter interface", "file_type": "document", "source_file": "README.md", "source_location": "L13", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_local_disk_storage", "label": "LocalDiskStorage", "file_type": "document", "source_file": "README.md", "source_location": "L13,L143", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_s3_storage", "label": "S3Storage (future)", "file_type": "document", "source_file": "README.md", "source_location": "L144", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_env_dotenv", "label": ".env configuration", "file_type": "document", "source_file": "README.md", "source_location": "L29-L44", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_create_user_script", "label": "npm run create-user script", "file_type": "document", "source_file": "README.md", "source_location": "L59-L66,L122", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_migrate", "label": "npm run db:migrate", "file_type": "document", "source_file": "README.md", "source_location": "L54,L86", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_generate", "label": "npm run db:generate", "file_type": "document", "source_file": "README.md", "source_location": "L85", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_push", "label": "npm run db:push (dev only)", "file_type": "document", "source_file": "README.md", "source_location": "L87", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_studio", "label": "npm run db:studio (Drizzle Studio)", "file_type": "document", "source_file": "README.md", "source_location": "L88", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_seed", "label": "npm run db:seed", "file_type": "document", "source_file": "README.md", "source_location": "L89", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_validate_script", "label": "npm run validate (check + build)", "file_type": "document", "source_file": "README.md", "source_location": "L84", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_auth_model", "label": "Auth model (sessions + hashed cookies)", "file_type": "document", "source_file": "README.md", "source_location": "L132-L138", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_sliding_renewal", "label": "Sliding session renewal (30d/15d)", "file_type": "document", "source_file": "README.md", "source_location": "L135", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_sha256_cookie_hash", "label": "SHA-256 cookie hashing before DB lookup", "file_type": "document", "source_file": "README.md", "source_location": "L134", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_company_users", "label": "company_users role mapping", "file_type": "document", "source_file": "README.md", "source_location": "L138", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_storage_model", "label": "Storage model (opaque storage_key)", "file_type": "document", "source_file": "README.md", "source_location": "L141-L144", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_hmac_signed_urls", "label": "HMAC-signed short-lived file URLs", "file_type": "document", "source_file": "README.md", "source_location": "L143", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_api_files_route", "label": "/api/files route (signature verification + streaming)", "file_type": "document", "source_file": "README.md", "source_location": "L120,L143", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_layout_app_group", "label": "(app) route group (authed shell)", "file_type": "document", "source_file": "README.md", "source_location": "L113-L116,L137", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_layout_auth_group", "label": "(auth) route group (login shell)", "file_type": "document", "source_file": "README.md", "source_location": "L117-L118", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_hooks_server", "label": "hooks.server.ts (session validation)", "file_type": "document", "source_file": "README.md", "source_location": "L99", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_env_ts", "label": "env.ts (Zod-validated process.env)", "file_type": "document", "source_file": "README.md", "source_location": "L108", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_db_schema_dir", "label": "src/lib/server/db/schema/", "file_type": "document", "source_file": "README.md", "source_location": "L105-L106", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase0", "label": "Phase 0: scaffold (shipped)", "file_type": "document", "source_file": "README.md", "source_location": "L150", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase1", "label": "Phase 1: Properties + Assets", "file_type": "document", "source_file": "README.md", "source_location": "L151", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase2", "label": "Phase 2: Checklists + maintenance", "file_type": "document", "source_file": "README.md", "source_location": "L152", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase3", "label": "Phase 3: Projects + structured decisions", "file_type": "document", "source_file": "README.md", "source_location": "L153", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase4", "label": "Phase 4: Wiki + FTS", "file_type": "document", "source_file": "README.md", "source_location": "L154", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_roadmap_phase5", "label": "Phase 5: QR, notifications, S3", "file_type": "document", "source_file": "README.md", "source_location": "L155", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_uuidv7", "label": "Decision: UUID v7 primary keys", "file_type": "document", "source_file": "README.md", "source_location": "L161", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_timestamptz", "label": "Decision: timestamptz UTC everywhere", "file_type": "document", "source_file": "README.md", "source_location": "L162", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_soft_delete", "label": "Decision: soft delete (deleted_at)", "file_type": "document", "source_file": "README.md", "source_location": "L163", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_money_type", "label": "Decision: numeric(18,4) + char(3) currency", "file_type": "document", "source_file": "README.md", "source_location": "L164", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_jsonb_custom_fields", "label": "Decision: JSONB custom fields + asset_field_defs", "file_type": "document", "source_file": "README.md", "source_location": "L165", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_xor_location", "label": "Decision: XOR asset location (project XOR property)", "file_type": "document", "source_file": "README.md", "source_location": "L166", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_asset_history", "label": "Decision: asset_location_history (movable assets)", "file_type": "document", "source_file": "README.md", "source_location": "L167", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_immutable_keys", "label": "Decision: immutable custom-field keys", "file_type": "document", "source_file": "README.md", "source_location": "L168", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_decision_scope", "label": "Decision: decisions scoped to project/property/asset/work_package", "file_type": "document", "source_file": "README.md", "source_location": "L169", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_currency_default", "label": "Decision: company default currency in settings_json", "file_type": "document", "source_file": "README.md", "source_location": "L170", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_tabs_routes", "label": "Decision: tabs = nested routes (not query-string)", "file_type": "document", "source_file": "README.md", "source_location": "L171", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "readme_decision_theme_key", "label": "Decision: localStorage['theme'] key shared across siblings", "file_type": "document", "source_file": "README.md", "source_location": "L172", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "drizzle_readme_migrations", "label": "Drizzle migrations directory", "file_type": "document", "source_file": "drizzle/README.md", "source_location": "L1-L5", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "drizzle_readme_review_rationale", "label": "Review SQL after generate: enum/index/custom_fields", "file_type": "document", "source_file": "drizzle/README.md", "source_location": "L13-L18", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "drizzle_readme_concurrently_note", "label": "Use CONCURRENTLY on large-table index changes", "file_type": "document", "source_file": "drizzle/README.md", "source_location": "L16", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "drizzle_readme_immutable_key_ref", "label": "Immutable-key policy reference", "file_type": "document", "source_file": "drizzle/README.md", "source_location": "L17-L18", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "apphtml_root", "label": "app.html root document", "file_type": "code", "source_file": "src/app.html", "source_location": "L1-L20", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "apphtml_theme_bootstrap", "label": "Dark-mode bootstrap inline script (localStorage['theme'])", "file_type": "code", "source_file": "src/app.html", "source_location": "L7-L14", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "apphtml_sveltekit_placeholders", "label": "%sveltekit.head% / %sveltekit.body% placeholders", "file_type": "code", "source_file": "src/app.html", "source_location": "L15,L18", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "apphtml_tailwind_body_classes", "label": "Tailwind body classes with dark: variants", "file_type": "code", "source_file": "src/app.html", "source_location": "L17", "source_url": null, "captured_at": null, "author": null, "contributor": null},
{"id": "apphtml_preload_hover", "label": "data-sveltekit-preload-data=hover", "file_type": "code", "source_file": "src/app.html", "source_location": "L17", "source_url": null, "captured_at": null, "author": null, "contributor": null}
],
"edges": [
{"source": "readme_buildfor_life_ops", "target": "readme_buildfor_life_budget", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L4", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_buildfor_life_repair", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L4", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_stack_sveltekit5", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L8", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_stack_tailwind_v4", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L9", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_stack_postgres_drizzle", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L10", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_auth_argon2id", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L11", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_easymde", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L12", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_sharp", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L12", "weight": 1.0},
{"source": "readme_storage_adapter", "target": "readme_local_disk_storage", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L13", "weight": 1.0},
{"source": "readme_storage_adapter", "target": "readme_s3_storage", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L144", "weight": 1.0},
{"source": "readme_local_disk_storage", "target": "readme_hmac_signed_urls", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L143", "weight": 1.0},
{"source": "readme_api_files_route", "target": "readme_hmac_signed_urls", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L120,L143", "weight": 1.0},
{"source": "readme_api_files_route", "target": "readme_local_disk_storage", "relation": "calls", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L120", "weight": 1.0},
{"source": "readme_storage_model", "target": "readme_storage_adapter", "relation": "rationale_for", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L141-L144", "weight": 1.0},
{"source": "readme_auth_model", "target": "readme_sha256_cookie_hash", "relation": "rationale_for", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L134", "weight": 1.0},
{"source": "readme_auth_model", "target": "readme_sliding_renewal", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L135", "weight": 1.0},
{"source": "readme_auth_model", "target": "readme_company_users", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L138", "weight": 1.0},
{"source": "readme_hooks_server", "target": "readme_auth_model", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L99,L135", "weight": 1.0},
{"source": "readme_layout_app_group", "target": "readme_auth_model", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L137", "weight": 1.0},
{"source": "readme_env_dotenv", "target": "readme_env_ts", "relation": "shares_data_with", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L108,L183", "weight": 1.0},
{"source": "readme_create_user_script", "target": "readme_db_schema_dir", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.8, "source_file": "README.md", "source_location": "L122", "weight": 1.0},
{"source": "readme_db_migrate", "target": "drizzle_readme_migrations", "relation": "calls", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L9", "weight": 1.0},
{"source": "readme_db_generate", "target": "drizzle_readme_migrations", "relation": "calls", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L8", "weight": 1.0},
{"source": "readme_db_push", "target": "drizzle_readme_migrations", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L10", "weight": 1.0},
{"source": "readme_db_studio", "target": "drizzle_readme_migrations", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L11", "weight": 1.0},
{"source": "drizzle_readme_review_rationale", "target": "drizzle_readme_migrations", "relation": "rationale_for", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L13-L18", "weight": 1.0},
{"source": "drizzle_readme_concurrently_note", "target": "drizzle_readme_review_rationale", "relation": "rationale_for", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L16", "weight": 1.0},
{"source": "drizzle_readme_immutable_key_ref", "target": "readme_decision_immutable_keys", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "drizzle/README.md", "source_location": "L17-L18", "weight": 1.0},
{"source": "readme_decision_jsonb_custom_fields", "target": "readme_decision_immutable_keys", "relation": "conceptually_related_to", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L165,L168", "weight": 1.0},
{"source": "readme_decision_xor_location", "target": "readme_decision_asset_history", "relation": "conceptually_related_to", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L166-L167", "weight": 1.0},
{"source": "readme_roadmap_phase5", "target": "readme_s3_storage", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L155", "weight": 1.0},
{"source": "readme_roadmap_phase3", "target": "readme_decision_decision_scope", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L153,L169", "weight": 1.0},
{"source": "readme_roadmap_phase4", "target": "readme_easymde", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L154,L12", "weight": 1.0},
{"source": "readme_roadmap_phase1", "target": "readme_decision_jsonb_custom_fields", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L151,L165", "weight": 1.0},
{"source": "apphtml_root", "target": "apphtml_theme_bootstrap", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "src/app.html", "source_location": "L7-L14", "weight": 1.0},
{"source": "apphtml_root", "target": "apphtml_sveltekit_placeholders", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "src/app.html", "source_location": "L15,L18", "weight": 1.0},
{"source": "apphtml_root", "target": "apphtml_tailwind_body_classes", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "src/app.html", "source_location": "L17", "weight": 1.0},
{"source": "apphtml_root", "target": "apphtml_preload_hover", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "src/app.html", "source_location": "L17", "weight": 1.0},
{"source": "apphtml_theme_bootstrap", "target": "readme_decision_theme_key", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L172", "weight": 1.0},
{"source": "apphtml_tailwind_body_classes", "target": "readme_stack_tailwind_v4", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L9,L17", "weight": 1.0},
{"source": "apphtml_root", "target": "readme_stack_sveltekit5", "relation": "implements", "confidence": "INFERRED", "confidence_score": 0.9, "source_file": "src/app.html", "source_location": "L1-L20", "weight": 1.0},
{"source": "readme_decision_theme_key", "target": "apphtml_theme_bootstrap", "relation": "rationale_for", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": "L172", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_buildfor_life_budget", "relation": "semantically_similar_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "README.md", "source_location": "L4,L178", "weight": 1.0},
{"source": "readme_buildfor_life_ops", "target": "readme_buildfor_life_repair", "relation": "semantically_similar_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "README.md", "source_location": "L4,L179", "weight": 1.0},
{"source": "readme_local_disk_storage", "target": "readme_s3_storage", "relation": "semantically_similar_to", "confidence": "INFERRED", "confidence_score": 0.9, "source_file": "README.md", "source_location": "L13,L144", "weight": 1.0}
],
"hyperedges": [
{"id": "auth_session_flow", "label": "Session auth flow (cookie, hash, hook, gate)", "nodes": ["readme_auth_model", "readme_sha256_cookie_hash", "readme_sliding_renewal", "readme_hooks_server", "readme_layout_app_group"], "relation": "participate_in", "confidence": "EXTRACTED", "confidence_score": 0.95, "source_file": "README.md"},
{"id": "storage_abstraction_stack", "label": "Storage abstraction (adapter, local impl, signed URLs, file route)", "nodes": ["readme_storage_adapter", "readme_local_disk_storage", "readme_hmac_signed_urls", "readme_api_files_route", "readme_storage_model"], "relation": "implement", "confidence": "EXTRACTED", "confidence_score": 0.95, "source_file": "README.md"},
{"id": "theme_propagation_pattern", "label": "Cross-sibling theme propagation via localStorage", "nodes": ["readme_decision_theme_key", "apphtml_theme_bootstrap", "readme_buildfor_life_budget", "readme_buildfor_life_repair"], "relation": "form", "confidence": "INFERRED", "confidence_score": 0.8, "source_file": "README.md"}
],
"input_tokens": 0,
"output_tokens": 0
}
+249 -205
View File
@@ -1,45 +1,45 @@
# Graph Report - C:/dev/build_for_life_project (2026-04-23)
# Graph Report - . (2026-04-27)
## Corpus Check
- 189 files · ~54,875 words
- 47 files · ~0 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 453 nodes · 486 edges · 131 communities detected
- Extraction: 80% EXTRACTED · 20% INFERRED · 0% AMBIGUOUS · INFERRED: 97 edges (avg confidence: 0.8)
- 555 nodes · 633 edges · 137 communities detected
- Extraction: 83% EXTRACTED · 17% INFERRED · 0% AMBIGUOUS · INFERRED: 109 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Auth & Load Helpers|Auth & Load Helpers]]
- [[_COMMUNITY_Documents Service|Documents Service]]
- [[_COMMUNITY_Assets Service & CSV|Assets Service & CSV]]
- [[_COMMUNITY_Email & Markdown|Email & Markdown]]
- [[_COMMUNITY_Property Accounts|Property Accounts]]
- [[_COMMUNITY_Page Server Loaders|Page Server Loaders]]
- [[_COMMUNITY_Stack & Deployment Concepts|Stack & Deployment Concepts]]
- [[_COMMUNITY_Documents & Storage Adapters|Documents & Storage Adapters]]
- [[_COMMUNITY_Auth Sessions & Catalog|Auth Sessions & Catalog]]
- [[_COMMUNITY_CSV & API Endpoints|CSV & API Endpoints]]
- [[_COMMUNITY_Expenses Service|Expenses Service]]
- [[_COMMUNITY_Email & Markdown Rendering|Email & Markdown Rendering]]
- [[_COMMUNITY_Projects Service|Projects Service]]
- [[_COMMUNITY_App Shell & Theme|App Shell & Theme]]
- [[_COMMUNITY_Asset Core|Asset Core]]
- [[_COMMUNITY_Maintenance Core|Maintenance Core]]
- [[_COMMUNITY_Checklists|Checklists]]
- [[_COMMUNITY_Admin Scripts|Admin Scripts]]
- [[_COMMUNITY_Asset Types Editor|Asset Types Editor]]
- [[_COMMUNITY_Rooms & Floors|Rooms & Floors]]
- [[_COMMUNITY_Tasks|Tasks]]
- [[_COMMUNITY_User Management|User Management]]
- [[_COMMUNITY_DB Schema Helpers|DB Schema Helpers]]
- [[_COMMUNITY_Bootstrap Scripts|Bootstrap Scripts]]
- [[_COMMUNITY_Maintenance Schedules|Maintenance Schedules]]
- [[_COMMUNITY_Assets Service|Assets Service]]
- [[_COMMUNITY_App Shell & Bootstrap|App Shell & Bootstrap]]
- [[_COMMUNITY_Checklists Service|Checklists Service]]
- [[_COMMUNITY_Rooms & Floors Service|Rooms & Floors Service]]
- [[_COMMUNITY_Tasks & Subtasks|Tasks & Subtasks]]
- [[_COMMUNITY_Work Packages|Work Packages]]
- [[_COMMUNITY_Storage Layer|Storage Layer]]
- [[_COMMUNITY_Migration Workflow|Migration Workflow]]
- [[_COMMUNITY_Session Auth|Session Auth]]
- [[_COMMUNITY_Signed URL Storage Layer|Signed URL Storage Layer]]
- [[_COMMUNITY_Drizzle Migration Conventions|Drizzle Migration Conventions]]
- [[_COMMUNITY_Auth Model|Auth Model]]
- [[_COMMUNITY_Companies Service|Companies Service]]
- [[_COMMUNITY_Form Utilities|Form Utilities]]
- [[_COMMUNITY_Custom Fields Design|Custom Fields Design]]
- [[_COMMUNITY_Field Types|Field Types]]
- [[_COMMUNITY_Form Helper|Form Helper]]
- [[_COMMUNITY_Env Config|Env Config]]
- [[_COMMUNITY_Seed Script|Seed Script]]
- [[_COMMUNITY_Asset Location Design|Asset Location Design]]
- [[_COMMUNITY_Decision Design|Decision Design]]
- [[_COMMUNITY_Drizzle Config|Drizzle Config]]
- [[_COMMUNITY_JSONB Custom Fields Policy|JSONB Custom Fields Policy]]
- [[_COMMUNITY_Cluster 21|Cluster 21]]
- [[_COMMUNITY_Cluster 22|Cluster 22]]
- [[_COMMUNITY_Cluster 23|Cluster 23]]
- [[_COMMUNITY_Cluster 24|Cluster 24]]
- [[_COMMUNITY_Cluster 25|Cluster 25]]
- [[_COMMUNITY_Cluster 26|Cluster 26]]
- [[_COMMUNITY_Cluster 27|Cluster 27]]
- [[_COMMUNITY_Cluster 28|Cluster 28]]
- [[_COMMUNITY_Cluster 29|Cluster 29]]
- [[_COMMUNITY_Cluster 30|Cluster 30]]
- [[_COMMUNITY_Cluster 31|Cluster 31]]
- [[_COMMUNITY_Cluster 32|Cluster 32]]
@@ -141,30 +141,36 @@
- [[_COMMUNITY_Cluster 128|Cluster 128]]
- [[_COMMUNITY_Cluster 129|Cluster 129]]
- [[_COMMUNITY_Cluster 130|Cluster 130]]
- [[_COMMUNITY_Cluster 131|Cluster 131]]
- [[_COMMUNITY_Cluster 132|Cluster 132]]
- [[_COMMUNITY_Cluster 133|Cluster 133]]
- [[_COMMUNITY_Cluster 134|Cluster 134]]
- [[_COMMUNITY_Cluster 135|Cluster 135]]
- [[_COMMUNITY_Cluster 136|Cluster 136]]
## God Nodes (most connected - your core abstractions)
1. `load()` - 79 edges
2. `GET()` - 20 edges
3. `LocalDiskStorage` - 10 edges
4. `load()` - 9 edges
5. `S3Storage` - 8 edges
6. `buildfor_life_ops` - 8 edges
7. `fanOutExternal()` - 7 edges
8. `getTaskWithSubtasks()` - 7 edges
9. `handle()` - 6 edges
10. `uploadDocument()` - 6 edges
1. `load()` - 95 edges
2. `buildfor_life_ops` - 26 edges
3. `GET()` - 23 edges
4. `Drizzle ORM + Zod` - 14 edges
5. `LocalDiskStorage` - 10 edges
6. `load()` - 9 edges
7. `S3Storage` - 8 edges
8. `buildfor_life_ops` - 8 edges
9. `Drizzle migrations (drizzle/)` - 8 edges
10. `fanOutExternal()` - 7 edges
## Surprising Connections (you probably didn't know these)
- `load()` --calls--> `renderMarkdown()` [INFERRED]
src\routes\(auth)\login\+page.server.ts → src\lib\server\markdown.ts
src\routes\(app)\properties\[id]\todos\+page.server.ts → src\lib\server\markdown.ts
- `load()` --calls--> `listTemplates()` [INFERRED]
src\routes\(auth)\login\+page.server.ts → src\lib\server\services\checklists.ts
src\routes\(app)\properties\[id]\todos\+page.server.ts → src\lib\server\services\checklists.ts
- `load()` --calls--> `getCompany()` [INFERRED]
src\routes\(auth)\login\+page.server.ts → src\lib\server\services\companies.ts
src\routes\(app)\properties\[id]\todos\+page.server.ts → src\lib\server\services\companies.ts
- `load()` --calls--> `listDocumentsForScope()` [INFERRED]
src\routes\(auth)\login\+page.server.ts → src\lib\server\services\documents.ts
src\routes\(app)\properties\[id]\todos\+page.server.ts → src\lib\server\services\documents.ts
- `load()` --calls--> `countOverdueForCompany()` [INFERRED]
src\routes\(auth)\login\+page.server.ts → src\lib\server\services\maintenance.ts
src\routes\(app)\properties\[id]\todos\+page.server.ts → src\lib\server\services\maintenance.ts
## Hyperedges (group relationships)
- **Session auth flow (cookie, hash, hook, gate)** — readme_auth_model, readme_sha256_cookie_hash, readme_sliding_renewal, readme_hooks_server, readme_layout_app_group [EXTRACTED 0.95]
@@ -173,123 +179,123 @@
## Communities
### Community 0 - "Auth & Load Helpers"
### Community 0 - "Page Server Loaders"
Cohesion: 0.04
Nodes (12): requireAdmin(), requireCompany(), load(), parseSettings(), getPageWithCurrentRevision(), getRevision(), listPagesForScope(), listRevisions() (+4 more)
Nodes (15): requireAdmin(), requireCompany(), e2n(), flattenTree(), load(), parseRange(), parseSettings(), getPageWithCurrentRevision() (+7 more)
### Community 1 - "Documents Service"
### Community 1 - "Stack & Deployment Concepts"
Cohesion: 0.04
Nodes (59): Argon2id sessions (@node-rs/argon2), asset_location_history (movable assets), XOR location: asset at project OR property (CHECK), Blob storage snapshot/rsync backup, 10M upload size cap (BODY_SIZE_LIMIT + client_max_body_size), buildfor_life_budget (sibling), buildfor_life_ops, buildfor_life_repair (sibling) (+51 more)
### Community 2 - "Documents & Storage Adapters"
Cohesion: 0.07
Nodes (13): assertScope(), deleteDocument(), getDocument(), listDocumentsForScope(), signedUrlForDocument(), uploadDocument(), getStorage(), LocalDiskStorage (+5 more)
### Community 2 - "Assets Service & CSV"
Cohesion: 0.11
Nodes (13): listAssets(), csvResponse(), toCsv(), gatherCustomFieldsFromForm(), createDecision(), decisionScopeLink(), listDecisionsForScope(), clamp() (+5 more)
### Community 3 - "Auth Sessions & Catalog"
Cohesion: 0.1
Nodes (20): assertProperty(), createAccount(), deleteAccount(), listAccounts(), addFieldDef(), createCompanyAssetType(), deleteCompanyAssetType(), loadEditableType() (+12 more)
### Community 3 - "Email & Markdown"
### Community 4 - "CSV & API Endpoints"
Cohesion: 0.11
Nodes (12): csvResponse(), toCsv(), gatherCustomFieldsFromForm(), createDecision(), decisionScopeLink(), listDecisionsForScope(), clamp(), GET() (+4 more)
### Community 5 - "Expenses Service"
Cohesion: 0.12
Nodes (13): Drizzle ORM + Zod, assertAccountInProperty(), assertProperty(), createExpense(), importExpenses(), listExpensesForProperties(), listExpensesForProperty(), monthlySeriesForProperties() (+5 more)
### Community 6 - "Email & Markdown Rendering"
Cohesion: 0.13
Nodes (15): getTransport(), isEmailConfigured(), sendEmail(), escapeHtml(), html(), renderMarkdown(), buildBodies(), isMatrixConfigured() (+7 more)
### Community 4 - "Property Accounts"
Cohesion: 0.15
Nodes (12): assertProperty(), createAccount(), deleteAccount(), listAccounts(), handle(), deleteFloor(), handleLogout(), createSession() (+4 more)
### Community 5 - "Projects Service"
### Community 7 - "Projects Service"
Cohesion: 0.11
Nodes (6): load(), unreadCountForUser(), getProject(), listProjects(), getProperty(), listProperties()
Nodes (13): load(), unreadCountForUser(), getProject(), listProjects(), assertNoCycle(), assertParentInCompany(), createProperty(), getAncestorIds() (+5 more)
### Community 6 - "App Shell & Theme"
### Community 8 - "Bootstrap Scripts"
Cohesion: 0.15
Nodes (17): main(), readArg(), slugify(), stripSurroundingQuotes(), main(), readArg(), stripSurroundingQuotes(), normalizeEmail() (+9 more)
### Community 9 - "Maintenance Schedules"
Cohesion: 0.18
Nodes (15): addInterval(), assertAsset(), countOverdueForCompany(), createSchedule(), deleteSchedule(), getSchedule(), listDueAndOverdue(), listEventsForAsset() (+7 more)
### Community 10 - "Assets Service"
Cohesion: 0.2
Nodes (11): assertContainer(), createAsset(), listAssets(), loadTypeWithFields(), moveAsset(), updateAsset(), validateCustomFields(), buildCustomFieldsSchema() (+3 more)
### Community 11 - "App Shell & Bootstrap"
Cohesion: 0.13
Nodes (16): data-sveltekit-preload-data=hover, app.html root document, %sveltekit.head% / %sveltekit.body% placeholders, Tailwind body classes with dark: variants, Dark-mode bootstrap inline script (localStorage['theme']), Argon2id sessions (@node-rs/argon2 + @oslojs/crypto), buildfor_life_budget (sibling), buildfor_life_ops (+8 more)
### Community 7 - "Asset Core"
Cohesion: 0.22
Nodes (10): assertContainer(), createAsset(), loadTypeWithFields(), moveAsset(), updateAsset(), validateCustomFields(), buildCustomFieldsSchema(), getCachedCustomFieldsSchema() (+2 more)
### Community 8 - "Maintenance Core"
Cohesion: 0.22
Nodes (13): addInterval(), assertAsset(), countOverdueForCompany(), createSchedule(), deleteSchedule(), getSchedule(), listDueAndOverdue(), listEventsForAsset() (+5 more)
### Community 9 - "Checklists"
### Community 12 - "Checklists Service"
Cohesion: 0.19
Nodes (7): addTemplateItem(), deleteTemplate(), getInstance(), getTemplate(), listTemplates(), removeTemplateItem(), setItemDone()
Nodes (7): addTemplateItem(), getInstance(), getTemplate(), listInstancesForProperties(), listTemplates(), removeTemplateItem(), setItemDone()
### Community 10 - "Admin Scripts"
Cohesion: 0.24
Nodes (9): main(), readArg(), slugify(), stripSurroundingQuotes(), main(), readArg(), stripSurroundingQuotes(), normalizeEmail() (+1 more)
### Community 13 - "Rooms & Floors Service"
Cohesion: 0.29
Nodes (9): assertProperty(), createFloor(), createRoom(), deleteFloor(), getRoom(), listFloors(), listRoomsWithCounts(), softDeleteRoom() (+1 more)
### Community 11 - "Asset Types Editor"
Cohesion: 0.31
Nodes (8): addFieldDef(), createCompanyAssetType(), deleteCompanyAssetType(), loadEditableType(), normalizeFieldKey(), removeFieldDef(), slugifyTypeSlug(), updateCompanyAssetType()
### Community 12 - "Rooms & Floors"
Cohesion: 0.33
Nodes (8): assertProperty(), createFloor(), createRoom(), getRoom(), listFloors(), listRoomsWithCounts(), softDeleteRoom(), updateRoom()
### Community 13 - "Tasks"
### Community 14 - "Tasks & Subtasks"
Cohesion: 0.36
Nodes (9): addSubtask(), assertWorkPackage(), createTask(), getTaskWithSubtasks(), listTasksForWorkPackage(), removeSubtask(), softDeleteTask(), toggleSubtask() (+1 more)
### Community 14 - "User Management"
Cohesion: 0.38
Nodes (8): assertMembership(), countAdmins(), listCompanyUsers(), removeUserFromCompany(), resetUserPassword(), setUserActive(), setUserRoleInCompany(), updateDisplayName()
### Community 15 - "DB Schema Helpers"
Cohesion: 0.29
Nodes (0):
### Community 16 - "Work Packages"
### Community 15 - "Work Packages"
Cohesion: 0.48
Nodes (6): assertProject(), createWorkPackage(), getWorkPackage(), listWorkPackagesForProject(), softDeleteWorkPackage(), updateWorkPackage()
### Community 17 - "Storage Layer"
### Community 16 - "Signed URL Storage Layer"
Cohesion: 0.38
Nodes (7): /api/files route (signature verification + streaming), HMAC-signed short-lived file URLs, LocalDiskStorage, Phase 5: QR, notifications, S3, S3Storage (future), StorageAdapter interface, Storage model (opaque storage_key)
### Community 18 - "Migration Workflow"
### Community 17 - "Drizzle Migration Conventions"
Cohesion: 0.29
Nodes (7): Use CONCURRENTLY on large-table index changes, Drizzle migrations directory, Review SQL after generate: enum/index/custom_fields, npm run db:generate, npm run db:migrate, npm run db:push (dev only), npm run db:studio (Drizzle Studio)
### Community 19 - "Session Auth"
### Community 18 - "Auth Model"
Cohesion: 0.33
Nodes (6): Auth model (sessions + hashed cookies), company_users role mapping, hooks.server.ts (session validation), (app) route group (authed shell), SHA-256 cookie hashing before DB lookup, Sliding session renewal (30d/15d)
### Community 20 - "Companies Service"
### Community 19 - "Companies Service"
Cohesion: 0.6
Nodes (4): createCompanyWithAdmin(), getCompany(), slugify(), updateCompany()
### Community 21 - "Form Utilities"
Cohesion: 0.4
Nodes (1): e2n()
### Community 22 - "Custom Fields Design"
### Community 20 - "JSONB Custom Fields Policy"
Cohesion: 0.5
Nodes (4): Immutable-key policy reference, Decision: immutable custom-field keys, Decision: JSONB custom fields + asset_field_defs, Phase 1: Properties + Assets
### Community 23 - "Field Types"
### Community 21 - "Cluster 21"
Cohesion: 1.0
Nodes (2): parseCsv(), parseCsvDict()
### Community 22 - "Cluster 22"
Cohesion: 1.0
Nodes (0):
### Community 24 - "Form Helper"
Cohesion: 1.0
Nodes (0):
### Community 25 - "Env Config"
### Community 23 - "Cluster 23"
Cohesion: 1.0
Nodes (2): .env configuration, env.ts (Zod-validated process.env)
### Community 26 - "Seed Script"
### Community 24 - "Cluster 24"
Cohesion: 1.0
Nodes (2): npm run create-user script, src/lib/server/db/schema/
### Community 27 - "Asset Location Design"
Cohesion: 1.0
Nodes (2): Decision: asset_location_history (movable assets), Decision: XOR asset location (project XOR property)
### Community 28 - "Decision Design"
### Community 25 - "Cluster 25"
Cohesion: 1.0
Nodes (2): Decision: decisions scoped to project/property/asset/work_package, Phase 3: Projects + structured decisions
### Community 29 - "Drizzle Config"
### Community 26 - "Cluster 26"
Cohesion: 1.0
Nodes (2): Decision: asset_location_history (movable assets), Decision: XOR asset location (project XOR property)
### Community 27 - "Cluster 27"
Cohesion: 1.0
Nodes (0):
### Community 28 - "Cluster 28"
Cohesion: 1.0
Nodes (0):
### Community 29 - "Cluster 29"
Cohesion: 1.0
Nodes (0):
@@ -647,146 +653,172 @@ Nodes (0):
### Community 118 - "Cluster 118"
Cohesion: 1.0
Nodes (0):
Nodes (1): npm run db:seed
### Community 119 - "Cluster 119"
Cohesion: 1.0
Nodes (0):
Nodes (1): npm run validate (check + build)
### Community 120 - "Cluster 120"
Cohesion: 1.0
Nodes (1): npm run db:seed
Nodes (1): (auth) route group (login shell)
### Community 121 - "Cluster 121"
Cohesion: 1.0
Nodes (1): npm run validate (check + build)
Nodes (1): Phase 0: scaffold (shipped)
### Community 122 - "Cluster 122"
Cohesion: 1.0
Nodes (1): (auth) route group (login shell)
Nodes (1): Phase 2: Checklists + maintenance
### Community 123 - "Cluster 123"
Cohesion: 1.0
Nodes (1): Phase 0: scaffold (shipped)
Nodes (1): Decision: UUID v7 primary keys
### Community 124 - "Cluster 124"
Cohesion: 1.0
Nodes (1): Phase 2: Checklists + maintenance
Nodes (1): Decision: timestamptz UTC everywhere
### Community 125 - "Cluster 125"
Cohesion: 1.0
Nodes (1): Decision: UUID v7 primary keys
Nodes (1): Decision: soft delete (deleted_at)
### Community 126 - "Cluster 126"
Cohesion: 1.0
Nodes (1): Decision: timestamptz UTC everywhere
Nodes (1): Decision: numeric(18,4) + char(3) currency
### Community 127 - "Cluster 127"
Cohesion: 1.0
Nodes (1): Decision: soft delete (deleted_at)
Nodes (1): Decision: company default currency in settings_json
### Community 128 - "Cluster 128"
Cohesion: 1.0
Nodes (1): Decision: numeric(18,4) + char(3) currency
Nodes (1): Decision: tabs = nested routes (not query-string)
### Community 129 - "Cluster 129"
Cohesion: 1.0
Nodes (1): Decision: company default currency in settings_json
Nodes (0):
### Community 130 - "Cluster 130"
Cohesion: 1.0
Nodes (1): Decision: tabs = nested routes (not query-string)
Nodes (0):
### Community 131 - "Cluster 131"
Cohesion: 1.0
Nodes (0):
### Community 132 - "Cluster 132"
Cohesion: 1.0
Nodes (0):
### Community 133 - "Cluster 133"
Cohesion: 1.0
Nodes (0):
### Community 134 - "Cluster 134"
Cohesion: 1.0
Nodes (0):
### Community 135 - "Cluster 135"
Cohesion: 1.0
Nodes (0):
### Community 136 - "Cluster 136"
Cohesion: 1.0
Nodes (0):
## Knowledge Gaps
- **42 isolated node(s):** `buildfor_life_budget (sibling)`, `buildfor_life_repair (sibling)`, `PostgreSQL 16+ via Drizzle ORM + Zod`, `Argon2id sessions (@node-rs/argon2 + @oslojs/crypto)`, `Sharp image thumbnails` (+37 more)
- **73 isolated node(s):** `buildfor_life_budget (sibling)`, `buildfor_life_repair (sibling)`, `PostgreSQL 16+ via Drizzle ORM + Zod`, `Argon2id sessions (@node-rs/argon2 + @oslojs/crypto)`, `Sharp image thumbnails` (+68 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Field Types`** (2 nodes): `needsEnumValues()`, `field-types.ts`
- **Thin community `Cluster 22`** (2 nodes): `needsEnumValues()`, `field-types.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Form Helper`** (2 nodes): `emptyToNull()`, `+page.server.ts`
- **Thin community `Cluster 23`** (2 nodes): `.env configuration`, `env.ts (Zod-validated process.env)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Env Config`** (2 nodes): `.env configuration`, `env.ts (Zod-validated process.env)`
- **Thin community `Cluster 24`** (2 nodes): `npm run create-user script`, `src/lib/server/db/schema/`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Seed Script`** (2 nodes): `npm run create-user script`, `src/lib/server/db/schema/`
- **Thin community `Cluster 25`** (2 nodes): `Decision: decisions scoped to project/property/asset/work_package`, `Phase 3: Projects + structured decisions`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Asset Location Design`** (2 nodes): `Decision: asset_location_history (movable assets)`, `Decision: XOR asset location (project XOR property)`
- **Thin community `Cluster 26`** (2 nodes): `Decision: asset_location_history (movable assets)`, `Decision: XOR asset location (project XOR property)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Decision Design`** (2 nodes): `Decision: decisions scoped to project/property/asset/work_package`, `Phase 3: Projects + structured decisions`
- **Thin community `Cluster 27`** (1 nodes): `drizzle.config.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Drizzle Config`** (1 nodes): `drizzle.config.ts`
- **Thin community `Cluster 28`** (1 nodes): `svelte.config.js`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 30`** (1 nodes): `svelte.config.js`
- **Thin community `Cluster 29`** (1 nodes): `vite.config.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 31`** (1 nodes): `vite.config.ts`
- **Thin community `Cluster 30`** (1 nodes): `app.d.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 32`** (1 nodes): `app.d.ts`
- **Thin community `Cluster 31`** (1 nodes): `accounts.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 33`** (1 nodes): `accounts.ts`
- **Thin community `Cluster 32`** (1 nodes): `notifications.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 34`** (1 nodes): `notifications.ts`
- **Thin community `Cluster 33`** (1 nodes): `roles.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 35`** (1 nodes): `roles.ts`
- **Thin community `Cluster 34`** (1 nodes): `CustomFieldsForm.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 36`** (1 nodes): `CustomFieldsForm.svelte`
- **Thin community `Cluster 35`** (1 nodes): `Sidebar.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 37`** (1 nodes): `Sidebar.svelte`
- **Thin community `Cluster 36`** (1 nodes): `TabNav.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 38`** (1 nodes): `TabNav.svelte`
- **Thin community `Cluster 37`** (1 nodes): `ThemeToggle.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 39`** (1 nodes): `ThemeToggle.svelte`
- **Thin community `Cluster 38`** (1 nodes): `TopBar.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 40`** (1 nodes): `TopBar.svelte`
- **Thin community `Cluster 39`** (1 nodes): `env.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 41`** (1 nodes): `env.ts`
- **Thin community `Cluster 40`** (1 nodes): `types.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 42`** (1 nodes): `types.ts`
- **Thin community `Cluster 41`** (1 nodes): `client.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 43`** (1 nodes): `client.ts`
- **Thin community `Cluster 42`** (1 nodes): `accounts.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 44`** (1 nodes): `accounts.ts`
- **Thin community `Cluster 43`** (1 nodes): `assets.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 45`** (1 nodes): `assets.ts`
- **Thin community `Cluster 44`** (1 nodes): `checklists.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 46`** (1 nodes): `checklists.ts`
- **Thin community `Cluster 45`** (1 nodes): `decisions.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 47`** (1 nodes): `decisions.ts`
- **Thin community `Cluster 46`** (1 nodes): `documents.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 48`** (1 nodes): `documents.ts`
- **Thin community `Cluster 47`** (1 nodes): `index.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 49`** (1 nodes): `index.ts`
- **Thin community `Cluster 48`** (1 nodes): `maintenance.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 50`** (1 nodes): `maintenance.ts`
- **Thin community `Cluster 49`** (1 nodes): `notifications.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 51`** (1 nodes): `notifications.ts`
- **Thin community `Cluster 50`** (1 nodes): `projects.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 52`** (1 nodes): `projects.ts`
- **Thin community `Cluster 51`** (1 nodes): `properties.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 53`** (1 nodes): `properties.ts`
- **Thin community `Cluster 52`** (1 nodes): `rooms.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 54`** (1 nodes): `rooms.ts`
- **Thin community `Cluster 53`** (1 nodes): `tenancy.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 55`** (1 nodes): `tenancy.ts`
- **Thin community `Cluster 54`** (1 nodes): `wiki.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 56`** (1 nodes): `wiki.ts`
- **Thin community `Cluster 55`** (1 nodes): `+error.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 57`** (1 nodes): `+error.svelte`
- **Thin community `Cluster 56`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 58`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 57`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 59`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 58`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 60`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 59`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 60`** (1 nodes): `+page.server.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 61`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 62`** (1 nodes): `+page.server.ts`
- **Thin community `Cluster 62`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 63`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 64`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 64`** (1 nodes): `+page.server.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 65`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 66`** (1 nodes): `+page.server.ts`
- **Thin community `Cluster 66`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 67`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -794,11 +826,11 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 69`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 70`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 70`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 71`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 72`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 72`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 73`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -824,11 +856,11 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 84`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 85`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 85`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 86`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 87`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 87`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 88`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -836,11 +868,11 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 90`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 91`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 91`** (1 nodes): `+page.server.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 92`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 93`** (1 nodes): `+page.server.ts`
- **Thin community `Cluster 93`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 94`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -858,11 +890,11 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 101`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 102`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 102`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 103`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 104`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 104`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 105`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -874,11 +906,11 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 109`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 110`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 110`** (1 nodes): `+page.server.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 111`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 112`** (1 nodes): `+page.server.ts`
- **Thin community `Cluster 112`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 113`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
@@ -886,51 +918,63 @@ Nodes (1): Decision: tabs = nested routes (not query-string)
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 115`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 116`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 116`** (1 nodes): `+layout.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 117`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 118`** (1 nodes): `+layout.svelte`
- **Thin community `Cluster 118`** (1 nodes): `npm run db:seed`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 119`** (1 nodes): `+page.svelte`
- **Thin community `Cluster 119`** (1 nodes): `npm run validate (check + build)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 120`** (1 nodes): `npm run db:seed`
- **Thin community `Cluster 120`** (1 nodes): `(auth) route group (login shell)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 121`** (1 nodes): `npm run validate (check + build)`
- **Thin community `Cluster 121`** (1 nodes): `Phase 0: scaffold (shipped)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 122`** (1 nodes): `(auth) route group (login shell)`
- **Thin community `Cluster 122`** (1 nodes): `Phase 2: Checklists + maintenance`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 123`** (1 nodes): `Phase 0: scaffold (shipped)`
- **Thin community `Cluster 123`** (1 nodes): `Decision: UUID v7 primary keys`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 124`** (1 nodes): `Phase 2: Checklists + maintenance`
- **Thin community `Cluster 124`** (1 nodes): `Decision: timestamptz UTC everywhere`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 125`** (1 nodes): `Decision: UUID v7 primary keys`
- **Thin community `Cluster 125`** (1 nodes): `Decision: soft delete (deleted_at)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 126`** (1 nodes): `Decision: timestamptz UTC everywhere`
- **Thin community `Cluster 126`** (1 nodes): `Decision: numeric(18,4) + char(3) currency`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 127`** (1 nodes): `Decision: soft delete (deleted_at)`
- **Thin community `Cluster 127`** (1 nodes): `Decision: company default currency in settings_json`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 128`** (1 nodes): `Decision: numeric(18,4) + char(3) currency`
- **Thin community `Cluster 128`** (1 nodes): `Decision: tabs = nested routes (not query-string)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 129`** (1 nodes): `Decision: company default currency in settings_json`
- **Thin community `Cluster 129`** (1 nodes): `expenses.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 130`** (1 nodes): `Decision: tabs = nested routes (not query-string)`
- **Thin community `Cluster 130`** (1 nodes): `ExpenseChart.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 131`** (1 nodes): `expenses.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 132`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 133`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 134`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 135`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Cluster 136`** (1 nodes): `+page.svelte`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `load()` connect `Auth & Load Helpers` to `Documents Service`, `Assets Service & CSV`, `Email & Markdown`, `Property Accounts`, `Projects Service`, `Asset Core`, `Maintenance Core`, `Checklists`, `Rooms & Floors`, `Tasks`, `User Management`, `Work Packages`, `Companies Service`, `Form Utilities`?**
_High betweenness centrality (0.298) - this node is a cross-community bridge._
- **Why does `GET()` connect `Assets Service & CSV` to `Auth & Load Helpers`, `Documents Service`, `Property Accounts`, `Asset Core`, `Maintenance Core`?**
_High betweenness centrality (0.120) - this node is a cross-community bridge._
- **Why does `listCompanyUsers()` connect `User Management` to `Auth & Load Helpers`?**
_High betweenness centrality (0.040) - this node is a cross-community bridge._
- **Are the 34 inferred relationships involving `load()` (e.g. with `countOverdueForCompany()` and `listDueAndOverdue()`) actually correct?**
_`load()` has 34 INFERRED edges - model-reasoned connections that need verification._
- **Are the 13 inferred relationships involving `GET()` (e.g. with `syncFieldDefs()` and `handle()`) actually correct?**
_`GET()` has 13 INFERRED edges - model-reasoned connections that need verification._
- **Are the 5 inferred relationships involving `load()` (e.g. with `setActiveCompany()` and `unreadCountForUser()`) actually correct?**
_`load()` has 5 INFERRED edges - model-reasoned connections that need verification._
- **Why does `load()` connect `Page Server Loaders` to `Documents & Storage Adapters`, `Auth Sessions & Catalog`, `CSV & API Endpoints`, `Expenses Service`, `Email & Markdown Rendering`, `Projects Service`, `Bootstrap Scripts`, `Maintenance Schedules`, `Assets Service`, `Checklists Service`, `Rooms & Floors Service`, `Tasks & Subtasks`, `Work Packages`, `Companies Service`?**
_High betweenness centrality (0.308) - this node is a cross-community bridge._
- **Why does `Drizzle ORM + Zod` connect `Expenses Service` to `Page Server Loaders`, `Stack & Deployment Concepts`, `Projects Service`, `Bootstrap Scripts`, `Maintenance Schedules`, `Assets Service`, `Checklists Service`?**
_High betweenness centrality (0.173) - this node is a cross-community bridge._
- **Why does `buildfor_life_ops` connect `Stack & Deployment Concepts` to `Expenses Service`?**
_High betweenness centrality (0.137) - this node is a cross-community bridge._
- **Are the 41 inferred relationships involving `load()` (e.g. with `renderMarkdown()` and `requireCompany()`) actually correct?**
_`load()` has 41 INFERRED edges - model-reasoned connections that need verification._
- **Are the 15 inferred relationships involving `GET()` (e.g. with `syncFieldDefs()` and `handle()`) actually correct?**
_`GET()` has 15 INFERRED edges - model-reasoned connections that need verification._
- **What connects `buildfor_life_budget (sibling)`, `buildfor_life_repair (sibling)`, `PostgreSQL 16+ via Drizzle ORM + Zod` to the rest of the system?**
_42 weakly-connected nodes found - possible documentation gaps or missing edges._
_73 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Page Server Loaders` be split into smaller, more focused modules?**
_Cohesion score 0.04 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_assets_id_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\assets\\[id]\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_assets_id_maintenance_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\assets\\[id]\\maintenance\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_assets_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L6"}], "edges": [{"source": "src_routes_app_properties_id_assets_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_assets_page_server_ts", "target": "assets", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_assets_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_assets_page_server_ts", "target": "src_routes_app_properties_id_assets_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_id_assets_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L6", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L7"}, {"caller_nid": "page_server_load", "callee": "get", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L8"}, {"caller_nid": "page_server_load", "callee": "getDescendantIds", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L10"}, {"caller_nid": "page_server_load", "callee": "listAssets", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts", "source_location": "L12"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_projects_id_decisions_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\projects\\[id]\\decisions\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_e2n", "label": "e2n()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L26"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L28"}], "edges": [{"source": "src_routes_app_properties_id_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "drizzle_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "zod", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "client", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L5", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L6", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "src_routes_app_properties_id_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L11", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "page_server_e2n", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L26", "weight": 1.0}, {"source": "src_routes_app_properties_id_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L28", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L29"}, {"caller_nid": "page_server_load", "callee": "getDescendantIds", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L34"}, {"caller_nid": "page_server_load", "callee": "orderBy", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L35"}, {"caller_nid": "page_server_load", "callee": "where", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L35"}, {"caller_nid": "page_server_load", "callee": "from", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L35"}, {"caller_nid": "page_server_load", "callee": "select", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L35"}, {"caller_nid": "page_server_load", "callee": "and", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L39"}, {"caller_nid": "page_server_load", "callee": "eq", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L40"}, {"caller_nid": "page_server_load", "callee": "isNull", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L41"}, {"caller_nid": "page_server_load", "callee": "ne", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L42"}, {"caller_nid": "page_server_load", "callee": "notInArray", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.server.ts", "source_location": "L43"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_expenses_import_template_csv_server_ts", "label": "+server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L1"}, {"id": "server_get", "label": "GET()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L7"}], "edges": [{"source": "src_routes_app_properties_id_expenses_import_template_csv_server_ts", "target": "guards", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_template_csv_server_ts", "target": "csv", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_template_csv_server_ts", "target": "src_routes_app_properties_id_expenses_import_template_csv_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_template_csv_server_ts", "target": "server_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L7", "weight": 1.0}], "raw_calls": [{"caller_nid": "server_get", "callee": "requireCompany", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L8"}, {"caller_nid": "server_get", "callee": "slice", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L9"}, {"caller_nid": "server_get", "callee": "toISOString", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L9"}, {"caller_nid": "server_get", "callee": "toCsv", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L10"}, {"caller_nid": "server_get", "callee": "csvResponse", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts", "source_location": "L47"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_assets_new_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\assets\\new\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_server_db_schema_expenses_ts", "label": "expenses.ts", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L1"}], "edges": [{"source": "src_lib_server_db_schema_expenses_ts", "target": "pg_core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_lib_server_db_schema_expenses_ts", "target": "src_lib_server_db_schema_properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_lib_server_db_schema_expenses_ts", "target": "src_lib_server_db_schema_accounts", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_lib_server_db_schema_expenses_ts", "target": "src_lib_server_db_schema_tenancy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_lib_server_db_schema_expenses_ts", "target": "src_lib_server_db_schema_shared", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\expenses.ts", "source_location": "L5", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_todos_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L6"}], "edges": [{"source": "src_routes_app_properties_id_todos_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_todos_page_server_ts", "target": "checklists", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_todos_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_todos_page_server_ts", "target": "src_routes_app_properties_id_todos_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_id_todos_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L6", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L7"}, {"caller_nid": "page_server_load", "callee": "get", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L8"}, {"caller_nid": "page_server_load", "callee": "getDescendantIds", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L10"}, {"caller_nid": "page_server_load", "callee": "listInstancesForProperties", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts", "source_location": "L12"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_server_db_schema_shared_ts", "label": "_shared.ts", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L1"}, {"id": "shared_pk", "label": "pk()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98"}, {"id": "shared_fk", "label": "fk()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L99"}, {"id": "shared_createdat", "label": "createdAt()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L100"}, {"id": "shared_updatedat", "label": "updatedAt()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L102"}, {"id": "shared_deletedat", "label": "deletedAt()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L104"}, {"id": "shared_slugcol", "label": "slugCol()", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L105"}], "edges": [{"source": "src_lib_server_db_schema_shared_ts", "target": "drizzle_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "pg_core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_pk", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_fk", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L99", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_createdat", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L100", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_updatedat", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L102", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_deletedat", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L104", "weight": 1.0}, {"source": "src_lib_server_db_schema_shared_ts", "target": "shared_slugcol", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L105", "weight": 1.0}], "raw_calls": [{"caller_nid": "shared_pk", "callee": "default", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98"}, {"caller_nid": "shared_pk", "callee": "primaryKey", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98"}, {"caller_nid": "shared_pk", "callee": "uuid", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98"}, {"caller_nid": "shared_pk", "callee": "sql", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L98"}, {"caller_nid": "shared_fk", "callee": "uuid", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L99"}, {"caller_nid": "shared_createdat", "callee": "defaultNow", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L101"}, {"caller_nid": "shared_createdat", "callee": "notNull", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L101"}, {"caller_nid": "shared_createdat", "callee": "timestamp", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L101"}, {"caller_nid": "shared_updatedat", "callee": "defaultNow", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L103"}, {"caller_nid": "shared_updatedat", "callee": "notNull", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L103"}, {"caller_nid": "shared_updatedat", "callee": "timestamp", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L103"}, {"caller_nid": "shared_deletedat", "callee": "timestamp", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L104"}, {"caller_nid": "shared_slugcol", "callee": "notNull", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L105"}, {"caller_nid": "shared_slugcol", "callee": "varchar", "source_file": "src\\lib\\server\\db\\schema\\_shared.ts", "source_location": "L105"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_projects_id_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\projects\\[id]\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_server_db_schema_properties_ts", "label": "properties.ts", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\properties.ts", "source_location": "L1"}], "edges": [{"source": "src_lib_server_db_schema_properties_ts", "target": "pg_core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\properties.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_lib_server_db_schema_properties_ts", "target": "pg_core", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\properties.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_lib_server_db_schema_properties_ts", "target": "src_lib_server_db_schema_tenancy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\properties.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_lib_server_db_schema_properties_ts", "target": "src_lib_server_db_schema_shared", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\db\\schema\\properties.ts", "source_location": "L4", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_maintenance_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L9"}], "edges": [{"source": "src_routes_app_properties_id_maintenance_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_maintenance_page_server_ts", "target": "maintenance", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_maintenance_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L6", "weight": 1.0}, {"source": "src_routes_app_properties_id_maintenance_page_server_ts", "target": "src_routes_app_properties_id_maintenance_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L7", "weight": 1.0}, {"source": "src_routes_app_properties_id_maintenance_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L9", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L10"}, {"caller_nid": "page_server_load", "callee": "get", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L11"}, {"caller_nid": "page_server_load", "callee": "getDescendantIds", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L13"}, {"caller_nid": "page_server_load", "callee": "all", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L15"}, {"caller_nid": "page_server_load", "callee": "listSchedulesForProperties", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L16"}, {"caller_nid": "page_server_load", "callee": "listEventsForProperties", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts", "source_location": "L17"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_assets_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\assets\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_new_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_emptytonull", "label": "emptyToNull()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L22"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L26"}], "edges": [{"source": "src_routes_app_properties_new_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "drizzle_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "zod", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "client", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L5", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L6", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "src_routes_app_properties_new_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L7", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "page_server_emptytonull", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L22", "weight": 1.0}, {"source": "src_routes_app_properties_new_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L26", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L27"}, {"caller_nid": "page_server_load", "callee": "orderBy", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L28"}, {"caller_nid": "page_server_load", "callee": "where", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L28"}, {"caller_nid": "page_server_load", "callee": "from", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L28"}, {"caller_nid": "page_server_load", "callee": "select", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L28"}, {"caller_nid": "page_server_load", "callee": "and", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L31"}, {"caller_nid": "page_server_load", "callee": "eq", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L31"}, {"caller_nid": "page_server_load", "callee": "isNull", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L31"}, {"caller_nid": "page_server_load", "callee": "get", "source_file": "src\\routes\\(app)\\properties\\new\\+page.server.ts", "source_location": "L33"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_expenses_ts", "label": "expenses.ts", "file_type": "code", "source_file": "src\\lib\\expenses.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_server_csv_parse_ts", "label": "csv-parse.ts", "file_type": "code", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L1"}, {"id": "csv_parse_parsecsv", "label": "parseCsv()", "file_type": "code", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L17"}, {"id": "csv_parse_parsecsvdict", "label": "parseCsvDict()", "file_type": "code", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L88"}], "edges": [{"source": "src_lib_server_csv_parse_ts", "target": "csv_parse_parsecsv", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L17", "weight": 1.0}, {"source": "src_lib_server_csv_parse_ts", "target": "csv_parse_parsecsvdict", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L88", "weight": 1.0}, {"source": "csv_parse_parsecsvdict", "target": "csv_parse_parsecsv", "relation": "calls", "confidence": "EXTRACTED", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L89", "weight": 1.0}], "raw_calls": [{"caller_nid": "csv_parse_parsecsv", "callee": "charCodeAt", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L19"}, {"caller_nid": "csv_parse_parsecsv", "callee": "slice", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L19"}, {"caller_nid": "csv_parse_parsecsv", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L56"}, {"caller_nid": "csv_parse_parsecsv", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L62"}, {"caller_nid": "csv_parse_parsecsv", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L68"}, {"caller_nid": "csv_parse_parsecsv", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L77"}, {"caller_nid": "csv_parse_parsecsv", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L78"}, {"caller_nid": "csv_parse_parsecsvdict", "callee": "map", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L91"}, {"caller_nid": "csv_parse_parsecsvdict", "callee": "push", "source_file": "src\\lib\\server\\csv-parse.ts", "source_location": "L102"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_expenses_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_sub_properties_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L7"}], "edges": [{"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "drizzle_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "client", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "src_routes_app_properties_id_sub_properties_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L5", "weight": 1.0}, {"source": "src_routes_app_properties_id_sub_properties_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L7", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L8"}, {"caller_nid": "page_server_load", "callee": "orderBy", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L9"}, {"caller_nid": "page_server_load", "callee": "where", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L9"}, {"caller_nid": "page_server_load", "callee": "from", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L9"}, {"caller_nid": "page_server_load", "callee": "select", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L9"}, {"caller_nid": "page_server_load", "callee": "and", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L19"}, {"caller_nid": "page_server_load", "callee": "eq", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L20"}, {"caller_nid": "page_server_load", "callee": "eq", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L21"}, {"caller_nid": "page_server_load", "callee": "isNull", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L22"}, {"caller_nid": "page_server_load", "callee": "asc", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts", "source_location": "L25"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_server_db_schema_index_ts", "label": "index.ts", "file_type": "code", "source_file": "src\\lib\\server\\db\\schema\\index.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_projects_id_work_wpid_taskid_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\[taskId]\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_projects_new_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\projects\\new\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_maintenance_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_new_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\new\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_lib_components_expensechart_svelte", "label": "ExpenseChart.svelte", "file_type": "code", "source_file": "src\\lib\\components\\ExpenseChart.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_expenses_import_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_layout_svelte", "label": "+layout.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\+layout.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_flattentree", "label": "flattenTree()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L15"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L49"}], "edges": [{"source": "src_routes_app_properties_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_page_server_ts", "target": "properties", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_page_server_ts", "target": "src_routes_app_properties_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_page_server_ts", "target": "page_server_flattentree", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L15", "weight": 1.0}, {"source": "src_routes_app_properties_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L49", "weight": 1.0}, {"source": "page_server_load", "target": "page_server_flattentree", "relation": "calls", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L52", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_flattentree", "callee": "get", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L19"}, {"caller_nid": "page_server_flattentree", "callee": "push", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L20"}, {"caller_nid": "page_server_flattentree", "callee": "set", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L21"}, {"caller_nid": "page_server_flattentree", "callee": "values", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L23"}, {"caller_nid": "page_server_flattentree", "callee": "sort", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L24"}, {"caller_nid": "page_server_flattentree", "callee": "map", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L28"}, {"caller_nid": "page_server_flattentree", "callee": "walk", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L36"}, {"caller_nid": "page_server_flattentree", "callee": "map", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L39"}, {"caller_nid": "page_server_flattentree", "callee": "has", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L41"}, {"caller_nid": "page_server_flattentree", "callee": "has", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L41"}, {"caller_nid": "page_server_flattentree", "callee": "push", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L42"}, {"caller_nid": "page_server_load", "callee": "error", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L50"}, {"caller_nid": "page_server_load", "callee": "listProperties", "source_file": "src\\routes\\(app)\\properties\\+page.server.ts", "source_location": "L51"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_sub_properties_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_todos_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\todos\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_properties_id_expenses_import_page_server_ts", "label": "+page.server.ts", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L1"}, {"id": "page_server_parsesettings", "label": "parseSettings()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L15"}, {"id": "page_server_load", "label": "load()", "file_type": "code", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L24"}], "edges": [{"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "kit", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L1", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "drizzle_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L2", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "guards", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L3", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "client", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L4", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "tenancy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L5", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "csv_parse", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L6", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "expenses", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L7", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "src_routes_app_properties_id_expenses_import_types", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L8", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "page_server_parsesettings", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L15", "weight": 1.0}, {"source": "src_routes_app_properties_id_expenses_import_page_server_ts", "target": "page_server_load", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L24", "weight": 1.0}, {"source": "page_server_load", "target": "page_server_parsesettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L32", "weight": 1.0}], "raw_calls": [{"caller_nid": "page_server_parsesettings", "callee": "parse", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L18"}, {"caller_nid": "page_server_load", "callee": "requireCompany", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L25"}, {"caller_nid": "page_server_load", "callee": "limit", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L26"}, {"caller_nid": "page_server_load", "callee": "where", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L26"}, {"caller_nid": "page_server_load", "callee": "from", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L26"}, {"caller_nid": "page_server_load", "callee": "select", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L26"}, {"caller_nid": "page_server_load", "callee": "eq", "source_file": "src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts", "source_location": "L29"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "src_routes_app_projects_id_work_wpid_page_svelte", "label": "+page.svelte", "file_type": "code", "source_file": "src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\+page.svelte", "source_location": "L1"}], "edges": [], "raw_calls": []}
+6
View File
@@ -5,6 +5,12 @@
"input_tokens": 0,
"output_tokens": 0,
"files": 189
},
{
"date": "2026-04-27T08:46:41.997113+00:00",
"input_tokens": 0,
"output_tokens": 0,
"files": 47
}
],
"total_input_tokens": 0,
File diff suppressed because one or more lines are too long
+5000 -2074
View File
File diff suppressed because it is too large Load Diff
+47 -28
View File
@@ -2,20 +2,23 @@
"drizzle.config.ts": 1776759714.513955,
"svelte.config.js": 1776759709.852201,
"vite.config.ts": 1776759710.9294431,
"scripts\\create-user.ts": 1776764431.9558957,
"scripts\\create-user.ts": 1777018112.65223,
"scripts\\diag-user.ts": 1776764326.9098525,
"scripts\\seed\\system-asset-types.ts": 1776912939.322366,
"src\\app.d.ts": 1776759729.3791924,
"src\\hooks.server.ts": 1776759818.2697544,
"src\\lib\\accounts.ts": 1776920290.934606,
"src\\lib\\expenses.ts": 1776932953.1379492,
"src\\lib\\field-types.ts": 1776920825.6922204,
"src\\lib\\notifications.ts": 1776931229.9228654,
"src\\lib\\roles.ts": 1776926943.3422728,
"src\\lib\\components\\CustomFieldsForm.svelte": 1776913252.4056394,
"src\\lib\\components\\ExpenseChart.svelte": 1776933040.9983582,
"src\\lib\\components\\Sidebar.svelte": 1776927136.3547218,
"src\\lib\\components\\TabNav.svelte": 1776913159.6860654,
"src\\lib\\components\\ThemeToggle.svelte": 1776759845.5468612,
"src\\lib\\components\\TopBar.svelte": 1776931220.6103387,
"src\\lib\\server\\csv-parse.ts": 1776934140.7087996,
"src\\lib\\server\\csv.ts": 1776917156.6442757,
"src\\lib\\server\\custom-fields-form.ts": 1776913256.9887655,
"src\\lib\\server\\env.ts": 1776931062.8713133,
@@ -29,28 +32,30 @@
"src\\lib\\server\\db\\schema\\checklists.ts": 1776913879.7155764,
"src\\lib\\server\\db\\schema\\decisions.ts": 1776915253.4674976,
"src\\lib\\server\\db\\schema\\documents.ts": 1776912778.9190943,
"src\\lib\\server\\db\\schema\\index.ts": 1776930971.0629141,
"src\\lib\\server\\db\\schema\\expenses.ts": 1776932803.6511443,
"src\\lib\\server\\db\\schema\\index.ts": 1776932816.3969572,
"src\\lib\\server\\db\\schema\\maintenance.ts": 1776913892.3164668,
"src\\lib\\server\\db\\schema\\notifications.ts": 1776930892.1852467,
"src\\lib\\server\\db\\schema\\projects.ts": 1776915246.2953787,
"src\\lib\\server\\db\\schema\\properties.ts": 1776912743.139987,
"src\\lib\\server\\db\\schema\\properties.ts": 1777268843.2618184,
"src\\lib\\server\\db\\schema\\rooms.ts": 1776918599.0392416,
"src\\lib\\server\\db\\schema\\tenancy.ts": 1776930946.3660817,
"src\\lib\\server\\db\\schema\\wiki.ts": 1776916193.8209262,
"src\\lib\\server\\db\\schema\\_shared.ts": 1776930905.6758077,
"src\\lib\\server\\db\\schema\\_shared.ts": 1777268980.6436405,
"src\\lib\\server\\notifications\\email.ts": 1776931016.4405794,
"src\\lib\\server\\notifications\\matrix.ts": 1776931029.1186867,
"src\\lib\\server\\services\\accounts.ts": 1776920323.303991,
"src\\lib\\server\\services\\asset-types.ts": 1776920794.8900447,
"src\\lib\\server\\services\\assets.ts": 1776918741.5526845,
"src\\lib\\server\\services\\checklists.ts": 1776914015.1864648,
"src\\lib\\server\\services\\assets.ts": 1777269430.577926,
"src\\lib\\server\\services\\checklists.ts": 1777269092.1071846,
"src\\lib\\server\\services\\companies.ts": 1776926919.6478693,
"src\\lib\\server\\services\\decisions.ts": 1776931161.1573675,
"src\\lib\\server\\services\\documents.ts": 1776913042.152006,
"src\\lib\\server\\services\\maintenance.ts": 1776914056.7123244,
"src\\lib\\server\\services\\expenses.ts": 1777269052.5917537,
"src\\lib\\server\\services\\maintenance.ts": 1777269075.0133095,
"src\\lib\\server\\services\\notifications.ts": 1776931111.3630683,
"src\\lib\\server\\services\\projects.ts": 1776915354.1029918,
"src\\lib\\server\\services\\properties.ts": 1776913017.5585654,
"src\\lib\\server\\services\\properties.ts": 1777278959.9801009,
"src\\lib\\server\\services\\rooms.ts": 1776918691.0627687,
"src\\lib\\server\\services\\tasks.ts": 1776931148.6006575,
"src\\lib\\server\\services\\users.ts": 1776926913.1441553,
@@ -86,11 +91,11 @@
"src\\routes\\(app)\\assets\\+page.svelte": 1776917178.3600802,
"src\\routes\\(app)\\assets\\export.csv\\+server.ts": 1776917162.3927114,
"src\\routes\\(app)\\assets\\new\\+page.server.ts": 1776918891.8736053,
"src\\routes\\(app)\\assets\\new\\+page.svelte": 1776918929.98384,
"src\\routes\\(app)\\assets\\new\\+page.svelte": 1776933848.5311816,
"src\\routes\\(app)\\assets\\[id]\\+layout.server.ts": 1776918845.937803,
"src\\routes\\(app)\\assets\\[id]\\+layout.svelte": 1776918861.8613749,
"src\\routes\\(app)\\assets\\[id]\\+page.server.ts": 1776918966.9773984,
"src\\routes\\(app)\\assets\\[id]\\+page.svelte": 1776918977.7803142,
"src\\routes\\(app)\\assets\\[id]\\+page.svelte": 1776933888.6220212,
"src\\routes\\(app)\\assets\\[id]\\documents\\+page.server.ts": 1776913388.3625875,
"src\\routes\\(app)\\assets\\[id]\\documents\\+page.svelte": 1776913399.9952705,
"src\\routes\\(app)\\assets\\[id]\\history\\+page.server.ts": 1776913363.7884815,
@@ -100,7 +105,7 @@
"src\\routes\\(app)\\assets\\[id]\\logs\\+page.server.ts": 1776913374.1819277,
"src\\routes\\(app)\\assets\\[id]\\logs\\+page.svelte": 1776913381.2650573,
"src\\routes\\(app)\\assets\\[id]\\maintenance\\+page.server.ts": 1776918070.6355166,
"src\\routes\\(app)\\assets\\[id]\\maintenance\\+page.svelte": 1776914405.4364533,
"src\\routes\\(app)\\assets\\[id]\\maintenance\\+page.svelte": 1776933855.15931,
"src\\routes\\(app)\\assets\\[id]\\maintenance\\events\\[eventId]\\+page.server.ts": 1776914228.1214633,
"src\\routes\\(app)\\assets\\[id]\\maintenance\\events\\[eventId]\\+page.svelte": 1776914244.2005274,
"src\\routes\\(app)\\assets\\[id]\\move\\+page.server.ts": 1776919009.9313874,
@@ -117,15 +122,15 @@
"src\\routes\\(app)\\projects\\+page.server.ts": 1776915423.3344169,
"src\\routes\\(app)\\projects\\+page.svelte": 1776915434.5388634,
"src\\routes\\(app)\\projects\\new\\+page.server.ts": 1776918064.9852977,
"src\\routes\\(app)\\projects\\new\\+page.svelte": 1776915457.2821925,
"src\\routes\\(app)\\projects\\new\\+page.svelte": 1776933886.386777,
"src\\routes\\(app)\\projects\\[id]\\+layout.server.ts": 1776915464.0169995,
"src\\routes\\(app)\\projects\\[id]\\+layout.svelte": 1776916623.3352191,
"src\\routes\\(app)\\projects\\[id]\\+page.server.ts": 1776915476.949084,
"src\\routes\\(app)\\projects\\[id]\\+page.svelte": 1776915496.4709525,
"src\\routes\\(app)\\projects\\[id]\\+page.svelte": 1776933887.4594047,
"src\\routes\\(app)\\projects\\[id]\\assets\\+page.server.ts": 1776915627.8771396,
"src\\routes\\(app)\\projects\\[id]\\assets\\+page.svelte": 1776915636.780885,
"src\\routes\\(app)\\projects\\[id]\\decisions\\+page.server.ts": 1776915599.6791656,
"src\\routes\\(app)\\projects\\[id]\\decisions\\+page.svelte": 1776917192.7825646,
"src\\routes\\(app)\\projects\\[id]\\decisions\\+page.svelte": 1776933891.6662703,
"src\\routes\\(app)\\projects\\[id]\\decisions\\export.csv\\+server.ts": 1776917170.5648644,
"src\\routes\\(app)\\projects\\[id]\\documents\\+page.server.ts": 1776915643.2300684,
"src\\routes\\(app)\\projects\\[id]\\documents\\+page.svelte": 1776915654.9358807,
@@ -144,25 +149,36 @@
"src\\routes\\(app)\\projects\\[id]\\work\\+page.server.ts": 1776915505.82269,
"src\\routes\\(app)\\projects\\[id]\\work\\+page.svelte": 1776915517.9951062,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\+page.server.ts": 1776915525.877059,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\+page.svelte": 1776919721.2087197,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\+page.svelte": 1776933889.7585225,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\[taskId]\\+page.server.ts": 1776915557.2259672,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\[taskId]\\+page.svelte": 1776919694.229038,
"src\\routes\\(app)\\properties\\+page.server.ts": 1776913103.3089087,
"src\\routes\\(app)\\properties\\+page.svelte": 1776913114.269045,
"src\\routes\\(app)\\properties\\new\\+page.server.ts": 1776913120.8220265,
"src\\routes\\(app)\\properties\\new\\+page.svelte": 1776913139.3366928,
"src\\routes\\(app)\\properties\\[id]\\+layout.server.ts": 1776913161.8158467,
"src\\routes\\(app)\\properties\\[id]\\+layout.svelte": 1776919919.0561438,
"src\\routes\\(app)\\properties\\[id]\\+page.server.ts": 1776913174.1071742,
"src\\routes\\(app)\\properties\\[id]\\+page.svelte": 1776913195.5002894,
"src\\routes\\(app)\\projects\\[id]\\work\\[wpId]\\[taskId]\\+page.svelte": 1776933890.5722253,
"src\\routes\\(app)\\properties\\+page.server.ts": 1777276929.1625655,
"src\\routes\\(app)\\properties\\+page.svelte": 1777276938.2817636,
"src\\routes\\(app)\\properties\\new\\+page.server.ts": 1777269213.3164072,
"src\\routes\\(app)\\properties\\new\\+page.svelte": 1777269235.010823,
"src\\routes\\(app)\\properties\\[id]\\+layout.server.ts": 1777269158.567569,
"src\\routes\\(app)\\properties\\[id]\\+layout.svelte": 1777269527.4078188,
"src\\routes\\(app)\\properties\\[id]\\+page.server.ts": 1777269189.052704,
"src\\routes\\(app)\\properties\\[id]\\+page.svelte": 1777269198.207656,
"src\\routes\\(app)\\properties\\[id]\\accounts\\+page.server.ts": 1776919929.527811,
"src\\routes\\(app)\\properties\\[id]\\accounts\\+page.svelte": 1776920324.8604333,
"src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts": 1776913196.9792116,
"src\\routes\\(app)\\properties\\[id]\\assets\\+page.svelte": 1776919046.6825135,
"src\\routes\\(app)\\properties\\[id]\\assets\\+page.server.ts": 1777269436.916948,
"src\\routes\\(app)\\properties\\[id]\\assets\\+page.svelte": 1777269466.5592797,
"src\\routes\\(app)\\properties\\[id]\\documents\\+page.server.ts": 1776913212.7782526,
"src\\routes\\(app)\\properties\\[id]\\documents\\+page.svelte": 1776913224.460486,
"src\\routes\\(app)\\properties\\[id]\\expenses\\+page.server.ts": 1777269312.9716668,
"src\\routes\\(app)\\properties\\[id]\\expenses\\+page.svelte": 1777269370.880504,
"src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.server.ts": 1776934262.3544624,
"src\\routes\\(app)\\properties\\[id]\\expenses\\import\\+page.svelte": 1776934221.4350948,
"src\\routes\\(app)\\properties\\[id]\\expenses\\import\\template.csv\\+server.ts": 1776934226.9037814,
"src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.server.ts": 1777269483.5094693,
"src\\routes\\(app)\\properties\\[id]\\maintenance\\+page.svelte": 1777269504.9545746,
"src\\routes\\(app)\\properties\\[id]\\rooms\\+page.server.ts": 1776918787.5071964,
"src\\routes\\(app)\\properties\\[id]\\rooms\\+page.svelte": 1776919689.9999452,
"src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.server.ts": 1777269241.9622283,
"src\\routes\\(app)\\properties\\[id]\\sub-properties\\+page.svelte": 1777269248.938543,
"src\\routes\\(app)\\properties\\[id]\\todos\\+page.server.ts": 1777269507.4355745,
"src\\routes\\(app)\\properties\\[id]\\todos\\+page.svelte": 1777269519.2786589,
"src\\routes\\(app)\\settings\\notifications\\+page.server.ts": 1776931253.4051654,
"src\\routes\\(app)\\settings\\notifications\\+page.svelte": 1776931270.4663363,
"src\\routes\\(app)\\wiki\\+page.server.ts": 1776916429.1431959,
@@ -185,7 +201,10 @@
"src\\routes\\api\\qr\\+server.ts": 1776917032.1335907,
"src\\routes\\logout\\+server.ts": 1776760388.366003,
"src\\routes\\switch-company\\+server.ts": 1776914979.4505768,
"README.md": 1776761445.7901409,
"drizzle\\README.md": 1776759950.7471619,
"DEPLOYMENT.md": 1777262556.380254,
"README.md": 1777018156.956704,
"drizzle\\README.md": 1777018117.6299796,
"graphify-out\\graph.html": 1777279589.8841953,
"graphify-out\\GRAPH_REPORT.md": 1777279513.4607954,
"src\\app.html": 1776759722.8929892
}
+1
View File
@@ -16,6 +16,7 @@
"db:studio": "drizzle-kit studio",
"db:seed": "tsx scripts/seed/system-asset-types.ts",
"create-user": "tsx scripts/create-user.ts",
"reminders:check": "tsx scripts/maintenance-reminders.ts",
"prepare": "husky || true"
},
"dependencies": {
+71
View File
@@ -0,0 +1,71 @@
import 'dotenv/config';
import { pool } from '../src/lib/server/db/client';
import {
runRemindersOnce,
type RunResult
} from '../src/lib/server/services/maintenance-reminders';
function stripSurroundingQuotes(s: string | undefined): string | undefined {
if (!s || s.length < 2) return s;
const first = s[0];
const last = s[s.length - 1];
if ((first === "'" && last === "'") || (first === '"' && last === '"')) {
return s.slice(1, -1);
}
return s;
}
function readArg(flag: string, fallback?: string): string | undefined {
const i = process.argv.indexOf(flag);
return stripSurroundingQuotes(i >= 0 ? process.argv[i + 1] : fallback);
}
function hasFlag(flag: string): boolean {
return process.argv.includes(flag);
}
async function main(): Promise<void> {
const soonRaw = readArg('--soon-days', '7') ?? '7';
const soonDays = Number.parseInt(soonRaw, 10);
if (!Number.isFinite(soonDays) || soonDays < 0 || soonDays > 365) {
console.error('--soon-days must be an integer in [0, 365]');
process.exit(2);
}
const companyId = readArg('--company');
const dryRun = hasFlag('--dry-run');
const backfill = hasFlag('--backfill');
if (dryRun && backfill) {
console.error('--dry-run and --backfill are mutually exclusive');
process.exit(2);
}
const startedAt = new Date();
let result: RunResult;
try {
result = await runRemindersOnce({ companyId, soonDays, dryRun, backfill });
} catch (e) {
console.error(JSON.stringify({ ok: false, error: (e as Error).message }));
process.exit(1);
}
const out = {
ok: true,
startedAt: startedAt.toISOString(),
durationMs: Date.now() - startedAt.getTime(),
soonDays,
companyId: companyId ?? null,
...result
};
// Single-line JSON so journald/grep handle it cleanly.
console.log(JSON.stringify(out));
}
main()
.catch((e) => {
console.error(JSON.stringify({ ok: false, error: (e as Error).message }));
process.exitCode = 1;
})
.finally(async () => {
await pool.end().catch(() => {});
});
+7
View File
@@ -76,8 +76,15 @@ export const notificationKindEnum = pgEnum('notification_kind', [
'asset_moved',
'decision_created',
'maintenance_event_recorded',
'maintenance_due_soon',
'maintenance_overdue',
'generic'
]);
export const maintenanceReminderKindEnum = pgEnum('maintenance_reminder_kind', [
'due_soon',
'overdue'
]);
export const expenseKindEnum = pgEnum('expense_kind', [
'water',
'electricity',
+35 -2
View File
@@ -1,8 +1,16 @@
import { pgTable, varchar, text, integer, numeric, timestamp, boolean, index } from 'drizzle-orm/pg-core';
import { pgTable, varchar, text, integer, numeric, timestamp, boolean, index, uniqueIndex } from 'drizzle-orm/pg-core';
import { users } from './tenancy';
import { assets } from './assets';
import { checklistTemplates, checklistInstances } from './checklists';
import { scheduleKindEnum, intervalUnitEnum, pk, fk, createdAt, updatedAt } from './_shared';
import {
scheduleKindEnum,
intervalUnitEnum,
maintenanceReminderKindEnum,
pk,
fk,
createdAt,
updatedAt
} from './_shared';
export const maintenanceSchedules = pgTable(
'maintenance_schedules',
@@ -80,7 +88,32 @@ export const maintenanceEvents = pgTable(
})
);
// Dedup log for the reminder cron. We insert (schedule_id, kind, due_at)
// before firing notify(); the unique index makes the insert atomic, so
// concurrent or repeated runs cannot double-fire for the same window.
// After service is recorded, schedules.next_due_at advances → new tuple →
// reminder fires fresh. Rows are kept indefinitely for audit; a separate
// cleanup task can prune > 90 days old.
export const maintenanceRemindersSent = pgTable(
'maintenance_reminders_sent',
{
id: pk(),
scheduleId: fk('schedule_id')
.notNull()
.references(() => maintenanceSchedules.id, { onDelete: 'cascade' }),
kind: maintenanceReminderKindEnum('kind').notNull(),
dueAt: timestamp('due_at', { withTimezone: true }).notNull(),
firedAt: timestamp('fired_at', { withTimezone: true }).notNull().defaultNow()
},
(t) => ({
// The dedup key. INSERT … ON CONFLICT DO NOTHING relies on this.
dedupUq: uniqueIndex('mrs_schedule_kind_due_uq').on(t.scheduleId, t.kind, t.dueAt),
bySchedule: index('mrs_by_schedule').on(t.scheduleId, t.kind)
})
);
export type MaintenanceSchedule = typeof maintenanceSchedules.$inferSelect;
export type NewMaintenanceSchedule = typeof maintenanceSchedules.$inferInsert;
export type UsageReading = typeof usageReadings.$inferSelect;
export type MaintenanceEvent = typeof maintenanceEvents.$inferSelect;
export type MaintenanceReminderSent = typeof maintenanceRemindersSent.$inferSelect;
@@ -0,0 +1,226 @@
import { and, eq, inArray, isNotNull, lte } from 'drizzle-orm';
import { db } from '$lib/server/db/client';
import { assets } from '$lib/server/db/schema/assets';
import { properties } from '$lib/server/db/schema/properties';
import {
maintenanceRemindersSent,
maintenanceSchedules
} from '$lib/server/db/schema/maintenance';
import { companyUsers } from '$lib/server/db/schema/tenancy';
import { notify } from './notifications';
export type ReminderKind = 'due_soon' | 'overdue';
export interface DueSchedule {
scheduleId: string;
scheduleName: string;
nextDueAt: Date;
kind: ReminderKind;
assetId: string;
assetName: string;
companyId: string;
propertyId: string | null;
propertyName: string | null;
}
/**
* Time-based active schedules whose next_due_at is overdue or within the
* `soonDays` window. Joined to asset → property so the notification body can
* cite the location. Usage-based schedules don't have next_due_at and are
* intentionally excluded — they need a different trigger (usage-reading
* crossover) which isn't part of this iteration.
*/
export async function findDueSchedules(opts: {
companyId?: string;
soonDays: number;
now?: Date;
}): Promise<DueSchedule[]> {
const now = opts.now ?? new Date();
const horizon = new Date(now.getTime() + opts.soonDays * 86_400_000);
const where = [
eq(maintenanceSchedules.active, true),
eq(maintenanceSchedules.kind, 'time'),
isNotNull(maintenanceSchedules.nextDueAt),
lte(maintenanceSchedules.nextDueAt, horizon)
];
if (opts.companyId) where.push(eq(assets.companyId, opts.companyId));
const rows = await db
.select({
scheduleId: maintenanceSchedules.id,
scheduleName: maintenanceSchedules.name,
nextDueAt: maintenanceSchedules.nextDueAt,
assetId: maintenanceSchedules.assetId,
assetName: assets.name,
companyId: assets.companyId,
propertyId: assets.currentPropertyId,
propertyName: properties.name
})
.from(maintenanceSchedules)
.innerJoin(assets, eq(assets.id, maintenanceSchedules.assetId))
.leftJoin(properties, eq(properties.id, assets.currentPropertyId))
.where(and(...where));
return rows
.filter((r): r is typeof r & { nextDueAt: Date } => r.nextDueAt !== null)
.map((r) => ({
scheduleId: r.scheduleId,
scheduleName: r.scheduleName,
nextDueAt: r.nextDueAt,
kind: r.nextDueAt < now ? ('overdue' as const) : ('due_soon' as const),
assetId: r.assetId,
assetName: r.assetName,
companyId: r.companyId,
propertyId: r.propertyId,
propertyName: r.propertyName
}));
}
/** admin + manager user_ids for a company. */
async function recipientsFor(companyId: string): Promise<string[]> {
const rows = await db
.select({ userId: companyUsers.userId })
.from(companyUsers)
.where(
and(
eq(companyUsers.companyId, companyId),
inArray(companyUsers.role, ['admin', 'manager'])
)
);
return Array.from(new Set(rows.map((r) => r.userId)));
}
/**
* Try to record that a reminder is being fired. Returns true if this is the
* first time for (schedule, kind, due_at), false if it was already logged.
* The unique index makes this atomic across concurrent runs.
*/
async function tryRecordSent(
scheduleId: string,
kind: ReminderKind,
dueAt: Date
): Promise<boolean> {
const result = await db
.insert(maintenanceRemindersSent)
.values({ scheduleId, kind, dueAt })
.onConflictDoNothing({
target: [
maintenanceRemindersSent.scheduleId,
maintenanceRemindersSent.kind,
maintenanceRemindersSent.dueAt
]
})
.returning({ id: maintenanceRemindersSent.id });
return result.length > 0;
}
function buildBody(s: DueSchedule, now: Date): string {
const due = s.nextDueAt;
const days = Math.round((due.getTime() - now.getTime()) / 86_400_000);
const where = s.propertyName ? ` at ${s.propertyName}` : '';
if (s.kind === 'overdue') {
const overdueDays = Math.max(0, -days);
return `${s.scheduleName} on ${s.assetName}${where} is overdue by ${overdueDays} day${overdueDays === 1 ? '' : 's'} (was due ${due.toISOString().slice(0, 10)}).`;
}
return `${s.scheduleName} on ${s.assetName}${where} is due in ${days} day${days === 1 ? '' : 's'} (${due.toISOString().slice(0, 10)}).`;
}
export interface RunOpts {
companyId?: string;
soonDays: number;
now?: Date;
dryRun?: boolean;
/**
* On first deploy, mark every currently-due/overdue schedule as already
* notified so day-one isn't a deluge of stale alerts. Returns count in
* `backfilled`. Existing dedup rows are not touched (still ON CONFLICT
* DO NOTHING).
*/
backfill?: boolean;
}
export interface RunResult {
scanned: number;
fired: number;
skippedDedup: number;
noRecipients: number;
backfilled: number;
dryRun: boolean;
}
export async function runRemindersOnce(opts: RunOpts): Promise<RunResult> {
const now = opts.now ?? new Date();
const due = await findDueSchedules({
companyId: opts.companyId,
soonDays: opts.soonDays,
now
});
const result: RunResult = {
scanned: due.length,
fired: 0,
skippedDedup: 0,
noRecipients: 0,
backfilled: 0,
dryRun: !!opts.dryRun
};
// Backfill: log everything as already-sent without notifying. Useful on
// first deploy. Doesn't depend on dryRun because backfill is the entire
// effect when set.
if (opts.backfill) {
for (const s of due) {
const inserted = await tryRecordSent(s.scheduleId, s.kind, s.nextDueAt);
if (inserted) result.backfilled += 1;
else result.skippedDedup += 1;
}
return result;
}
// Cache recipients per company within this run.
const recipientsCache = new Map<string, string[]>();
for (const s of due) {
let userIds = recipientsCache.get(s.companyId);
if (!userIds) {
userIds = await recipientsFor(s.companyId);
recipientsCache.set(s.companyId, userIds);
}
if (userIds.length === 0) {
result.noRecipients += 1;
continue;
}
if (opts.dryRun) {
result.fired += 1;
continue;
}
// Atomically claim this (schedule, kind, due_at). Only proceed to notify
// if we won the insert.
const claimed = await tryRecordSent(s.scheduleId, s.kind, s.nextDueAt);
if (!claimed) {
result.skippedDedup += 1;
continue;
}
const title =
s.kind === 'overdue'
? `Maintenance overdue: ${s.scheduleName}`
: `Maintenance due soon: ${s.scheduleName}`;
const notificationKind =
s.kind === 'overdue' ? 'maintenance_overdue' : 'maintenance_due_soon';
await notify({
companyId: s.companyId,
userIds,
kind: notificationKind,
title,
body: buildBody(s, now),
link: `/assets/${s.assetId}/maintenance`
});
result.fired += 1;
}
return result;
}
+2
View File
@@ -12,6 +12,8 @@ export type NotificationKind =
| 'asset_moved'
| 'decision_created'
| 'maintenance_event_recorded'
| 'maintenance_due_soon'
| 'maintenance_overdue'
| 'generic';
export interface NotifyInput {
+24
View File
@@ -17,6 +17,28 @@ export interface PropertyCreateInput {
notes?: string | null;
}
// Soft cap. Properties past this depth still save, but get logged so a sudden
// spike in journal entries draws attention to a tree growing pathologically.
// Exists because getDescendantIds is an unbounded recursive CTE and deep
// hierarchies are also painful to navigate in the UI.
const MAX_RECOMMENDED_DEPTH = 5;
async function warnIfDeep(
companyId: string,
propertyId: string,
candidateParentId: string
): Promise<void> {
const parentAncestors = await getAncestorIds(companyId, candidateParentId);
const newChildDepth = parentAncestors.length + 1;
if (newChildDepth > MAX_RECOMMENDED_DEPTH) {
console.warn(
`[properties] depth cap exceeded: parenting ${propertyId} under ${candidateParentId} ` +
`places it at depth ${newChildDepth} (recommended max: ${MAX_RECOMMENDED_DEPTH}). ` +
`Allowed but flagged.`
);
}
}
async function assertParentInCompany(companyId: string, parentId: string): Promise<void> {
const [row] = await db
.select({ id: properties.id })
@@ -35,6 +57,7 @@ async function assertParentInCompany(companyId: string, parentId: string): Promi
export async function createProperty(input: PropertyCreateInput): Promise<{ id: string }> {
if (input.parentId) {
await assertParentInCompany(input.companyId, input.parentId);
await warnIfDeep(input.companyId, '<new>', input.parentId);
}
const values: NewProperty = {
companyId: input.companyId,
@@ -86,6 +109,7 @@ export async function updateProperty(
if (patch.parentId === id) throw new Error('a property cannot be its own parent');
await assertParentInCompany(companyId, patch.parentId);
await assertNoCycle(companyId, id, patch.parentId);
await warnIfDeep(companyId, id, patch.parentId);
}
await db
.update(properties)
+46 -2
View File
@@ -1,9 +1,53 @@
import { error } from '@sveltejs/kit';
import { listProperties } from '$lib/server/services/properties';
import type { Property } from '$lib/server/db/schema/properties';
import type { PageServerLoad } from './$types';
export type PropertyRow = Property & { depth: number };
/**
* Reorder the flat company-scoped property list into a depth-first traversal
* so parents render immediately above their children. Within each level we
* sort by name. Orphan rows (parent_id points outside the visible set —
* shouldn't happen with the current restrict-on-delete policy, but defended
* here so the UI never silently drops a row) are appended at the end as roots.
*/
function flattenTree(rows: Property[]): PropertyRow[] {
const byParent = new Map<string | null, Property[]>();
for (const r of rows) {
const key = r.parentId;
const list = byParent.get(key);
if (list) list.push(r);
else byParent.set(key, [r]);
}
for (const list of byParent.values()) {
list.sort((a, b) => a.name.localeCompare(b.name));
}
const out: PropertyRow[] = [];
const visible = new Set(rows.map((r) => r.id));
function walk(parentId: string | null, depth: number): void {
const list = byParent.get(parentId) ?? [];
for (const r of list) {
out.push({ ...r, depth });
walk(r.id, depth + 1);
}
}
walk(null, 0);
if (out.length < rows.length) {
const seen = new Set(out.map((r) => r.id));
for (const r of rows) {
if (!seen.has(r.id) && (!r.parentId || !visible.has(r.parentId))) {
out.push({ ...r, depth: 0 });
}
}
}
return out;
}
export const load: PageServerLoad = async ({ locals }) => {
if (!locals.company) throw error(400, 'No active company. Pick one from the sidebar.');
const rows = await listProperties(locals.company.id);
return { properties: rows };
const flat = await listProperties(locals.company.id);
return { properties: flattenTree(flat) };
};
+3 -1
View File
@@ -42,7 +42,9 @@
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
{#each data.properties as p}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/30">
<td class="px-4 py-2 text-sm font-medium text-gray-900 dark:text-gray-100">
<td class="py-2 pr-4 text-sm font-medium text-gray-900 dark:text-gray-100"
style:padding-left="{1 + p.depth * 1.5}rem">
{#if p.depth > 0}<span class="mr-1 select-none text-gray-400 dark:text-gray-500"></span>{/if}
<a href="/properties/{p.id}" class="hover:text-primary-600 dark:hover:text-primary-400">{p.name}</a>
</td>
<td class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400">{p.kind ?? '—'}</td>