Skip to content

feat(matters): people on a matter, and who wrote which thread - #128

Open
sergiomaldo wants to merge 2 commits into
LegalQuants:mainfrom
sergiomaldo:feat/matter-team-ui
Open

feat(matters): people on a matter, and who wrote which thread#128
sergiomaldo wants to merge 2 commits into
LegalQuants:mainfrom
sergiomaldo:feat/matter-team-ui

Conversation

@sergiomaldo

@sergiomaldo sergiomaldo commented Aug 21, 2026

Copy link
Copy Markdown

Frontend for matter membership. Backend counterpart: LegalQuants/lq-ai#536
(project_members + share_scope, migration 0067, and GET /api/v1/users/directory).

Two commits. The first is a standalone fix — GET /knowledge-bases?project_id= filters the
legacy knowledge_bases.project_id column, so junction-attached KBs never appear in a matter's
linked list; it reads them from attached_knowledge_base_ids instead. It is here because the
People work builds on the same +page.server.ts load path and does not apply without it. Happy to
split it into its own PR if you'd prefer to review them separately.

Safe to merge ahead of the backend. The People section is gated on the roster fetch succeeding,
every new matter field is optional, and the directory round-trip is skipped for anyone who cannot
staff the matter. Against an API that predates all of this, the page renders exactly as it does
today — there's a test for that.

What it adds

A People section on the matter page: the roster, a role picker per person, a people-picker for
adding one, and the matter's share scope. A lead manages all of it; everyone else sees it read-only.

Screened people are listed apart from the working team, under their own heading, with a "Lift
screen" control. They aren't members with a lesser role — they're the record of an ethical wall, and
folding them into the roster makes the wall easy to miss. The UI calls them Screened throughout;
blocked is only ever the wire value.

Chats in a shared matter now carry their author's name. An unattributed thread is exactly what
privilege work cannot afford, and once several people work one matter, "who ran this" stops being
obvious from context. Names resolve from the roster and fall back to the email.

The matter list gains a chip distinguishing a matter somebody put you on ("Shared") from one
nobody had to ("Firm-wide") — different facts, and a lawyer will want to tell them apart. Keyed on
caller_access_basis rather than owner_id, so it stays right for a lead who isn't the owner.

Archive is hidden for non-leads, matching the backend, rather than offering a button that 403s.

Notes for review

  • The new types live in $lib/matters/types rather than backend.d.ts, since those are generated
    from the pinned vendor/lq-ai OpenAPI sketch; they fold in at the next pin bump.
  • activeMatters is now generic so callers reading the sharing fields don't lose them to a widening
    return type.
  • The load path's positional mocks in page.server.test.ts now document the fetch order at the
    top of the file. Adding one request to load silently shifts every mock below it — which is how
    three pre-existing KB tests broke while this was being written.

Docs

  • docs/GUIDE.md — a practitioner-facing "People on a matter" section: the four roles, that
    firm-wide means reading, that a screen overrides administrator rights, and the one thing you
    can't do (post into a colleague's thread) with the reason.
  • README.md — "Adding your colleagues", placed where an installer hits it after first login:
    creating users, the one-time password, roles, and the share-scope default.

Testing

npm run test:unit — 1655 passed / 293 files. npm run lint and svelte-check clean. New:
TeamSection.svelte.test.ts, SharedChip.svelte.test.ts, and roster/attribution cases in
matters/[id]/page.server.test.ts — including one asserting the page still renders against an API
without the membership endpoints.

Also verified by hand against a running deployment: three real logins, a matter shared and then a
colleague screened off it through the UI, with the screen confirmed to deny at the API.


🤖 Generated with Claude Code

sergiomaldo and others added 2 commits August 21, 2026 10:23
…he legacy project_id filter

GET /api/v1/knowledge-bases?project_id= filters on the legacy
knowledge_bases.project_id column, while attach/detach (Wave D.1 T3) write
the project_knowledge_bases junction table — so a freshly linked KB never
appeared in the matter's Knowledge section. Resolve linked KBs from the
project's attached_knowledge_base_ids against the full KB list instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend counterpart: matter membership + `share_scope` over `projects`
(`project_members`, migration 0067) and `GET /api/v1/users/directory`.

Adds a People section to the matter page: the roster, a role picker per
person, a people-picker for adding one, and the matter's share scope. A
lead manages all of it; everyone else sees it read-only.

Screened people (`role='blocked'`) are listed **apart** from the working
team, under their own heading, with a "Lift screen" control. They are not
members with a lesser role — they are the record of an ethical wall, and
mixing them into the roster makes the wall easy to miss. The UI calls
them *Screened* throughout; `blocked` is only ever the wire value.

Chats in a shared matter now carry their author's name. An unattributed
thread is exactly what privilege work cannot afford, and once three
people work one matter, "who ran this" stops being obvious from context.
Names resolve from the roster and fall back to the email.

The matter list gains a chip distinguishing a matter somebody put you on
("Shared") from one nobody had to ("Firm-wide") — different facts, and a
lawyer will want to tell them apart. Keyed on `caller_access_basis`
rather than `owner_id`, so it stays right for a lead who is not the
owner.

Archive is hidden for non-leads, matching the backend, rather than
offering a button that 403s.

Degrades cleanly against an API that predates all of this: the People
section is gated on the roster fetch succeeding, every new matter field
is optional, and the directory round-trip is skipped for anyone who
cannot staff the matter anyway. `activeMatters` is now generic so callers
reading the sharing fields do not lose them to a widening return type.

The new types are declared in `$lib/matters/types` rather than read from
`backend.d.ts`, since those are generated from the pinned `vendor/lq-ai`
sketch; they fold in at the next pin bump.

Test note: the `load` path's positional mocks in `page.server.test.ts`
now document the fetch order at the top of the file, because adding one
request to `load` silently shifts every mock below it — which is how the
three pre-existing KB tests broke while this was being written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant