Skip to content

Lift the Layered Dependency View's rules into a tested module - #1243

Merged
vincentmakes merged 4 commits into
mainfrom
claude/mutation-lift-ldv
Oct 10, 2026
Merged

vincentmakes merged 4 commits into
mainfrom
claude/mutation-lift-ldv

Conversation

@vincentmakes

@vincentmakes vincentmakes commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

The Layered Dependency View (Dependencies report, a card's dependency section, TurboLens assessments) decided how its cards and lines look and behave inline in components that only a React Flow render can reach, so the nightly scores ~1,100 survivors in LayeredDependencyView.tsx. Those rules move into ldvViewModel.ts with literal, rule-by-rule tests, and the two layouts' triplicated edge assembly collapses onto shared helpers. Nothing a user sees changes. Fourth of the four approved PRs (after #1240, #1241, #1242, all merged); main is merged in, so it carries 2.158.12 above 2.158.11.

Type

  • feature
  • fix
  • refactor
  • docs
  • chore (CI / build / dependency / housekeeping)
  • security

Changes

  • New frontend/src/features/reports/ldvViewModel.ts (no React at runtime), taking from the view:
    • card: the type-colour fallback, the tint (12 % / 22 % / 6 % proposed), the lifecycle dot colour, the time-travel accent, the border, the planned/retired badge and its slot, type-icon placement beside the dot, the name's line clamp, handle visibility (mirrored ts-/bt- handles share their base's), the handle's position along its edge, and the 5 px click-versus-drag slop;
    • line: when a line is lit, its colours (a severed line stays red when lit), the 24-character label cut, the label's width, the connector and line stroke widths, and the label placement search (now fed a pointAt(t) function instead of an SVG element);
    • canvas: type counts, "did the type filter empty the view", the background cycle, the export file name, what a card click does in each mode (cardClickAction), whether a line touches the hovered card, and paint order.
  • LayeredDependencyView.tsx renders from those answers; markup, sx, i18n keys and the export filter are unchanged.
  • ldvLayoutShared.ts gains flowMarkers, relationSevered and routeEdgeData, replacing three copies of the same arrowhead, severed and route-field code in buildLdvFlow and both passes of the aggregate builder. The arrow colours are named once (ARROW_COLOR, SEVERED_ARROW_COLOR, same values).
  • ldvEdgeRouting.computeAbsPos is deleted: it had no caller and no test.
  • scripts/mutation/floors.toml: a [modules] floor of 91 for ldvViewModel.ts (CI score − 2).

Test Plan

  • ldvViewModel.test.ts (new). Every expectation is a literal, never the module's own constant, and test data is built inside each test. It covers:

    • each colour, border and badge rule, including the badge's slot when a NEW badge holds the top;
    • the handle mirror rule both ways (a used mirror does not light its base);
    • the click slop's boundary on both axes and both directions;
    • the label obstacle check's margin on all four sides;
    • label placement: preferred spot, nearest clear sample, the 8 % end skip, the fallback, the exact sample sequence, and the tie going to the earlier sample;
    • every click action, including the fall-through when a mode's handler is missing;
    • paint order with a hovered line, a hovered card and nothing hovered.
  • ldvLayoutShared.test.ts:

    • flowMarkers for each direction, bothWays and severed;
    • relationSevered for each change state;
    • routeEdgeData with no centre line, a centre line of 0, and a channel route.
  • layeredDependencyLayout.test.ts and ldvAggregate.test.ts: the first tests of each builder's arrowheads. They cover a reverse, an unset-to-retired and a bidirectional line, plus three connector cases: one merging both directions, one carrying a single type's reverse flow, and one whose relations are all severed.

  • ldvEdgeRouting.test.ts:

    • the label spread: 0.2 / 0.5 / 0.8 within 80 px of the first label, left to right, with a label exactly 80 px away left out, and unlabelled lines joining no cluster;
    • the nudge off a card (0.2 → 0.32), and its five-step cap;
    • the obstruction clearance as literal numbers (130 when centred, 80 when 50 px off-centre).
  • The demo-landscape guard (never lets two connectors leave one node from the same point) now keys on the physical point (ts-N = t-N), so a mirrored handle can no longer hide a shared point. I checked it holds on all six level and centre combinations.

  • The suspected spreadOverflow gap (top t and ts bucketed apart) did not reproduce: the aggregate layout puts a box's incoming and outgoing connectors on opposite borders. No fix; the tightened guard above would catch it.

  • The view test's React Flow fake is unchanged. Its header leaves edge geometry to the browser suite (jsdom has no getTotalLength), and the edge component's decisions are now unit-tested in the model.

  • Locally: tsc -b and eslint on the touched files only. Their one warning (react-refresh on the readableTypeColor re-export) predates this PR. The floors file is validated by backend/tests/core/test_mutation_scripts.py -k floors. As agreed, the tests run in CI.

  • Mutation score (CI's Frontend Mutation Tests on 903c02ecc): 88.0 % of 442 changed-line mutants; ldvViewModel.ts 93.8 % (317 of 338), ldvLayoutShared.ts 95.3 % (41 of 43), LayeredDependencyView.tsx 50.8 % (31 of 61, the view's sx and render wiring). Survivors kept in the two modules, none pragma'd: the colour constants (FALLBACK_TYPE_COLOR, the five PHASE_DOT entries, ARROW_COLOR, SEVERED_ARROW_COLOR, the BACKGROUND_CYCLE literals — presentation values the tests read back rather than pin one by one); the pixel arithmetic in DOT_BOX and TYPE_ICON_RIGHT_BESIDE_DOT (layout constants); and three label-search mutants that are equivalent on the sample grid — i < steps → <= adds t = 1, which the end skip drops anyway, t < 0.08 / t > 0.92 → <= / >= on a grid no sample of which lands on either bound, and the preferred-spot early return, since the preferred point is also the first clear sample. The [modules] floor of 91 for ldvViewModel.ts (score − 2) is in floors.toml.

  • All CI checks pass (backend lint, backend tests, frontend lint, frontend build, frontend tests, mutation tests)

  • Manually tested the affected feature

  • Added/updated tests for new or changed behavior

  • Mutation gate: my tests kill the mutants on the lines I changed (make mutation-diff). Score: 88.0 % (floor 60 %); survivors listed above, no pragma added.

Checklist

  • My changes follow the conventions in CLAUDE.md
  • I added permission checks to any new mutating endpoints
  • I created an Alembic migration for any schema changes
  • 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 10 locales (if applicable)
  • I updated user documentation in docs/ (if UI or feature change)
  • Screenshots attached for UI changes (if applicable)

VERSION 2.158.12, one CHANGELOG Changed line. No backend, schema, permission, UI-string or docs change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014eZ98kaESE7YBa84fT1msc


Generated by Claude Code

The view's node, edge and canvas components decided their colours,
borders, badges, handle visibility, click-versus-drag, label width and
placement, stroke width, click actions and paint order inline, where
only a React Flow render could reach them. Those rules now live in
ldvViewModel.ts with literal, rule-by-rule tests; the component renders
what the module answers.

The plain and aggregate layouts built arrowheads, the severed check and
the route fields three times over; they share flowMarkers,
relationSevered and routeEdgeData in ldvLayoutShared.ts, and the arrow
colours are named once. The unused computeAbsPos is gone.

New tests also pin the router's label spread (0.2 / 0.5 / 0.8 within
80 px, the nudge off a card and its five-step cap), the obstruction
clearance, each builder's arrowheads, and key the demo-landscape guard
on the physical handle point so a mirrored handle cannot hide a shared
point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eZ98kaESE7YBa84fT1msc
log2(7) is just over the 2.8 cap, so seven relations already draw the
capped width; the test now checks six (under) and seven (capped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eZ98kaESE7YBa84fT1msc
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Deploying turbo-ea-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: a43ec71
Status: ✅  Deploy successful!
Preview URL: https://30f62aea.turbo-ea.pages.dev
Branch Preview URL: https://claude-mutation-lift-ldv.turbo-ea.pages.dev

View logs

CI's Frontend Mutation Tests scored the new module at 93.8 % (317 of 338
mutants); the floor sits two points under, as the other module floors do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eZ98kaESE7YBa84fT1msc
@vincentmakes
vincentmakes merged commit b1704a3 into main Oct 10, 2026
38 checks passed
@vincentmakes
vincentmakes deleted the claude/mutation-lift-ldv branch October 10, 2026 14:42
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