Sloth-ninja puts Clio matters at the centre of JessicaOSS

The fork is building a working matters view around live practice-management data, including controlled time-entry edits.

integrationworkflow

Instead of importing a second copy of a firm's matters, this work reads them from each user's connected Clio account. Clio is practice-management software used by many law firms.

  • Personal and firm-wide matter views, with matter details, related contacts and activity history.
  • In-app time-entry changes and deletions, while billed or unauthorised entries stay locked.
  • Matter-to-workspace links, so an owner can create or connect a workspace around a Clio matter. This remains unavailable until the supporting database change lands.
  • Financial figures that Clio has redacted remain hidden, rather than appearing as zero.
So what Firms already running Clio should watch this fork if they want matter workspaces without another stale matter database.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

2 commits from Sloth-ninja/JessicaOSS, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
7945ad49 Practice Management backend: Clio-backed Matters seam + routes sloth-ninja 2026-08-07 ↗ GitHub
commit body
Task 2 of the Practice Management plan. Live Clio reads only; the sole
stored artefact is the matter_workspace_links id/number pair, whose
migration (20260807_01) is Task 1 and is NOT in this PR - every links
query degrades on 42P01/42703/PGRST204 so this runs safely ahead of it.

- lib/clio/mattersSurface.ts: list (mine = responsible union originating,
  de-duped, sorted open_date desc; all = one order=open_date(desc) page),
  detail + financials, related contacts, time entries, activity update
  (If-Match) / delete, workspace link lifecycle. 60s per-user list cache
  keyed user+tab+query+status, 200-entry cap, cleared on activity write.
- routes/clioMatters.ts mounted at /clio-matters behind the research
  limiter; requireAuth + asyncHandler throughout, fixed client details +
  safeErrorLog, uuid/numeric id guards. Minutes to seconds happens at
  this boundary once.
- clioRequest gains an additive headers option for If-Match; caller
  headers sit beneath the fixed ones so Authorization / Accept /
  X-API-VERSION can never be overridden.
- Selector fixes (docs-verified 06/08 spike, live probe pending):
  clio_matter_financials asks billable_matters for its own fields
  (the shipped matter{...} brace 400'd every call, owner-reproduced);
  clio_find_contact treats primary_email_address as a scalar.
- Lifecycle: account deletion and SAR export cover the links table,
  42P01-tolerant.

Probe-gated per the spec's open questions: one 200-row page with honest
counts, billed entries locked AND refused server-side with a fixed 409.

Every read uses the caller's own Clio token; link lookups go through
checkProjectAccess, the choke point that also excludes tombstoned
matters. Redacted money/hours report as hidden, never as zero.

Verified: tsc clean; full vitest 836 passed / 48 files (baseline 757,
79 new); prettier clean on all new files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
20288bac Address review: PGRST205 degrade, per-user rate limit, header hardening sloth-ninja 2026-08-07 ↗ GitHub
commit body
Five should-fixes and six nits from the independent review of #81.

PGRST205 was a real defect. A missing TABLE on Supabase is PostgREST's
PGRST205 (404, answered from its schema cache), never Postgres's 42P01 -
verified empirically against PostgREST 14.16 in a local container (no
credentials involved): missing table -> PGRST205, missing column in a
filter -> 42703, missing column in a write payload -> PGRST204. The
42P01-only degrade would have thrown in production, and would have failed
account deletion / SAR export outright on an unmigrated database. The
code table now lives in a dependency-free leaf module lib/postgrestCodes.ts.

- /clio-matters gets its own limiter keyed on res.locals.userId (IP only
  as the pre-auth fallback), inside the router because an app-level
  limiter runs before auth. The pilot firm NATs one office IP, so the
  shared IP-keyed research bucket would have let a few solicitors take
  /companies and /legislation down for everyone. New vars
  RATE_LIMIT_CLIO_MATTERS_MAX / _WINDOW_MINUTES, documented in
  .env.example and CLAUDE.md. Auth moves to router level so no future
  route can omit it.
- headersFor drops any caller header colliding case-insensitively with a
  fixed one: object spread is case-sensitive and new Headers() then joins
  case-differing duplicates into "Bearer attacker, Bearer real".
- linkWorkspace gains the matter-side pre-check; link candidates are
  ordered by created_at with the caller's own preferred; the workspace
  side of the conflict gets its own 409 copy.
- Nits: cross-user cache isolation test, etag input guard, created_by
  predicate on the unlink delete, uuid guard on getLinkForProject,
  lifecycle fixtures corrected, amountsHidden renamed amountsUnavailable
  (a null price can mean a rate-less entry, so only quantityRedacted may
  drive "Hidden by your Clio permissions" copy).

The first attempt imported the predicate from the feature seam into
userDataCleanup, forming an import cycle through lib/access.ts that broke
an unrelated tombstone test in routes/tabular.test.ts; hence the leaf
module. Lifecycle paths must never import a feature seam.

Verified: tsc clean; full vitest 871 passed / 49 files (baseline 757);
prettier clean on all new and newly-touched files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-1037.md from inside the repo you want the changes in.

⬇ Download capture-thread-1037.md