Document Data Panel — Design
In review

Document Data Panel — Design

2026-05-20 · reworked 2026-05-21 · MaxContracts, POs & GRNs · contracts deep firstIssue #390

Problem

The document data panel — the right-hand panel of the document detail page (PDF on the left, data panel on the right) — serves every document type on production's single Document Management list — contracts, purchase orders, and goods-received notes (invoices live on a separate page and are out of scope here). Two things undercut it:

  1. Contracts render the same sections regardless of sub-type. An NDA shows an empty Financial and Renewal block; a loan has nowhere to put a repayment schedule. To a human, those empty, ill-fitting boxes read as one thing: this system doesn't really understand contracts.
  2. Across types, the panel's treatment is uneven, and the types sit side-by-side in one list — so weak presentation on any one type drags down perceived competence on all of them.

The two functional jobs the contract panel once justified are already done:

And the agent itself never consumes the structured display — humans do. So the remaining value of this rework is not data the system needs; it is perception and trust.

Reframe

The panel's job is to demonstrate, for every document type a customer sees, that we read the document correctly with all its nuance. Contracts are the sharpest case — 11 wildly different sub-types — so they ship first and deepest. The same trust treatment then spans purchase orders and goods-received notes, so the whole Document Management list reads as competent rather than just the contracts in it.

Goals & non-goals

Goals

Non-goals (this rework)

Approach

The panel is a per-type section configuration rendered through shared components, with cross-cutting trust overlays (source anchors, coverage, callouts, validation badge) applied throughout. The governing principle is unchanged from the legal-analysis rework: prompts decide content; code decides shape, section selection, and trust rendering.

Per-type prompt buckets + guidance LLM JSON sections · fields + source pages · coverage Hybrid renderer per-type components + generic KV Renderer + trust overlays are shared across all document types.
Content flows from prompt to renderer; the renderer owns the skeleton and is reused per type.

The decisions that distinguish this rework from the original draft:

Decision · one unified panel for all types

Rather than a contracts-only panel, build a single document data-panel architecture and bring every type onto it. The 11-sub-type "type slot" stays contract-specific (POs and GRNs are single-shape), but the skeleton, the hybrid renderer, and all trust overlays are shared. A contract's payment schedule and a PO/GRN's line items are both just per-type components within the same skeleton. Contracts ship first and deepest; PO/GRN follow. (Invoices have their own separate page and are out of scope.)

Decision · hybrid renderer

Relax the original "pure generic key/value, minimal hardcoding" rule. Flat KV lists read like a data dump and undercut the nuance we are trying to convey. Keep one generic renderer for the long tail but allow a few hand-built per-type components where nuance matters most: the contract payment / repayment schedule, the PO/GRN line items, and the coverage view.

Decision · source anchoring is in scope, page-level

Per-fact source anchoring moves from "deferred" into scope, realized at page granularity: each fact carries a source page; clicking it navigates the PDF to that page. No in-scope document type stores per-fact page data today, so source pages come from extraction — contracts first, in Phase 2; in Phase 1 the chips render from fixtures only. Pixel-precise highlight is a separate project — see the phases table.

Open technical risk (spike before Phase 1 commits): the PDF panel has two iframes — #invoice-frame and #email-frame — toggled by a tab switcher; the document frame's src already carries a fragment (#view=FitH&pagemode=none). A page chip must target the correct (document) frame. Changing only the hash of an already-loaded native PDF viewer does not reliably re-navigate (Chrome/PDFium often ignores it; behaviour differs across Chrome/Firefox/Safari). The spike picks the technique — e.g. reassign the document frame's src with #page=N&view=FitH (mutating only the fragment, preserving the presigned query string) and force a reload, vs. adopting PDF.js. Its exit criterion is behavioural — the page visibly changes in a loaded viewer across our target browsers — not a unit test on the fragment string. Treat "no PDF.js required" as a hypothesis, not a settled fact.

Decision · reuse data, but the renderer pipeline is new

We reuse existing data where it exists — the ContractData schema, the formal-validation diagnostics, the compliance-checks the contract prompt already produces, the preselection metadata, and the hero/payment-schedule timeline. But the rendering architecture is greenfield: there is no generic key/value renderer, no :component dispatch, and no skeleton today (every section in contract.clj is bespoke Hiccup), and the EDN content model (:sections/:fields/:kind/:component/:coverage) is a new shape not present in ContractData. So Phase 1 is "render existing data through a new pipeline", not a re-skin. Two data caveats: contract risk-flags are in the schema and consumed by the UI but generated by no current code, so coverage and callout data is Phase 2 (Phase 1 renders them from fixtures); whereas compliance-checks is produced today and is real Phase-1 data to render.

