Recent changes
Material refactors and behaviour changes shipped tohugo.nordiclawfirm.com.
2026-04-13 — Sponsor-level export templates + sponsor details
- Sponsor-level master templates (migration
0063):sponsorsgainedexport_template_{isl,ef,msl}_r2_keycolumns 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
generateSideLetterfallback. Implemented byresolveFundTemplateKey(db, fundId, kind)insrc/services/sponsor-export-templates.ts. No file copying — columns just point at R2 keys. - Sponsor details (migration
0065):sponsorsgainedfirm_name,firm_address, andfirm_countrycolumns 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.
- −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 --noEmitclean across the tree, includingtests/**andscripts/**for the first time- CI workflow gates every push: typecheck → vitest → discovery:check
Security & correctness (P0)
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.
- 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 listingtmp/imports/. Aligned totmp/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
Authorizationheader 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-down2217 → 1275 LOC (−42%). Extracted three large handlers into services:
src/services/chat-router.ts—/api/chatsrc/services/clause-search.ts—/api/clauses/searchsrc/services/lp-profile.ts—/api/lp-profile, migrated from the deletedcomparable-filter-legacy.tsto the sharedparseComparableFilterFromQueryinservices/comparables.ts
src/components/Layout.tsx split2211 → 744 LOC (−66%). Extracted four focused files:
Sidebar.tsx— nav data, group rendering, kbd badgesSearchModal.tsx— global search modalHelpModal.tsx— keyboard-shortcut helpboot-scripts.ts— dark-mode init, htmx error interceptor, helper blobs
src/pages/lp-tasks.tsx split3348 → 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.
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.
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,FundLpacContentdb/commitments.ts,db/clause-tags.ts,db/closings.ts,db/lpac.tslib/isl-preview-script.ts
src/lib/r2-keys.ts—documentsKey,exportsKey,exportTemplateKey,tempImportKey,testFileKey+R2_PREFIXESconstant. 7 writer sites rerouted.src/lib/cache-headers.ts—setPrivateCache(c)/setNoStore(c), always pairingCache-ControlwithVary: Cookie, HX-Request, Accept-Encoding. Fixed a cross-user cache leak risk oninvestor-detail(had noVary).src/lib/api-responses.ts,src/lib/country.ts, consolidatedsrc/lib/format.tswithformatMoneyCompact/Short/Full+formatDate.
New tests (+53)
| Suite | Tests | Coverage |
|---|---|---|
tests/lib/db-retry.test.ts | 13 | retryableDB proxy: prepare/bind/all terminal methods, transient retry, fatal propagation, batch() |
tests/middleware/auth.test.ts | 10 | 401 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.ts | 10 | loadMfnData, calculateMfnFromData, loadAndCalculateAllMfn, cache rehydration to Maps |
tests/services/clause-edit.test.ts | 6 | createNewClauseVersion happy path, assignment copy with notes, history chain, race-on-race guard, FTS update |
tests/services/clause-fork.test.ts | 5 | forkClauseForLp: parent_id link, assignment move, MFN election migration, ops_tags copy |
tests/services/ingest.test.ts | 9 | sha256Hex, findDuplicate, oversize/traversal/non-docx rejection, R2+D1+queue+activity_log happy path, dedup short-circuit |
UX rollouts (earlier today)
- Clause Precedent remodel: replaced
/statisticsdashboard and Negotiation Brief with/intelligence— clause picker + multi-state UI (empty / variants / no-match) + "widen the scope" suggestions. Built on the shared comparable-filter engine. - Multi-country filter with full country names via
<details>checkbox dropdown. Same component used on/intelligenceand/investors. FullBleedShellrollout to 16 pages: Configuration, Admin, Pro forma builder, Materially equivalent clauses, MFN settings, MFN scenario, Operations, Activity, Categories, Clause names, Ops tags, Definitions, Fund closings, LPAC, Fund export settings, Export. Every adopting page now has the same fixed header strip + scrollable body pattern.- MFN scenario added back to the fund sidebar under Pro forma builder (had been dropped during an earlier nav reorg; only reachable via
g s).
Documentation
- New project
CLAUDE.mdwith stack overview, file layout, conventions, cardinal rules, and the local dev loop. - New
README.mdfocused on getting started; the original 2020–2024 historical retrospective moved todocs/history.md. - 16 stale planning
.mdfiles at repo root archived underdocs/archive/plans-2026-04/. - 1.1 MB
sidebar-test.webmbinary deleted from git;*.webmadded to.gitignore. - Audit reports + plan + reviews live at
docs/audit/.
Out of scope (deliberately deferred)
- Biome / ESLint / Prettier / pre-commit hooks. All code is LLM-written and already gets
tscbefore suggestions; lint/format would be ceremony. - Self-hosting Tailwind / HTMX. Iteration speed beats production-mode purity for this stage. The Tailwind CDN script tag stays.