6b52469b | fix(I1): stop offering workspace links that cannot work | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body getLinkForMatter collapsed the "unsupported" sentinel to null, so "this
matter has no workspace yet" and "workspace linking does not exist on this
deployment yet" were indistinguishable by the time they reached the UI. The
detail page therefore drew a "Start workspace" button, and the Matters page a
"Link to a Clio matter" row action, that could only ever answer 409 - the
`matter_workspace_links` migration (20260807_01) is not in the repo at all, so
that is the CURRENT state everywhere, not a hypothetical.
- getLinkForMatter now returns the sentinel; the detail route turns it into an
explicit `linksUnavailable` flag (and still nulls `link`).
- New `areLinksAvailable(db)` probe, fails OPEN on a transient error (the
27/07 rule: availability gate, not a destructive op) - the write path's own
fixed refusal stays the honest backstop.
- The flag rides the LIST payload too: both remaining link affordances live on
surfaces that never load a matter detail.
- Frontend hides both affordances behind it with honest copy ("Workspace
linking isn't available yet."); the picker also refuses rather than letting
a solicitor choose a matter and only then be told.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
a46ca14f | fix(I2): exempt /clio-matters from the IP-keyed general limiter | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body The per-user bucket inside the /clio-matters router was decorative: the
app-level generalLimiter still ran in front of it, keyed by IP, at a shared
300 requests per 15 minutes. The pilot firm NATs its whole office through one
address, so that IP bucket - not the per-user one - was the binding constraint
the moment two solicitors browsed matters, and exhausting it would degrade
every other route for their colleagues.
Exemption is a skip on the prefix (kept where generalLimiter is mounted, so the
JSON body parser and everything after it still apply - mount-order would have
skipped those too). Exact-segment match, not a bare startsWith, so a future
`/clio-matters-anything` cannot inherit the exemption.
Corrects the rationale comments, which claimed matters browsing could exhaust
the "shared research allowance" and take /companies and /legislation down:
/clio-matters was never on researchLimiter - it was on generalLimiter, so the
stated mechanism was wrong even before this change. Fixed in
routes/clioMatters.ts, index.ts, .env.example and the CLAUDE.md registry row.
index.ts retains its pre-existing prettier deviation at makeLimiter (the file
was not prettier-clean at baseline); only the hunks added here are normalised.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
a0c89e55 | fix(I3): carry isOwner on the workspace link so Unlink is not offered-then-refused | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body Unlinking is owner-only and the server enforces it with a 403, but the link
payload carried no ownership signal - so a colleague reading a firm-visible
matter saw an Unlink button and only learned it was not theirs by pressing it.
toLink already had the authorising `checkProjectAccess` result in hand, so
isOwner is derived from that same result and cannot disagree with what the
server will enforce. The two write paths (linkWorkspace, createWorkspaceForMatter)
pass true by construction - both refuse or create for the caller as owner.
The frontend withholds the button rather than disabling it; the handler keeps
its 403 branch as the belt for a payload that went stale under an open page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
d17de50e | chore(i/M13): scrub a real client name from the public repo | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body "Kyckr" is a real client of the pilot firm and this repository is public. It
appeared 11 times in manageTools.test.ts (as search-query fixtures) and once in
BUILD_LOG's 04/08 entry, where it recorded which client a solicitor was
searching for - a confidentiality leak, not a naming nit.
Test fixtures now use the fictitious "Aldergate". The BUILD_LOG line is redacted
IN PLACE as "[client name redacted 07/08]" rather than rewritten, so the entry
stays honest about what it originally said.
Stated plainly: this scrubs the working tree only. The name remains in git
history (commits predating this one) and would need a history rewrite to remove
- out of scope for a fix wave, flagged to the owner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2bfa0b57 | fix(a/M11): route the limiter's IP fallback through ipKeyGenerator | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body The custom keyGenerator returned req.ip directly, which express-rate-limit
rejects as ERR_ERL_KEY_GEN_IPV6 - confirmed firing in the route suite, not
theoretical. A raw IPv6 address gives every host in a /64 its own bucket, and a
single client can hold trillions of those, so the fallback limit was bypassable.
Kept the fallback rather than deleting it (it is unreachable today - requireAuth
is mounted ahead of the limiter and 401s first - but "unreachable" is a property
of the current mount order, not a guarantee), and recorded that reachability
argument in the comment. Keys are now namespaced user:/ip: so a user id can
never collide with an address literal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
d7091d34 | refactor(c/M12): one UUID_RE, and put the numeric-id docblock on isClioId | sloth-ninja | 2026-08-07 | ↗ GitHub |
commit body Two defects in the same few lines. The docblock describing numeric Clio ids sat
above UUID_RE, which is neither numeric nor Clio's - the function it documents
(isClioId) had none, so the comment described the wrong constant.
UUID_RE also existed twice, once in the seam and once in the routes, guarding
the same values. Duplicated validation regexes drift, and the copy that drifts
is the one that stops guarding. The seam now exports it and the route imports it.
No behaviour change; the two suites over these files stay green (90 tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
300ee296 | fix(M5): never guess a currency symbol on a money figure | sloth-ninja | 2026-08-08 | ↗ GitHub |
commit body formatMoney defaulted a missing currency code to GBP, so an amount Clio
reported without a currency rendered as "£1,240.00" - a specific claim about a
figure on a legal bill that nothing supported, and simply wrong on a matter
billed in euros.
An absent or unrecognised code now renders the number bare (still 2dp, so
columns line up), never dressed in a symbol. Same honesty rule the redaction
flags already encode: a value we cannot substantiate is not presented as one we
can. Null stays an em dash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
a86e6867 | fix(f/M4,M1,M7): wire the link lookup; stop blaming Clio for our 429s; treat 404 as gone | sloth-ninja | 2026-08-08 | ↗ GitHub |
commit body Three reviewer items that land in the same files.
(f/M4) GET /clio-matters/links/:projectId now exists, using the getLinkForProject
export that was dead code. Registered in the literal-path block ahead of the
/:matterId routes - declared after them, "links" would be swallowed as a matter
id (asserted by a test). Uuid-guarded in the route as well as the seam, and one
uniform 404 for every kind of "no" (absent / not visible / malformed /
unmigrated) so the endpoint cannot be used to probe which workspaces exist. The
Workspaces-tab badge that would consume it stays deferred.
(M1) A 429 can come from our own per-user bucket, but the server called it "Too
many Clio requests" and the client overwrote it with "Clio is rate-limiting
requests" - both blaming Clio for a limit JessicaOS imposed. The server detail
is now neutral, and the surfaces show the server's own message on 429 with a
neutral fallback, so a real Clio 429 and ours stay distinguishable.
(M7) A 404 on save or delete means the entry is already gone from Clio (deleted
in another window), which makes the list on screen stale - so 404 joins 409/412
in offering "Reload time entries", and a 404 delete drops the row rather than
leaving a ghost the solicitor can retry forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
57d58714 | docs(j/M10) + fix(M8): document the clioState gates; stop the initial-tab flash | sloth-ninja | 2026-08-08 | ↗ GitHub |
commit body (j/M10) The three clioState gates are deliberately asymmetric - fail-closed for
the link WRITE affordance, fail-open for the READ tabs, and narrowest of all for
the banner, which makes a positive claim about the user's account. That was
three scattered half-comments that read like inconsistency. Kept as they are
(the asymmetry is correct per the 27/07 rule) and explained in one block naming
"unknown" as the value that separates them.
(M8) With no stored preference the page resolved to Workspaces while the Clio
status was still loading, painted that list, then jumped to My matters the
moment the status landed. The tab decision now waits for a definite status, the
default matches the tab bar's existing optimism instead of contradicting it, and
the body renders a skeleton until then - so no wrong list is ever painted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
005ea7e5 | docs(M3): commit the Practice Management spec and state per-viewer workspace semantics | sloth-ninja | 2026-08-08 | ↗ GitHub |
commit body Two things here, and the first was not in the brief.
The spec was NEVER COMMITTED. CLAUDE.md, BUILD_LOG and three source files all
cite docs/PRACTICE_MANAGEMENT_SPEC.md, and every sibling spec (FIRM_LIBRARY,
TABULAR_TEMPLATES, DELETION_GOVERNANCE) is tracked - this one existed only as an
untracked file in the owner's checkout, so the whole train's design document was
one `git clean` from gone and invisible to reviewers. Committed as-is apart from
the sentence below.
(M3) Surfaces section 4 now states the per-viewer semantics explicitly: the
workspace a solicitor sees against a Clio matter is one they can access, so a
colleague's appears only when firm-visible. Two people on one matter seeing
different workspaces (or one and none) is correct, not a sync bug - worth
writing down before someone "fixes" it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3cdc1eab | docs: BUILD_LOG entry for the Practice Management fix wave | sloth-ninja | 2026-08-08 | ↗ GitHub |
commit body Scope, per-item changes, verification evidence, the contention note, and the
items flagged to the owner (missing 20260807_01 migration; "Kyckr" surviving in
git history; the four reviewer-deferred items).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
9c6cd9f0 | Address review B1: do not respell the scrubbed client name in the scrub's own record | sloth-ninja | 2026-08-08 | ↗ GitHub |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|