PR: #598: feat(brief): governing law onto EditableList [Story-2359] by @ankitmaheshwari-com
View PR →

Story-2359: Governing law becomes editable

Governing law is the list of acts and statutes a matter actually runs on — the Arbitration and Conciliation Act, a state stamp act, a contract's chosen law. It sits in the Brief's Case particulars rail, and until this change it was a wall: Vega read it out of the documents, joined it with ·, and printed it. If the framer misread a statute, or missed one, counsel had no way to say so. The most load-bearing legal fact on the page was the one nobody could touch.

This Story is pure reuse. Story-2355 built EditableList and put Key instruments on it; Story-2360 put three more lists on it. Governing law is the fifth consumer. No new primitive, no new editor, no new data path, no pnpm codegen, no backend change.

The gate that had to pass first

A field is only worth making editable if the write path will actually accept it. The frame edit endpoint (PATCH …/frame) declares fields as a free-form object in OpenAPI — additionalProperties: true — so the OpenAPI mirror alone cannot answer the question. The real allowlist is enforced server-side, in pramaan-functions:

src/domains/matter_frame/frame_contract.py

_META_FIELDS = frozenset({"frame_status", "missing_core_fields"})
EDITABLE_FRAME_FIELDS = frozenset(FrameMirror.model_fields) - _META_FIELDS

Every FrameOutput field except Vega's two honesty-gate meta fields is editable. governing_law is neither of those, and it carries exactly the shape key_instruments already proved:

WhereWhat it says
src/domains/matter_frame/dtos.py:660governing_law: list[str] = Field(default_factory=list)
schemas/frame_output.v1.schema.json"governing_law": { "items": { "type": "string" }, … } — array<string>
schemas/frame_output.v1.fields.jsonlisted in the flat field vocabulary
frame_contract.pyNOT in _META_FIELDS ⇒ in EDITABLE_FRAME_FIELDS
frame_contract.pyNOT a canonical-overlap field (FORUM_OVERLAP_FIELDS = tribunal_name, forum_type; PARTY_OVERLAP_FIELD = parties)
GATE: PASS — no pramaan-functions change needed. The edit path validates the merged frame against the same FrameOutput model (_validated_frame → FrameMirror.model_validate), so a string[] body for governing_law is accepted exactly as key_instruments is today. Unlike relief_sought (Story-2360), there is not even a canonical-overlap nuance to reason around: governing law is frame-native, with no competing canonical column.

What changed on the page

BeforeAfter
Markup RailListFact — one line, items joined with " · ", one provenance glyph for the whole line EditableList — one row per act, each row its own editable surface with its own provenance marker
Empty state the word "Not set", and no way past it the "Add an act or statute" button IS the empty state
Write none onSaveList("governing_law") → the page's existing saveList → useAppEditMatterFrame

Counsel clicks an act, the caret drops in, blur commits. Clearing an act's text removes the row — the row IS the value, so an empty row is no row. "Add an act or statute" opens a fresh row. Every commit sends the whole next array, because the frame edit contract is full-value-per-field.

What was inherited, not rebuilt

The dead code that went with it

RailListFact was the read-only joined-line renderer, and governing_law was its only caller. Promoting the field orphaned it, so it is deleted in the same change rather than left to rot beside its replacement (AGENTS.md, "Repo hygiene — no orphan files"). Net: the rail gained an editor and lost a component.

Visual density preserved

The rail is dense on purpose — it is a reference card, not a form. The promoted block keeps the rail's shape: the same FieldLabel eyebrow, the same mb-4 block rhythm, the same Divider above and below, and the same EditableList density Key instruments established two rows down. The only visual delta is deliberate and is the point of the Story: acts stack as rows instead of running together on one ·-joined line, which is what makes each one individually correctable.

Storybook

No new story. Governing law exercises states the Shared/EditableList catalog already demonstrates — Populated, Empty/add-first, Add flow, Remove, Provenance-marked, Rollback on error. No genuinely new state emerged, so no new catalog entry was earned.

Verification

CheckResult
pnpm lint0 errors (3 pre-existing warnings, unrelated files)
pnpm test189 files, 1237 tests, all passing
pnpm buildclean
git diff --checkclean
Staging seam, named honestly. The gate above is a contract proof — read from the authoritative FrameOutput model and the server-side edit validator in pramaan-functions — not a live round-trip. No live staging write of governing_law was performed from this branch, and a green build/test is not deployed-runtime proof (AGENTS.md LI-7). Staging UAT — add, edit, remove an act, plus the empty first-run state on an unframed matter — runs post-merge on stg.app.pramaan.io.

Files changed

4 file(s), +21 / -33 lines

Path Role + -
messages/en.jsonConfig+2-0
messages/hi.jsonConfig+2-0
src/components/matter-detail/brief/matter-brief-page.tsxComponent+1-1
src/components/matter-detail/brief/matter-brief-sections.tsxComponent+16-32