Skip to content

Matrix report: show what a relation means, and filter by it - #885

Merged
vincentmakes merged 5 commits into
mainfrom
claude/matrix-report-improvements-de9tt7
Jul 28, 2026
Merged

Matrix report: show what a relation means, and filter by it#885
vincentmakes merged 5 commits into
mainfrom
claude/matrix-report-improvements-de9tt7

Conversation

@vincentmakes

@vincentmakes vincentmakes commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Implements discussion #883: the Matrix report can now show a relation's values in the cell and be filtered by relation type, direction and attribute value, instead of only saying that a link exists. Along the way it fixes three bugs that made relation data invisible — boolean relation attributes could never be set, relations attached to a card that has children never reached a cell, and several relations between the same pair counted as one.

Type

  • feature
  • fix
  • refactor
  • docs

Changes

Everything the grid shows, filters and exports is derived from each relation type's attributes_schema at runtime. No attribute key appears anywhere in the code, so a dimension an admin adds gets a glyph, colour, legend entry, filter and export column with no code change. flowDirection gets no special case either — it is a select like any other, and structural direction (which end of the relation the row card sits on) is carried per edge instead.

Backend — app/api/v1/reports.py

  • Each intersection carries its edges: [relationTypeIndex, orientation, attributeSetIndex] per underlying relation. Distinct attribute bags are interned and referenced by index, so the payload grows with the variety of the values rather than the volume of the relations (22 demo relations → 8 bags).
  • New relation_types, attr and direction query params, pushed into SQL as JSONB predicates. Only edges are filtered — the card lists always ship complete, because pruning cards server-side would break the parent_id chains the client rebuilds the hierarchy from.
  • Column-level SELECT and a join on the endpoint card types replace two IN lists that grew with the landscape.
  • Unknown card type → 400 rather than a silently empty grid. MATRIX_MAX_EDGES (200k) caps the payload and flags truncation.

