Hugo Fund Formation Platform16 Apr, 05:07 CET

Recent changes

Material refactors and behaviour changes shipped to hugo.nordiclawfirm.com.

2026-04-13 — Sponsor-level export templates + sponsor details

Export templates now inherit from sponsor
  • Sponsor-level master templates (migration 0063): sponsors gained export_template_{isl,ef,msl}_r2_key columns alongside the existing fund-level columns. The three export kinds are ISL (Individual Side Letter), EF (Election Form), MSL (Master Side Letter).
  • Resolution precedence at export time (per kind): fund-own override → sponsor master → hardcoded generateSideLetter fallback. Implemented by resolveFundTemplateKey(db, fundId, kind) in src/services/sponsor-export-templates.ts. No file copying — columns just point at R2 keys.
  • Sponsor details (migration 0065): sponsors gained firm_name, firm_address, and firm_country columns mirrored as optional fund-level overrides. resolveFundFirmDetails(db, fundId) resolves each field independently with per-field COALESCE (fund override → sponsor default → null) and tracks each field's source ('fund' | 'sponsor' | 'none'). These values are injected into templates as the {firm_name}, {firm_address}, {firm_country} placeholders.
  • Sponsor-details rename: column and placeholder names stayed firm_* for stability, but user-visible labels were renamed to "Sponsor details" / "Sponsor name" / "Sponsor address" / "Sponsor country" since they describe the sponsor law firm, not Hugo's own firm.
  • Settings UI: per-sponsor at /sponsors/:id/export/settings, per-fund at /funds/:id/export/settings. Both pages now carry a sponsor-details form, master-template upload slots per kind, and a supported-placeholders reference. The fund-level view shows "Inherits from sponsor" captions on empty override fields.
  • New docs page: Export Templates covers the three kinds, the inheritance model, sponsor details, and the placeholder vocabulary.

2026-04-13 — Codebase health pass + P2 follow-up wave

Three parallel audit agents (architecture, infrastructure, DX) produced the findings under docs/audit/ in the worker repo. A plan-review agent validated the prioritisation, three implementation agents shipped the P0/P1 fixes, then eight implementation agents in two parallel waves shipped the P2 follow-ups. A post-implementation review verified each wave before commit.

Net result
  • −5,637 LOC across 60 files (31 modified, 29 created)
  • 307 tests passing — up from 254 (+53 new tests across 6 previously untested services)
  • tsc --noEmit clean across the tree, including tests/** and scripts/** for the first time
  • CI workflow gates every push: typecheck → vitest → discovery:check

Security & correctness (P0)

Auth fail-closed

src/middleware/auth.ts previously fell through to a u_alice admin guest if CF Access was misconfigured. Now: when REQUIRE_CF_ACCESS=true, missing JWT + missing service-token headers returns 401. Service tokens are accepted but resolve to userRole: 'user' — admin routes still 403. The mock-user / cookie / u_alice fallback only fires when REQUIRE_CF_ACCESS !== 'true' (local dev). See Authentication.

Other P0 fixes
  • Auth middleware now uses retry-wrapped D1. Was bypassing the proxy, so transient D1 errors on the first query of a request hit users as 500s instead of retrying.
  • Scheduled cron sweep re-enabled (*/5 * * * *). Had been left empty during an unrelated SITREP cleanup — the sweeper handles stuck-parse re-enqueue, R2 orphan scan, search_cache TTL, 90-day export retention, and rate-limit table pruning.
  • R2 temp-import prefix fix: writer was using _tmp/imports/, sweeper was listing tmp/imports/. Aligned to tmp/imports/; existing orphans need a one-shot manual cleanup.
  • CORS wildcard origin dropped. Word add-in is same-origin and Zapier/n8n/MCP are server-to-server, so no browser cross-origin consumers exist. Removing the middleware also dropped the Authorization header allowlist that invited accidental Bearer-token use.
  • npm run typecheck + GitHub Actions CI on every push and PR. Deploys can no longer ship type errors.