Design

The shared skeleton

Every document type renders through the same skeleton; a per-type config decides which sections appear and in what order. Empty sections auto-hide. The trust overlays apply within any section.

  1. Summary / metadata — key facts at a glance. Whether a type gets a prominent hero band is decided per type in the design (contracts keep theirs; PO/GRN TBD during implementation).
  2. Type sections — the per-type body. Contracts use the type-adaptive slot (below); PO/GRN use their P2P sections (line items + references).
  3. Validation & Compliance — the existing formal validation surfaced as a prominent trust badge, plus the :legal slice from #389 for contracts when available.
  4. Coverage — the found / not-found checklist for the type (a panel-level overlay component).
  5. Matches — linked documents (POs / invoices / GRNs).
  6. History / versions.

The contract spine (deep, first delivery)

Contracts render this locked order. The existing section renderers are partly reused, but the order and grouping change materially from today: the current panel is Hero → Obligations → Financial → Validation → Renewal&Termination → Parties → Scope → Legal, with no standalone Term section and Parties below Financial. This rework promotes Parties, splits a dedicated Term & Renewal section, replaces Financial+Scope with the type-adaptive middle, and moves Validation down. Two pieces of coupled work the reorder pulls in:

Treat the reorder as real work, not a no-op.

  1. Summary / metadata hero (exists) — counterparty, dates, total value, renewal.
  2. Parties (exists) — principal / counterparty.
  3. Term & Renewal (new section) — effective / expiration, renewal type, notice period, cancel-by. Pulls together fields that today live split across the hero (effective/expiration) and the existing "Renewal & Termination" renderer; the section itself is new.
  4. ⟳ Type-adaptive middle — 1–3 type-specific sections (below). Replaces today's one-size-fits-all Financial + Scope.
  5. Validation & Compliance — formal validation badge. The :legal slice (#389) is not built yet, so in Phase 1 this section renders the validation badge only; the :legal area stays absent until #389 lands.
  6. Obligations (exists) — key obligations.
  7. Legal (exists) — governing law, jurisdiction, liability cap, confidentiality.
  8. Matches — linked POs / invoices. Moves from today's after-hero injection (extra-after-hero in type-specific-view) to this dedicated late section.
  9. History / versions.

The contract type-adaptive middle — fixed buckets, LLM-decided content

TypeSections (buckets)
subscriptionSubscription & Billing · Service Levels · Price Escalation & Changes
loanLoan Terms · Repayment Schedule · Collateral & Covenants
leaseLeased Asset · Lease Terms & Payments · Return & Maintenance
rentalRented Object · Rental Terms & Payments · Maintenance & Return
insuranceCoverage · Premium & Payment · Exclusions & Deductibles
serviceScope & Deliverables · Service Levels · Pricing & Penalties
supplySupply Scope & Items · Pricing & Delivery · Quality & Penalties
purchasePurchase Terms · Warranty & Acceptance
frameworkFramework Scope & Ceiling · Call-off Terms
ndaConfidentiality Scope · Breach & Penalties
otherKey Terms (generic catch-all)
Section titles/order are configuration; rename/add/drop is a config change, not new frontend code.

Other document types on the unified panel

Content model & the JSON / EDN contract

The type-slot output per document. Three additions over the original draft: per-field :source (page), a :callout field kind for interpretation, and a panel-level :coverage checklist. The same field model is reused by other types' sections.

edn — fixture for one contract{:type "subscription"
 :sections
 [{:id "billing" :title "Subscription & Billing"
   :fields [{:label "Recurring fee" :value 2400 :kind :currency :source 2}
            {:label "Billing cycle" :value "Annual" :source 2}
            {:kind :callout :severity :warning :source 5
             :value "Auto-renews for 12 months unless cancelled 90 days prior."}]}
  {:id "payment-plan" :title "Payment Schedule"
   :component :payment-schedule          ; per-type component, not generic KV
   :rows [["2026-01-01" 2400] ["2027-01-01" 2520]]
   :source 6}]
 :coverage
 [{:term "Liability cap"   :found? false}
  {:term "Confidentiality" :found? true :source 7}
  {:term "Price escalation" :found? true :source 4}]}

Scope of this model (Phase 1). The EDN above describes the type-adaptive middle plus the panel-level :coverage — not the whole panel. The spine sections (Parties, Term & Renewal, Validation, Obligations, Legal) stay hand-built from structured-data in Phase 1; generalizing them onto this model is an open question. So a fixture carries the adaptive middle + coverage, not Parties/Legal. :source is valid at field level and at section level (e.g. on a :component section like the payment schedule); it is not used on spine sections in Phase 1.

Trust mechanisms (cross-cutting, all types)

Coverage view. For each document type, a curated expected-terms checklist — a new, authored deliverable, one list per contract sub-type and per other type (see Open questions) — is rendered with each item marked found or not found. Absence becomes visible thoroughness rather than an empty box. The data behind found/not-found is the contract risk-flags / extraction output, which is produced in Phase 2 (not generated today); Phase 1 renders the view from fixtures. Phase 1 must also remove the existing contract-risk-signals-box (in contract-validation-section): because risk-flags are never produced, it currently renders all 8 risk types as a green "pass" — a false all-clear that is the opposite of the trust we want. The new coverage view supersedes it. (compliance-checks is real data that renders today — but note #389 plans to re-home it under the :legal slice, so the Phase-1 compliance renderer is interim, not permanent.)

Interpretation callouts. Short plain-language notes on nuanced or risky terms (an auto-renewal trap, a Vertragsstrafe, an unusual termination condition), via the :callout field kind. Same data dependency as coverage: rendered in Phase 1 from fixtures, populated in Phase 2 once contract risk-flags are generated.

Source anchoring (page-level). Each fact shows a small page chip; clicking it navigates the PDF to that page (technique pending the spike in Approach). No in-scope type stores per-fact page data today, so contracts get the first real source pages once extraction emits them (Phase 2); Phase 1 renders chips from fixtures. No stored coordinates required.

Validation made prominent. Surface the already-built formal validation as a visible trust badge at the top of Validation & Compliance, not a buried sub-section. Data source: the 6 checks are computed in validation.clj but read by the panel from the diagnostics model (:document/diagnostics → :validations) and rendered by contract-validation-section; we reuse that path.

Warning · don't over-flag

Callouts and not-found markers are powerful precisely because they are rare. Flag the genuinely notable; a panel that warns about everything trains the reader to ignore all of it.

Penalties & cross-cutting content (contracts)

Penalties / fines (e.g. a Vertragsstrafe) are cross-cutting and must never be lost:

  1. Dedicated home where prominent — service/supply ("…& Penalties"), NDA ("Breach & Penalties"), subscription (service credits in Service Levels), loan (default terms in Repayment / Covenants).
  2. Safety net everywhere else — because content is LLM-decided and guidance is non-exhaustive, a defined fine returns as a :callout (or KV pair) in the closest section even where no dedicated bucket exists.

Prompt & extraction changes (backend)

Frontend renderer (Hiccup)

Build phases

PhaseScopeTouches extraction?
2 — Contracts, backend extractionPer-type prompts producing the JSON; generate contract risk-flags → coverage + callouts; teach the contract prompt to use the === PAGE N === markers and emit a per-fact :source page (with golden-doc accuracy validation); schema validation; pipeline wiring.Yes
3 — PO & GRN onto the unified panelPurchase orders and goods-received notes get the skeleton + trust overlays + their P2P sections (incl. a :line-items component). No existing per-fact page data, so source pages come from their extraction; summary-band decided per type. (Invoices are out of scope — separate page.)Some
Future (out of scope) — precise highlightText-quote / bounding-box highlight in the PDF. Requires persisting Document AI geometry + coordinate mapping + a PDF.js (or page-image + SVG overlay) rewrite.Yes (heavy)
Recommended starting scope is Phase 1; precise highlight is explicitly deferred.
Decision · first implementation plan = Phase 1 only

The unified all-types architecture is a design constraint, not first-delivery surface. The first writing-plans output covers Phase 1 (contracts, renderer-first against fixtures) only; Phases 2 and 3 get their own plans (and may warrant their own specs). This keeps the first plan tractable.

Testing

Open questions

References