Frontend

  • matrixDimensions.ts (new) — classifies each schema field as a flag (boolean), an enum (single_select) or a scalar, and derives the glyph, colour and label for every value. Codes come from the localized labels, so a French user reads C R M S.
  • matrixCells.ts (new) — builds the grid in one pass over the edges. This replaced four full-grid walks whose per-cell aggregation was itself quadratic in the leaf counts; cost grew with the area of the grid even though the data is sparse. matrixHierarchy.aggregateCount is deleted with them.
  • MatrixFilterBar.tsx (new) — relation type, direction and one filter per attribute, following the existing PortfolioReport filter-bar pattern. Hidden entirely when the axis pair has no relation types.
  • Cell displaysexists and count unchanged; codes adds colour-coded glyphs in the dense 32px grid, labels widens the data columns to 120px and shows the value names. Only the data columns change width — the row-header columns stay pinned to ROW_HEADER_COL_WIDTH, which their sticky offsets are computed from (the Fix Matrix report hierarchical-row cell misalignment #846 invariant, now covered by a test).
  • Gaps — uncovered-row/column tiles and a gaps-only view. Plus row/column search, axis transpose, relation detail in the cell popover, and column-header tooltips.
  • Excel export — a bespoke buildExportData: the grid as shown, and one row per relation with its values spread across columns. ReportShell's XLSX gate now allows a report that supplies its own export data even without a table view; ReportLegend gains an optional glyph.

Fixes

  • Boolean relation attributes could never be set. The attribute editor rendered only single_select, so any flag a relation type declared — the built-in Create/Read/Update/Delete pairs among them — was invisible and unusable. They now render as checkboxes and appear as relation badges.
  • Relations attached to a card that has children were dropped. Such a card was only a heading spanning its children, with no row or column of its own, so the grid discarded the relation while the totals and the hide-unrelated toggle — which read raw card ids — still counted it: the card showed on the axis, the intersection stayed blank, the numbers didn't add up. In the demo dataset that hid 17 of the 25 Organization–Application relations. The card now gets a line labelled (itself) under its heading, and a collapsed group counts its own relations along with its children's.
  • Hide unrelated cards could hide a card that was related — the test looked at descendants and never at the card itself.
  • Multiple relations between the same pair counted as one, and a same-type matrix counted each card's own diagonal.
  • Three Matrix labels rendered raw translation keys.

⚠️ Reviewers bisecting the diff: commit c3db091 restricted the endpoint to the relation types the metamodel declares for the axis pair. That was a regression — it silently dropped relations left behind by a renamed type, brought in by an import, or declared for another pair, and emptied the grid for a pair the metamodel says nothing about. Commit 13f1867 undoes it. What belongs in the grid is decided by the cards a relation connects.

Docs, i18n, demo

  • New guide sections (cell displays, relation filter, gaps, search/transpose, export) in all 10 localesen de fr es it pt zh ru da ar.
  • ~30 new matrix.* keys across the same 10 locales.
  • Demo relAppToInterface relations gain flow directions so the provider/consumer story has content; CRUD and usage-type demo data already existed.
  • VERSION → 2.33.1, CHANGELOG.md entries, docs/api/openapi.json regenerated.

Test Plan

Verified against a live instance seeded with SEED_DEMO=true, driving the real UI:

  • Application × Data Object, Codes mode — legend reads CRUD ● Create ● Read ● Update ● Delete, cells carry the glyphs, filters reach the server with the expected attr= params.
  • Organization × Application, Hide unrelated on — Relations tile 25 and grid Σ 25 now agree; before the last commit 17 of those 25 were missing. Engineering Division, Manufacturing, R&D and Sales & Marketing each carry their dots on an (itself) row.
  • Endpoint payload — confirmed attribute-bag interning (8 bags for 22 relations), that filtering shrinks the edges, and that the card lists stay complete under a filter.

Automated:

  • BackendTestMatrixRelationSemantics, TestMatrixFilters, TestMatrixRelationTypeCoverage cover orientation, interning, per-type filter scoping, __empty__, malformed filters, a relation type not declared for the pair, and a pair the metamodel declares nothing for. Full backend suite green.
  • Frontend — 1910 tests green. New: matrixCells.test.ts (including the invariant that every payload edge lands in a celldroppedEdges === 0 — which is the check that would have caught the parent-card bug), matrixDimensions.test.ts, MatrixReport.test.tsx, plus matrixHierarchy and RelationAttributesEditor additions.
  • npm run lint, npx tsc -b --noEmit, npm run build, mkdocs build --strict all clean.

Not done: the new UI is not re-screenshotted. The sandbox can't reach Google Fonts, so every Material Symbol captures as its ligature name. 35_report_matrix keeps a longer settle time and will pick up the new UI on the next npm run capture from an environment with normal font access.

  • All CI checks pass (backend lint, backend tests, frontend lint, frontend build, frontend tests)
  • Manually tested the affected feature
  • Added/updated tests for new or changed behavior

Checklist

  • My changes follow the conventions in CLAUDE.md
  • I added permission checks to any new mutating endpoints — none added; /reports/matrix keeps its reports.ea_dashboard gate
  • I created an Alembic migration for any schema changes — none needed, no schema change
  • I did not introduce hardcoded card types or fields (metamodel is data-driven)
  • I used async def for all new route handlers and DB operations
  • I did not expose sensitive fields (password hashes, encrypted secrets) in API responses
  • I bumped /VERSION and added a CHANGELOG.md entry (if user-facing change)
  • I added translations for new UI strings in all locales (10, not 8 — the template wording is stale)
  • I updated user documentation in docs/ (if UI or feature change)
  • Screenshots attached for UI changes — see the note in the Test Plan

https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY

claude added 5 commits July 28, 2026 06:34
The relation attribute editor only rendered single_select pickers, so any
boolean dimension declared in a relation type's attributes_schema was
invisible and could never be set — the seeded CRUD flags on
Application/Business Process to Data Object among them. Add a boolean branch
driven off the schema (nothing is keyed to a particular attribute), surface
those values as relation badges, and widen the editor gate accordingly:
gating on single_select alone is what made flag-only relation types
uneditable.

Also fixes three Matrix report labels that rendered raw translation keys, and
lets a report that supplies its own buildExportData offer Excel export even
without a table view — the Matrix has real tabular data but no table toggle,
so XLSX was unreachable for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
The matrix endpoint collapsed every link into a deduplicated set of card-id
pairs, discarding the relation type, its orientation and its attributes. A
cell could therefore only ever say "something connects these two" — never
what, nor how many, since the set also meant a pair with three relations
counted as one.

Each intersection now carries its edges: relation type, orientation relative
to the row axis, and the attribute values declared by that relation type's
schema. Distinct attribute bags are interned and referenced by index, so the
payload grows with the variety of the values rather than the volume of the
relations. Relation-type, attribute-value and direction filters are pushed
into SQL.

Only edges are filtered; the card lists always ship complete, because pruning
cards server-side would break the parent chains the client needs to rebuild
the hierarchy. Cards and relations are fetched with column-level selects and
a join on the endpoint types, replacing two IN lists that grew with the
landscape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
The matrix could say a link existed but never what it meant. Cells now have
two more display modes — compact codes in the dense grid, or readable labels
in a widened one — and a filter bar narrows the grid to relations of a given
type, direction or attribute value, which also hides the cards that no longer
match.

Everything shown, filtered and exported is derived from the relation type's
own attributes_schema at runtime. No attribute is named in code, so a
dimension an admin adds today gets a glyph, a colour, a legend entry, a
filter and an export column with no code change. flowDirection gets no
special case either: it is a select like any other, and structural direction
— which end of the relation the row card sits on — is carried per edge
instead.

Also adds coverage-gap metrics and a gaps-only view, row/column search, axis
transpose, a two-sheet Excel export (the grid, and a row per relation with
its values), and column-header tooltips.

The four full-grid passes behind the totals and the heat scale collapse into
one pass over the edges, which the per-cell value work made necessary; the
now-unused aggregateCount goes with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
Adds the guide sections for the value cell displays, the relation filter,
gap analysis, search and transpose, and the two-sheet Excel export, in all
ten locales.

The demo interface relations carried no flow direction, so the
Application x Interface provider/consumer story had nothing to show: the
first application listed against an interface now publishes it and the rest
consume it. The CRUD and usage-type demo data already existed.

Uncoloured relation values are now assigned a colour by their position within
their relation type rather than by hashing the key. Values of one relation
type are read side by side, and the hash gave two of the four CRUD flags
near-identical reds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
A card with sub-cards was only a heading spanning them, with no row or column
of its own, so a relation attached to the card itself had nowhere to land.
The grid dropped it, while the totals, the coverage figure and the
hide-unrelated toggle — which read raw card ids — still counted it: the card
appeared on the axis, the intersection stayed blank, and the numbers did not
add up. In the demo dataset that hid 17 of the 25 Organization-Application
relations.

Such a card now gets a line of its own labelled "(itself)" under its heading,
and a collapsed group counts its own relations along with its children's.
Hiding unrelated cards no longer drops a card whose only relations are its
own, either — that test looked at descendants and never at the card itself.

Also restores relation coverage the endpoint lost in 2.33.0: it had begun
matching on the relation types the metamodel declares for the axis pair, which
silently discarded relations left behind by a renamed type, brought in by an
import, or declared for another pair, and emptied the grid entirely for a pair
the metamodel says nothing about. What belongs in the grid is decided by the
cards a relation connects.

buildCellMatrix now counts the edges it cannot place, so the invariant that
every relation lands in a cell is asserted rather than assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
@vincentmakes vincentmakes changed the title Matrix report: relation semantics, filtering, and cell display modes Matrix report: show what a relation means, and filter by it Jul 28, 2026
@vincentmakes
vincentmakes merged commit 489d49b into main Jul 28, 2026
21 checks passed
@vincentmakes
vincentmakes deleted the claude/matrix-report-improvements-de9tt7 branch July 28, 2026 09:55
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.

2 participants