Architecture (P2 follow-ups, all shipped 2026-04-13 PM)

src/index.tsx slim-down

2217 → 1275 LOC (−42%). Extracted three large handlers into services:

  • src/services/chat-router.ts/api/chat
  • src/services/clause-search.ts/api/clauses/search
  • src/services/lp-profile.ts/api/lp-profile, migrated from the deleted comparable-filter-legacy.ts to the shared parseComparableFilterFromQuery in services/comparables.ts
src/components/Layout.tsx split

2211 → 744 LOC (−66%). Extracted four focused files:

  • Sidebar.tsx — nav data, group rendering, kbd badges
  • SearchModal.tsx — global search modal
  • HelpModal.tsx — keyboard-shortcut help
  • boot-scripts.ts — dark-mode init, htmx error interceptor, helper blobs
src/pages/lp-tasks.tsx split

3348 → 2796 LOC + 976 LOC of new helpers. All 42 inline DB.prepare() calls extracted to src/db/lp-tasks.ts and src/services/lp-tasks.ts. All 6 as any casts eliminated. 13 new LIMIT 500 defaults on list-returning queries.

DB query rollup

145 → 28 inline DB.prepare() across the five fattest non-lp-tasks files (admin, mfn-settings, review, investor-import, funds) — an 81% reduction. The remaining 28 are intentional (atomic db.batch() contexts or dynamic-table DELETE in undo paths). New typed-helper modules:

  • src/db/admin.ts (24 helpers)
  • src/db/mfn.ts (28 helpers)
  • src/db/review.ts (12 helpers)
  • src/db/investor-import.ts (22 helpers)
  • src/db/funds.ts (listFundsForUser, the most-duplicated shape in the codebase)

~35 new LIMIT clauses across these helpers.

Cross-page imports cleanup

11 → 2 sibling-page imports. Pages should be leaves — no other page should import from them. Extracted 11 new files to src/components/ and src/db/:

  • LpSidebar, SideLetterRow, OpsTagsSection, ClauseDetailCards, FundClosingsContent, FundLpacContent
  • db/commitments.ts, db/clause-tags.ts, db/closings.ts, db/lpac.ts
  • lib/isl-preview-script.ts
Centralized helpers
  • src/lib/r2-keys.tsdocumentsKey, exportsKey, exportTemplateKey, tempImportKey, testFileKey + R2_PREFIXES constant. 7 writer sites rerouted.
  • src/lib/cache-headers.tssetPrivateCache(c) / setNoStore(c), always pairing Cache-Control with Vary: Cookie, HX-Request, Accept-Encoding. Fixed a cross-user cache leak risk on investor-detail (had no Vary).
  • src/lib/api-responses.ts, src/lib/country.ts, consolidated src/lib/format.ts with formatMoneyCompact/Short/Full + formatDate.

New tests (+53)

SuiteTestsCoverage
tests/lib/db-retry.test.ts13retryableDB proxy: prepare/bind/all terminal methods, transient retry, fatal propagation, batch()
tests/middleware/auth.test.ts10401 fail-closed, JWT happy path, service-token passthrough, JWT-but-no-row, malformed JWT, local-dev fallback, retry-wrapped DB usage
tests/services/mfn-queries.test.ts10loadMfnData, calculateMfnFromData, loadAndCalculateAllMfn, cache rehydration to Maps
tests/services/clause-edit.test.ts6createNewClauseVersion happy path, assignment copy with notes, history chain, race-on-race guard, FTS update
tests/services/clause-fork.test.ts5forkClauseForLp: parent_id link, assignment move, MFN election migration, ops_tags copy
tests/services/ingest.test.ts9sha256Hex, findDuplicate, oversize/traversal/non-docx rejection, R2+D1+queue+activity_log happy path, dedup short-circuit

UX rollouts (earlier today)

Documentation

Out of scope (deliberately deferred)

Ctrl+K to open · ↑↓ navigate · Enter go · Esc close
Copied