[Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

Description

@radroid

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I am describing a concrete problem or use case, not just a vague idea.

Area

apps/web

Problem or use case

There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

Concretely:

1. No architecture view exists, and no primitive for one exists.
apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

2. No coverage data can be produced today, at all.

  • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
  • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
  • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
    Error: Failed to parse source for import analysis ...
    Plugin: vite:import-analysis
    File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
    
    The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

Proposed solution

One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

// apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

Upstream files touched, and exactly what changes:

FileChange
apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
// apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


Deliverable A — architecture map (t3x:architecture)

Where the graph comes from

Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

  • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
  • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
  • @t3tools/shared@t3tools/contracts
  • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

How the data is fetched (no server work in v1)

Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

How it renders

Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

B0. The metric, stated precisely

"% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

Primary metric — merged statement coverage over an explicit include set:

covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
packages/*/src/**/*.{ts,tsx}
infra/relay/src/**/*.ts
exclude: **/*.test.{ts,tsx}
**/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
**/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
**/routeTree.gen.ts
apps/marketing/** (Astro; not under vitest)
scripts/**

The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

Fallback metric — module test-adjacency (available today, no test run, no dependency):

Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

B1. Producing coverage

Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

Because the root run is broken (see Problem), collect per package and merge:

vp run -r --concurrency-limit 2 test \
--coverage --coverage.provider=v8 \
--coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
--coverage.reporter=json --coverage.reporter=json-summary \
--coverage.reportOnFailure \
--testTimeout=120000 --hookTimeout=120000

Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

B2. Triggering the run from inside the app

This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

B3. The overlay itself

The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

  • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
  • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
  • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
  • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

Suggested file layout (all fork-owned, churn 0)

apps/web/src/t3x/rightPanel/
T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
architecture/
graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
layout.ts # layered DAG layout, dependency-free
ArchitectureMap.tsx # SVG renderer + selection + coverage tint
coverage/
snapshot.ts # read + merge coverage-summary.json across packages
adjacency.ts # sibling-test-file proxy
CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list

Why this matters

For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

Smallest useful scope

Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

  1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
  2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
  3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
  4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
  5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

That is a complete, useful, honest feature on its own.

Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

Alternatives considered

Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

Risks or tradeoffs

Seam cost (the main one)

Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

Upstream filechurnest. fork Δest. added risk
apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
apps/web/src/rightPanelStore.ts9~12 lines~108
.gitignore / t3.json (deliverable B only)4 combined~2 lines~8
pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

Two things must be said plainly:

  1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
  2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

Upstream-conflict hazard

The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

Honest metric risks

  • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
  • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
  • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
  • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

Performance / operational

  • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
  • Coverage artifacts dirty the working tree until .gitignore is updated.

UNVERIFIED — must be settled before Deliverable B is designed further

  1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
  2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
  3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
  4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
  5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
  6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

Examples or references

Duplicate / related issues

A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

Adjacent, not duplicate:

Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

Panel plumbing:

  • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
  • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
  • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
  • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
  • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
  • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
  • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
  • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
  • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

Fork seam:

  • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
  • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
  • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
  • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
  • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

Data sources:

  • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
  • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
  • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
  • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
  • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

Coverage / testing:

  • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
  • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
  • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
  • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
  • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
  • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
  • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
  • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
  • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
  • No madge / dependency-cruiser / @nx / turbo; no turbo.json
  • .gitignore has no coverage entry
  • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
  • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

Duplicate search performed before filing

Searched both repos exhaustively, not sampled.

Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

Contribution

  • I would be open to helping implement this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

      Description

      @radroid

      Before submitting

      • I searched existing issues and did not find a duplicate.
      • I am describing a concrete problem or use case, not just a vague idea.

      Area

      apps/web

      Problem or use case

      There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

      Concretely:

      1. No architecture view exists, and no primitive for one exists.
      apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

      2. No coverage data can be produced today, at all.

      • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
      • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
      • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
        Error: Failed to parse source for import analysis ...
        Plugin: vite:import-analysis
        File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
        
        The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

      3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

      Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

      Proposed solution

      One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


      0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

      The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

      Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

      // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

      Upstream files touched, and exactly what changes:

      FileChange
      apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
      apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
      apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
      apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
      apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
      // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

      Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

      Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

      Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

      Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


      Deliverable A — architecture map (t3x:architecture)

      Where the graph comes from

      Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

      • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
      • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
      • @t3tools/shared@t3tools/contracts
      • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

      Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

      Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

      Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

      Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

      Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

      How the data is fetched (no server work in v1)

      Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

      Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

      How it renders

      Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

      This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


      Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

      B0. The metric, stated precisely

      "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

      Primary metric — merged statement coverage over an explicit include set:

      covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

      Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

      include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
      packages/*/src/**/*.{ts,tsx}
      infra/relay/src/**/*.ts
      exclude: **/*.test.{ts,tsx}
      **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
      **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
      **/routeTree.gen.ts
      apps/marketing/** (Astro; not under vitest)
      scripts/**
      

      The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

      Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

      Fallback metric — module test-adjacency (available today, no test run, no dependency):

      Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

      This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

      B1. Producing coverage

      Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

      Because the root run is broken (see Problem), collect per package and merge:

      vp run -r --concurrency-limit 2 test \
      --coverage --coverage.provider=v8 \
      --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
      --coverage.reporter=json --coverage.reporter=json-summary \
      --coverage.reportOnFailure \
      --testTimeout=120000 --hookTimeout=120000
      

      Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

      Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

      Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

      B2. Triggering the run from inside the app

      This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

      v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

      B3. The overlay itself

      The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

      • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
      • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
      • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
      • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

      Suggested file layout (all fork-owned, churn 0)

      apps/web/src/t3x/rightPanel/
      T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
      icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
      architecture/
      graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
      layout.ts # layered DAG layout, dependency-free
      ArchitectureMap.tsx # SVG renderer + selection + coverage tint
      coverage/
      snapshot.ts # read + merge coverage-summary.json across packages
      adjacency.ts # sibling-test-file proxy
      CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
      

      Why this matters

      For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

      For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

      It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

      It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

      It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

      Smallest useful scope

      Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

      1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
      2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
      3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
      4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
      5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

      That is a complete, useful, honest feature on its own.

      Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

      Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

      On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

      Alternatives considered

      Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

      Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

      Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

      Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

      Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

      Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

      Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

      Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

      Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

      Risks or tradeoffs

      Seam cost (the main one)

      Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

      Upstream filechurnest. fork Δest. added risk
      apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
      apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
      apps/web/src/rightPanelStore.ts9~12 lines~108
      .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
      pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

      Two things must be said plainly:

      1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
      2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

      Upstream-conflict hazard

      The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

      Honest metric risks

      • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
      • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
      • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
      • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

      Performance / operational

      • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
      • Coverage artifacts dirty the working tree until .gitignore is updated.

      UNVERIFIED — must be settled before Deliverable B is designed further

      1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
      2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
      3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
      4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
      5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
      6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

      Examples or references

      Duplicate / related issues

      A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

      Adjacent, not duplicate:

      Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

      Panel plumbing:

      • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
      • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
      • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
      • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
      • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
      • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
      • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
      • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
      • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

      Fork seam:

      • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
      • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
      • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
      • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
      • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

      Data sources:

      • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
      • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
      • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
      • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
      • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

      Coverage / testing:

      • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
      • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
      • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
      • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
      • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
      • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
      • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
      • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
      • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
      • No madge / dependency-cruiser / @nx / turbo; no turbo.json
      • .gitignore has no coverage entry
      • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
      • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

      Duplicate search performed before filing

      Searched both repos exhaustively, not sampled.

      Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

      Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

      The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

      Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

      Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

      Contribution

      • I would be open to helping implement this.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        enhancementNew feature or request

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

          Description

          @radroid

          Before submitting

          • I searched existing issues and did not find a duplicate.
          • I am describing a concrete problem or use case, not just a vague idea.

          Area

          apps/web

          Problem or use case

          There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

          Concretely:

          1. No architecture view exists, and no primitive for one exists.
          apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

          2. No coverage data can be produced today, at all.

          • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
          • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
          • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
            Error: Failed to parse source for import analysis ...
            Plugin: vite:import-analysis
            File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
            
            The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

          3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

          Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

          Proposed solution

          One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


          0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

          The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

          Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

          // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

          Upstream files touched, and exactly what changes:

          FileChange
          apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
          apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
          apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
          apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
          apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
          // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

          Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

          Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

          Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

          Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


          Deliverable A — architecture map (t3x:architecture)

          Where the graph comes from

          Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

          • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
          • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
          • @t3tools/shared@t3tools/contracts
          • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

          Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

          Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

          Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

          Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

          Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

          How the data is fetched (no server work in v1)

          Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

          Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

          How it renders

          Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

          This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


          Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

          B0. The metric, stated precisely

          "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

          Primary metric — merged statement coverage over an explicit include set:

          covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

          Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

          include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
          packages/*/src/**/*.{ts,tsx}
          infra/relay/src/**/*.ts
          exclude: **/*.test.{ts,tsx}
          **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
          **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
          **/routeTree.gen.ts
          apps/marketing/** (Astro; not under vitest)
          scripts/**
          

          The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

          Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

          Fallback metric — module test-adjacency (available today, no test run, no dependency):

          Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

          This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

          B1. Producing coverage

          Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

          Because the root run is broken (see Problem), collect per package and merge:

          vp run -r --concurrency-limit 2 test \
          --coverage --coverage.provider=v8 \
          --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
          --coverage.reporter=json --coverage.reporter=json-summary \
          --coverage.reportOnFailure \
          --testTimeout=120000 --hookTimeout=120000
          

          Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

          Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

          Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

          B2. Triggering the run from inside the app

          This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

          v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

          B3. The overlay itself

          The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

          • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
          • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
          • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
          • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

          Suggested file layout (all fork-owned, churn 0)

          apps/web/src/t3x/rightPanel/
          T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
          icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
          architecture/
          graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
          layout.ts # layered DAG layout, dependency-free
          ArchitectureMap.tsx # SVG renderer + selection + coverage tint
          coverage/
          snapshot.ts # read + merge coverage-summary.json across packages
          adjacency.ts # sibling-test-file proxy
          CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
          

          Why this matters

          For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

          For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

          It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

          It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

          It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

          Smallest useful scope

          Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

          1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
          2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
          3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
          4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
          5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

          That is a complete, useful, honest feature on its own.

          Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

          Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

          On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

          Alternatives considered

          Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

          Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

          Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

          Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

          Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

          Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

          Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

          Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

          Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

          Risks or tradeoffs

          Seam cost (the main one)

          Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

          Upstream filechurnest. fork Δest. added risk
          apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
          apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
          apps/web/src/rightPanelStore.ts9~12 lines~108
          .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
          pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

          Two things must be said plainly:

          1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
          2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

          Upstream-conflict hazard

          The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

          Honest metric risks

          • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
          • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
          • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
          • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

          Performance / operational

          • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
          • Coverage artifacts dirty the working tree until .gitignore is updated.

          UNVERIFIED — must be settled before Deliverable B is designed further

          1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
          2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
          3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
          4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
          5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
          6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

          Examples or references

          Duplicate / related issues

          A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

          Adjacent, not duplicate:

          Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

          Panel plumbing:

          • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
          • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
          • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
          • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
          • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
          • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
          • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
          • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
          • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

          Fork seam:

          • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
          • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
          • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
          • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
          • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

          Data sources:

          • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
          • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
          • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
          • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
          • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

          Coverage / testing:

          • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
          • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
          • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
          • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
          • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
          • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
          • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
          • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
          • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
          • No madge / dependency-cruiser / @nx / turbo; no turbo.json
          • .gitignore has no coverage entry
          • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
          • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

          Duplicate search performed before filing

          Searched both repos exhaustively, not sampled.

          Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

          Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

          The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

          Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

          Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

          Contribution

          • I would be open to helping implement this.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            enhancementNew feature or request

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

              Description

              @radroid

              Before submitting

              • I searched existing issues and did not find a duplicate.
              • I am describing a concrete problem or use case, not just a vague idea.

              Area

              apps/web

              Problem or use case

              There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

              Concretely:

              1. No architecture view exists, and no primitive for one exists.
              apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

              2. No coverage data can be produced today, at all.

              • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
              • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
              • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
                Error: Failed to parse source for import analysis ...
                Plugin: vite:import-analysis
                File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
                
                The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

              3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

              Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

              Proposed solution

              One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


              0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

              The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

              Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

              // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

              Upstream files touched, and exactly what changes:

              FileChange
              apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
              apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
              apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
              apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
              apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
              // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

              Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

              Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

              Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

              Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


              Deliverable A — architecture map (t3x:architecture)

              Where the graph comes from

              Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

              • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
              • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
              • @t3tools/shared@t3tools/contracts
              • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

              Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

              Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

              Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

              Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

              Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

              How the data is fetched (no server work in v1)

              Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

              Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

              How it renders

              Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

              This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


              Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

              B0. The metric, stated precisely

              "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

              Primary metric — merged statement coverage over an explicit include set:

              covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

              Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

              include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
              packages/*/src/**/*.{ts,tsx}
              infra/relay/src/**/*.ts
              exclude: **/*.test.{ts,tsx}
              **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
              **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
              **/routeTree.gen.ts
              apps/marketing/** (Astro; not under vitest)
              scripts/**
              

              The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

              Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

              Fallback metric — module test-adjacency (available today, no test run, no dependency):

              Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

              This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

              B1. Producing coverage

              Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

              Because the root run is broken (see Problem), collect per package and merge:

              vp run -r --concurrency-limit 2 test \
              --coverage --coverage.provider=v8 \
              --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
              --coverage.reporter=json --coverage.reporter=json-summary \
              --coverage.reportOnFailure \
              --testTimeout=120000 --hookTimeout=120000
              

              Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

              Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

              Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

              B2. Triggering the run from inside the app

              This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

              v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

              B3. The overlay itself

              The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

              • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
              • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
              • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
              • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

              Suggested file layout (all fork-owned, churn 0)

              apps/web/src/t3x/rightPanel/
              T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
              icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
              architecture/
              graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
              layout.ts # layered DAG layout, dependency-free
              ArchitectureMap.tsx # SVG renderer + selection + coverage tint
              coverage/
              snapshot.ts # read + merge coverage-summary.json across packages
              adjacency.ts # sibling-test-file proxy
              CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
              

              Why this matters

              For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

              For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

              It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

              It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

              It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

              Smallest useful scope

              Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

              1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
              2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
              3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
              4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
              5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

              That is a complete, useful, honest feature on its own.

              Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

              Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

              On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

              Alternatives considered

              Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

              Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

              Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

              Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

              Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

              Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

              Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

              Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

              Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

              Risks or tradeoffs

              Seam cost (the main one)

              Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

              Upstream filechurnest. fork Δest. added risk
              apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
              apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
              apps/web/src/rightPanelStore.ts9~12 lines~108
              .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
              pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

              Two things must be said plainly:

              1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
              2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

              Upstream-conflict hazard

              The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

              Honest metric risks

              • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
              • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
              • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
              • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

              Performance / operational

              • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
              • Coverage artifacts dirty the working tree until .gitignore is updated.

              UNVERIFIED — must be settled before Deliverable B is designed further

              1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
              2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
              3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
              4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
              5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
              6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

              Examples or references

              Duplicate / related issues

              A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

              Adjacent, not duplicate:

              Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

              Panel plumbing:

              • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
              • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
              • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
              • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
              • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
              • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
              • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
              • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
              • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

              Fork seam:

              • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
              • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
              • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
              • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
              • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

              Data sources:

              • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
              • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
              • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
              • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
              • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

              Coverage / testing:

              • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
              • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
              • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
              • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
              • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
              • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
              • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
              • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
              • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
              • No madge / dependency-cruiser / @nx / turbo; no turbo.json
              • .gitignore has no coverage entry
              • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
              • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

              Duplicate search performed before filing

              Searched both repos exhaustively, not sampled.

              Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

              Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

              The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

              Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

              Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

              Contribution

              • I would be open to helping implement this.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                enhancementNew feature or request

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

                  Description

                  @radroid

                  Before submitting

                  • I searched existing issues and did not find a duplicate.
                  • I am describing a concrete problem or use case, not just a vague idea.

                  Area

                  apps/web

                  Problem or use case

                  There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

                  Concretely:

                  1. No architecture view exists, and no primitive for one exists.
                  apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

                  2. No coverage data can be produced today, at all.

                  • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
                  • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
                  • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
                    Error: Failed to parse source for import analysis ...
                    Plugin: vite:import-analysis
                    File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
                    
                    The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

                  3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

                  Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

                  Proposed solution

                  One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


                  0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

                  The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

                  Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

                  // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

                  Upstream files touched, and exactly what changes:

                  FileChange
                  apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
                  apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
                  apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
                  apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
                  apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
                  // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

                  Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

                  Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

                  Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

                  Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


                  Deliverable A — architecture map (t3x:architecture)

                  Where the graph comes from

                  Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

                  • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
                  • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
                  • @t3tools/shared@t3tools/contracts
                  • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

                  Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

                  Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

                  Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

                  Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

                  Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

                  How the data is fetched (no server work in v1)

                  Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

                  Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

                  How it renders

                  Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

                  This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


                  Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

                  B0. The metric, stated precisely

                  "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

                  Primary metric — merged statement coverage over an explicit include set:

                  covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

                  Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

                  include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
                  packages/*/src/**/*.{ts,tsx}
                  infra/relay/src/**/*.ts
                  exclude: **/*.test.{ts,tsx}
                  **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
                  **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
                  **/routeTree.gen.ts
                  apps/marketing/** (Astro; not under vitest)
                  scripts/**
                  

                  The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

                  Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

                  Fallback metric — module test-adjacency (available today, no test run, no dependency):

                  Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

                  This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

                  B1. Producing coverage

                  Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

                  Because the root run is broken (see Problem), collect per package and merge:

                  vp run -r --concurrency-limit 2 test \
                  --coverage --coverage.provider=v8 \
                  --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
                  --coverage.reporter=json --coverage.reporter=json-summary \
                  --coverage.reportOnFailure \
                  --testTimeout=120000 --hookTimeout=120000
                  

                  Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

                  Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

                  Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

                  B2. Triggering the run from inside the app

                  This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

                  v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

                  B3. The overlay itself

                  The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

                  • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
                  • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
                  • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
                  • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

                  Suggested file layout (all fork-owned, churn 0)

                  apps/web/src/t3x/rightPanel/
                  T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
                  icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
                  architecture/
                  graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
                  layout.ts # layered DAG layout, dependency-free
                  ArchitectureMap.tsx # SVG renderer + selection + coverage tint
                  coverage/
                  snapshot.ts # read + merge coverage-summary.json across packages
                  adjacency.ts # sibling-test-file proxy
                  CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
                  

                  Why this matters

                  For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

                  For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

                  It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

                  It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

                  It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

                  Smallest useful scope

                  Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

                  1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
                  2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
                  3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
                  4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
                  5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

                  That is a complete, useful, honest feature on its own.

                  Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

                  Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

                  On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

                  Alternatives considered

                  Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

                  Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

                  Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

                  Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

                  Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

                  Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

                  Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

                  Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

                  Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

                  Risks or tradeoffs

                  Seam cost (the main one)

                  Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

                  Upstream filechurnest. fork Δest. added risk
                  apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
                  apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
                  apps/web/src/rightPanelStore.ts9~12 lines~108
                  .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
                  pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

                  Two things must be said plainly:

                  1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
                  2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

                  Upstream-conflict hazard

                  The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

                  Honest metric risks

                  • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
                  • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
                  • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
                  • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

                  Performance / operational

                  • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
                  • Coverage artifacts dirty the working tree until .gitignore is updated.

                  UNVERIFIED — must be settled before Deliverable B is designed further

                  1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
                  2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
                  3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
                  4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
                  5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
                  6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

                  Examples or references

                  Duplicate / related issues

                  A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

                  Adjacent, not duplicate:

                  Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

                  Panel plumbing:

                  • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
                  • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
                  • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
                  • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
                  • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
                  • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
                  • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
                  • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
                  • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

                  Fork seam:

                  • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
                  • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
                  • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
                  • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
                  • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

                  Data sources:

                  • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
                  • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
                  • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
                  • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
                  • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

                  Coverage / testing:

                  • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
                  • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
                  • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
                  • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
                  • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
                  • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
                  • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
                  • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
                  • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
                  • No madge / dependency-cruiser / @nx / turbo; no turbo.json
                  • .gitignore has no coverage entry
                  • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
                  • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

                  Duplicate search performed before filing

                  Searched both repos exhaustively, not sampled.

                  Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

                  Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

                  The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

                  Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

                  Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

                  Contribution

                  • I would be open to helping implement this.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    enhancementNew feature or request

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

                      Description

                      @radroid

                      Before submitting

                      • I searched existing issues and did not find a duplicate.
                      • I am describing a concrete problem or use case, not just a vague idea.

                      Area

                      apps/web

                      Problem or use case

                      There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

                      Concretely:

                      1. No architecture view exists, and no primitive for one exists.
                      apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

                      2. No coverage data can be produced today, at all.

                      • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
                      • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
                      • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
                        Error: Failed to parse source for import analysis ...
                        Plugin: vite:import-analysis
                        File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
                        
                        The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

                      3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

                      Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

                      Proposed solution

                      One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


                      0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

                      The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

                      Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

                      // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

                      Upstream files touched, and exactly what changes:

                      FileChange
                      apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
                      apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
                      apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
                      apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
                      apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
                      // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

                      Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

                      Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

                      Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

                      Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


                      Deliverable A — architecture map (t3x:architecture)

                      Where the graph comes from

                      Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

                      • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
                      • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
                      • @t3tools/shared@t3tools/contracts
                      • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

                      Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

                      Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

                      Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

                      Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

                      Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

                      How the data is fetched (no server work in v1)

                      Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

                      Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

                      How it renders

                      Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

                      This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


                      Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

                      B0. The metric, stated precisely

                      "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

                      Primary metric — merged statement coverage over an explicit include set:

                      covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

                      Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

                      include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
                      packages/*/src/**/*.{ts,tsx}
                      infra/relay/src/**/*.ts
                      exclude: **/*.test.{ts,tsx}
                      **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
                      **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
                      **/routeTree.gen.ts
                      apps/marketing/** (Astro; not under vitest)
                      scripts/**
                      

                      The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

                      Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

                      Fallback metric — module test-adjacency (available today, no test run, no dependency):

                      Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

                      This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

                      B1. Producing coverage

                      Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

                      Because the root run is broken (see Problem), collect per package and merge:

                      vp run -r --concurrency-limit 2 test \
                      --coverage --coverage.provider=v8 \
                      --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
                      --coverage.reporter=json --coverage.reporter=json-summary \
                      --coverage.reportOnFailure \
                      --testTimeout=120000 --hookTimeout=120000
                      

                      Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

                      Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

                      Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

                      B2. Triggering the run from inside the app

                      This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

                      v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

                      B3. The overlay itself

                      The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

                      • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
                      • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
                      • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
                      • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

                      Suggested file layout (all fork-owned, churn 0)

                      apps/web/src/t3x/rightPanel/
                      T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
                      icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
                      architecture/
                      graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
                      layout.ts # layered DAG layout, dependency-free
                      ArchitectureMap.tsx # SVG renderer + selection + coverage tint
                      coverage/
                      snapshot.ts # read + merge coverage-summary.json across packages
                      adjacency.ts # sibling-test-file proxy
                      CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
                      

                      Why this matters

                      For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

                      For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

                      It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

                      It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

                      It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

                      Smallest useful scope

                      Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

                      1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
                      2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
                      3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
                      4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
                      5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

                      That is a complete, useful, honest feature on its own.

                      Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

                      Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

                      On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

                      Alternatives considered

                      Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

                      Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

                      Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

                      Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

                      Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

                      Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

                      Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

                      Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

                      Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

                      Risks or tradeoffs

                      Seam cost (the main one)

                      Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

                      Upstream filechurnest. fork Δest. added risk
                      apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
                      apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
                      apps/web/src/rightPanelStore.ts9~12 lines~108
                      .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
                      pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

                      Two things must be said plainly:

                      1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
                      2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

                      Upstream-conflict hazard

                      The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

                      Honest metric risks

                      • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
                      • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
                      • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
                      • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

                      Performance / operational

                      • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
                      • Coverage artifacts dirty the working tree until .gitignore is updated.

                      UNVERIFIED — must be settled before Deliverable B is designed further

                      1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
                      2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
                      3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
                      4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
                      5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
                      6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

                      Examples or references

                      Duplicate / related issues

                      A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

                      Adjacent, not duplicate:

                      Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

                      Panel plumbing:

                      • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
                      • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
                      • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
                      • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
                      • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
                      • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
                      • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
                      • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
                      • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

                      Fork seam:

                      • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
                      • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
                      • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
                      • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
                      • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

                      Data sources:

                      • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
                      • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
                      • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
                      • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
                      • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

                      Coverage / testing:

                      • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
                      • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
                      • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
                      • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
                      • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
                      • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
                      • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
                      • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
                      • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
                      • No madge / dependency-cruiser / @nx / turbo; no turbo.json
                      • .gitignore has no coverage entry
                      • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
                      • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

                      Duplicate search performed before filing

                      Searched both repos exhaustively, not sampled.

                      Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

                      Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

                      The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

                      Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

                      Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

                      Contribution

                      • I would be open to helping implement this.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        enhancementNew feature or request

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

                          Description

                          @radroid

                          Before submitting

                          • I searched existing issues and did not find a duplicate.
                          • I am describing a concrete problem or use case, not just a vague idea.

                          Area

                          apps/web

                          Problem or use case

                          There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

                          Concretely:

                          1. No architecture view exists, and no primitive for one exists.
                          apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

                          2. No coverage data can be produced today, at all.

                          • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
                          • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
                          • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
                            Error: Failed to parse source for import analysis ...
                            Plugin: vite:import-analysis
                            File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
                            
                            The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

                          3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

                          Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

                          Proposed solution

                          One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


                          0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

                          The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

                          Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

                          // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

                          Upstream files touched, and exactly what changes:

                          FileChange
                          apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
                          apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
                          apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
                          apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
                          apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
                          // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

                          Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

                          Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

                          Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

                          Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


                          Deliverable A — architecture map (t3x:architecture)

                          Where the graph comes from

                          Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

                          • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
                          • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
                          • @t3tools/shared@t3tools/contracts
                          • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

                          Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

                          Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

                          Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

                          Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

                          Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

                          How the data is fetched (no server work in v1)

                          Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

                          Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

                          How it renders

                          Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

                          This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


                          Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

                          B0. The metric, stated precisely

                          "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

                          Primary metric — merged statement coverage over an explicit include set:

                          covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

                          Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

                          include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
                          packages/*/src/**/*.{ts,tsx}
                          infra/relay/src/**/*.ts
                          exclude: **/*.test.{ts,tsx}
                          **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
                          **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
                          **/routeTree.gen.ts
                          apps/marketing/** (Astro; not under vitest)
                          scripts/**
                          

                          The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

                          Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

                          Fallback metric — module test-adjacency (available today, no test run, no dependency):

                          Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

                          This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

                          B1. Producing coverage

                          Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

                          Because the root run is broken (see Problem), collect per package and merge:

                          vp run -r --concurrency-limit 2 test \
                          --coverage --coverage.provider=v8 \
                          --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
                          --coverage.reporter=json --coverage.reporter=json-summary \
                          --coverage.reportOnFailure \
                          --testTimeout=120000 --hookTimeout=120000
                          

                          Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

                          Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

                          Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

                          B2. Triggering the run from inside the app

                          This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

                          v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

                          B3. The overlay itself

                          The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

                          • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
                          • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
                          • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
                          • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

                          Suggested file layout (all fork-owned, churn 0)

                          apps/web/src/t3x/rightPanel/
                          T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
                          icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
                          architecture/
                          graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
                          layout.ts # layered DAG layout, dependency-free
                          ArchitectureMap.tsx # SVG renderer + selection + coverage tint
                          coverage/
                          snapshot.ts # read + merge coverage-summary.json across packages
                          adjacency.ts # sibling-test-file proxy
                          CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
                          

                          Why this matters

                          For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

                          For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

                          It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

                          It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

                          It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

                          Smallest useful scope

                          Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

                          1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
                          2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
                          3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
                          4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
                          5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

                          That is a complete, useful, honest feature on its own.

                          Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

                          Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

                          On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

                          Alternatives considered

                          Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

                          Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

                          Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

                          Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

                          Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

                          Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

                          Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

                          Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

                          Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

                          Risks or tradeoffs

                          Seam cost (the main one)

                          Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

                          Upstream filechurnest. fork Δest. added risk
                          apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
                          apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
                          apps/web/src/rightPanelStore.ts9~12 lines~108
                          .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
                          pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

                          Two things must be said plainly:

                          1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
                          2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

                          Upstream-conflict hazard

                          The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

                          Honest metric risks

                          • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
                          • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
                          • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
                          • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

                          Performance / operational

                          • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
                          • Coverage artifacts dirty the working tree until .gitignore is updated.

                          UNVERIFIED — must be settled before Deliverable B is designed further

                          1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
                          2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
                          3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
                          4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
                          5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
                          6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

                          Examples or references

                          Duplicate / related issues

                          A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

                          Adjacent, not duplicate:

                          Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

                          Panel plumbing:

                          • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
                          • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
                          • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
                          • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
                          • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
                          • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
                          • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
                          • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
                          • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

                          Fork seam:

                          • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
                          • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
                          • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
                          • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
                          • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

                          Data sources:

                          • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
                          • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
                          • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
                          • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
                          • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

                          Coverage / testing:

                          • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
                          • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
                          • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
                          • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
                          • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
                          • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
                          • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
                          • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
                          • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
                          • No madge / dependency-cruiser / @nx / turbo; no turbo.json
                          • .gitignore has no coverage entry
                          • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
                          • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

                          Duplicate search performed before filing

                          Searched both repos exhaustively, not sampled.

                          Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

                          Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

                          The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

                          Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

                          Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

                          Contribution

                          • I would be open to helping implement this.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            enhancementNew feature or request

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              [Feature]: Architecture map in the right panel, with test coverage painted on top of it #45

                              Description

                              @radroid

                              Before submitting

                              • I searched existing issues and did not find a duplicate.
                              • I am describing a concrete problem or use case, not just a vague idea.

                              Area

                              apps/web

                              Problem or use case

                              There is no way to see the shape of this codebase from inside T3 Code, and no way to see which parts of it are untested. Both gaps are worse here than in a normal repo because the agent is the primary author: when a model proposes a change to apps/server/src/orchestration/, nothing in the UI says "that package has 12 inbound dependents and no test sits next to the file you are editing."

                              Concretely:

                              1. No architecture view exists, and no primitive for one exists.
                              apps/web has zero visualization or graph dependencies. grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml returns 0 (verified in this checkout). The only structural description of the workspace is prose in docs/internals/workspace-layout.md, which nobody reads while a turn is running and which drifts silently.

                              2. No coverage data can be produced today, at all.

                              • Coverage is configured nowhere: grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output (verified).
                              • No provider is installed. @vitest/coverage-v8 / @vitest/coverage-istanbul appear in pnpm-lock.yaml:10174-10175 only as optional peers of vitest@4.1.9; the installed store has only @types+istanbul-lib-coverage@2.0.6. Vitest loads the provider with a bare dynamic import() and does not auto-install, so --coverage fails hard with ERR_MODULE_NOT_FOUND until it is added.
                              • A single root-level run that would produce one merged report is broken. I ran node_modules/.bin/vp test list from the repo root today and it dies during import analysis:
                                Error: Failed to parse source for import analysis ...
                                Plugin: vite:import-analysis
                                File: /Users/rajdholakia/Developer/t3code/apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline
                                
                                The root config has no assetsInclude/react/tailwind plugins, so coverage must be collected per package and merged offline.

                              3. The two questions are actually one question. "Which module is weakly tested" is only actionable if you can see what depends on it. A flat coverage table sorts by percentage; a graph sorts by blast radius. packages/contracts is the universal leaf (@t3tools/shared@t3tools/contracts; @t3tools/webclient-runtime, contracts, shared; t3 (apps/server) → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server — all verified from the workspace: protocol deps), so a gap there is categorically more dangerous than the same gap in apps/marketing. Only the combined view says that.

                              Today the honest number, computed by filesystem scan in this checkout: 681 of 1553 source modules have a sibling *.test.ts(x) file — 43.9%, across 738 test files. That is a real signal (co-location is near-universal here: exactly one test lives outside its module's directory, apps/server/test/ActivityPayloadProjection.test.ts), and today it is invisible.

                              Proposed solution

                              One right-panel surface with two deliverables. Deliverable A (architecture map) is the foundation and ships alone. Deliverable B (coverage overlay) paints it. They share the panel, the mount point, and the node identity scheme, which is why they are one issue — but see "can be split" at the bottom.


                              0. Panel plumbing: add ONE generic fork surface kind, not two feature kinds

                              The right panel is a per-thread surface workspace, and the surface union is a local zustand type in the web app, not a contracts schema (apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;). Nothing in packages/contracts references it. So this is a pure apps/web change — but unavoidably a three-file upstream edit.

                              Add exactly one kind, "t3x", whose descriptor carries its own label, icon name and sub-kind. Every future fork panel then costs zero additional upstream churn:

                              // apps/web/src/rightPanelStore.ts — added to RightPanelSurface|{ id: `t3x:${string}`; kind: "t3x"; resourceId: string; label: string; icon: string}

                              Upstream files touched, and exactly what changes:

                              FileChange
                              apps/web/src/rightPanelStore.ts:17-40one entry in RIGHT_PANEL_KINDS, one union member, add "t3x" to the Exclude<> in singletonSurface (:85-96) since it is multi-instance
                              apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle gains case "t3x": return surface.label; — one line, forever
                              apps/web/src/components/RightPanelTabs.tsx:237SurfaceIcon gains case "t3x": mapping surface.icon through a fork-owned lookup. Note: SurfaceIcon has no declared return type, so a missing case renders nothing silently — the compiler will not catch it. surfaceTitle (declared : string) and singletonSurface (declared : RightPanelSurface) will.
                              apps/web/src/components/RightPanelTabs.tsx:98-131 and :446-473two separate hard-coded lists — the empty-state const actions = [...] card grid and the + menu's four <SurfaceMenuItem> entries. Both need an entry or the tab can only be opened programmatically.
                              apps/web/src/components/ChatView.tsx:5831-5881one branch in the rightPanelContent ternary chain, delegating to a lazy fork-owned dispatcher
                              // apps/web/src/components/ChatView.tsx — alongside :396-397constT3xSurfacePanel=lazy(()=>import("../t3x/rightPanel/T3xSurfacePanel"));// ...in the chain at :5831) : activeRightPanelSurface?.kind==="t3x" ? (<T3xSurfacePanelsurface={activeRightPanelSurface}threadRef={activeThreadRef}/>

                              Hard constraint: do NOT add props to RightPanelTabsProps.RightPanelTabs is rendered twice — mode="inline" at ChatView.tsx:6299 and mode="sheet" at :6326 — so every new prop is threaded through both call sites in the fork's hottest file. Put label/icon/availability inside the surface descriptor instead.

                              Everything else lives under apps/web/src/t3x/rightPanel/ (churn 0). Copy DiffPanel / FilePreviewPanel (ChatView.tsx:396-397, lazy() + Effect atoms), notPreviewPanel — the browser tab is an Electron <webview> gated on window.desktopBridge.preview (apps/web/src/previewStateStore.ts:451-454) and would make this panel invisible in the hosted web app.

                              Persistence needs no version bump: the migration passes unknown kinds through (rightPanelStore.ts:187 special-cases only terminal and file; RIGHT_PANEL_STORAGE_VERSION = 7 at :43). The upstream store test (apps/web/src/rightPanelStore.test.ts, 449 lines) uses literal fixtures and never asserts over RIGHT_PANEL_KINDS, so it does not need editing.

                              Skip the keybinding in v1. Command ids are a closed union in contracts (packages/contracts/src/keybindings.ts:50-73, consumed by Schema.Literals at :86) — a shortcut would add contracts + web keybinding wiring + CommandPalette.tsx (churn 17). The + menu entry is enough.


                              Deliverable A — architecture map (t3x:architecture)

                              Where the graph comes from

                              Tier 0, v1, zero dependencies: the workspace package graph.pnpm-workspace.yaml declares apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts. Every edge is already encoded by the workspace: protocol in each package.json. Verified edges from this checkout:

                              • t3 (apps/server) → @t3tools/contracts, @t3tools/shared, @t3tools/tailscale, @t3tools/web, effect-acp, effect-codex-app-server
                              • @t3tools/web@t3tools/client-runtime, @t3tools/contracts, @t3tools/shared
                              • @t3tools/shared@t3tools/contracts
                              • @t3tools/contracts, effect-acp, effect-codex-app-server → leaves

                              Roughly 15 nodes and ~30 edges. Derivable in a few dozen lines.

                              Tier 0b, free bonus: the task graph.apps/server/vite.config.ts has run: { tasks: { build: { dependsOn: ["@t3tools/web#build"] } } } and apps/desktop/vite.config.ts has dependsOn: ["t3#build"]. Render as a second, toggleable edge set (build order vs. import).

                              Tier 1, v1.5: directory clusters inside a package — e.g. apps/server/src/{orchestration,provider,mcp,project,textGeneration,t3x}. Pure filesystem grouping, no parsing, and it is the level at which coverage actually reads well.

                              Tier 2, explicitly OUT of scope: the file-level import graph. 1553 source files, and there is no madge / dependency-cruiser / nx / turbo in this repo (grep -rn 'madge|dependency-cruiser|@nx|depcruise|turbo' package.json pnpm-lock.yaml → no matches; no turbo.json). That needs either a new dependency or a rolldown metafile pass and belongs in a follow-up.

                              Rejected: the Effect layer graph. There is no static source of truth for it — Layer.mergeAll / Layer.provide composition would have to be executed or AST-walked, and the result would be a diagram of apps/server only.

                              How the data is fetched (no server work in v1)

                              Read pnpm-workspace.yaml plus each package's package.jsonclient-side through the existing RPCs: projectsListEntries and projectsReadFile (packages/contracts/src/rpc.ts:170-171, WsProjectsReadFileRpc at :457). That is ~15 small reads. Zero contracts change, zero apps/server/src/server.ts change (churn 29).

                              Escape hatch if the walk turns out to be too chatty or needs to run server-side: a fork-owned raw HTTP route under /api/t3x/architecture, mounted through T3xRoutesLive (apps/server/src/t3x/index.ts:100). The template is apps/server/src/t3x/webPush/http.ts:1-13, which documents exactly why raw routes beat WS-RPC here ("an RPC would force edits to @t3tools/contracts + ws.ts + its scope map"), and the web-side caller template is apps/web/src/t3x/AutoResumeOverlay.tsx:14 (const AUTO_RESUME_PATH = "/api/t3x/auto-resume";).

                              How it renders

                              Hand-rolled SVG, no new dependency. ~15 nodes in a DAG is a layered (Sugiyama-lite) layout: topo-sort into ranks by longest path from the leaves, order within a rank to reduce crossings, draw boxes and orthogonal edges. A few hundred lines in apps/web/src/t3x/rightPanel/architecture/layout.ts. Node click → set the panel's selection; node double-click → open t3x:architecture scoped to that package (Tier 1 drill-down).

                              This deliberately avoids pnpm-lock.yaml (churn 64, the fork's #2 risk row at 5312) and apps/web/package.json (churn 19). See Alternatives for the Mermaid route and why it is a poor fit for an interactive, coverage-tinted map.


                              Deliverable B — coverage overlay (t3x:tests, and tinting on the map)

                              B0. The metric, stated precisely

                              "% of the application covered" is fuzzy and this issue commits to a definition rather than shipping a number that flatters us.

                              Primary metric — merged statement coverage over an explicit include set:

                              covered_statements / total_statements, summed as raw counts across every workspace package's coverage-summary.json, over the include set below. Never an average of per-package percentages.

                              Proposed include/exclude, which is a product decision and should be reviewed, not inherited:

                              include: apps/{server,web,mobile,desktop}/src/**/*.{ts,tsx}
                              packages/*/src/**/*.{ts,tsx}
                              infra/relay/src/**/*.ts
                              exclude: **/*.test.{ts,tsx}
                              **/_generated/** (e.g. packages/effect-codex-app-server/src/_generated/schema.gen.ts)
                              **/vendor/** (apps/web/src/lib/vendor/, apps/web/src/terminal/ghostty/vendor/)
                              **/routeTree.gen.ts
                              apps/marketing/** (Astro; not under vitest)
                              scripts/**
                              

                              The panel must render the denominator next to the percentage ("62% of 41,208 statements across 1,204 files"), because the percentage alone is meaningless without the include set. It must also list which packages contributed a snapshot and which did not.

                              Honesty trap that must not be papered over: Vitest 4 removed coverage.all. vp test --help --coverage documents --coverage.include as "Files included in coverage as glob patterns. ... By default only files covered by tests are included." Without an explicit include, a module no test ever imports is absent from the denominator, not scored 0% — the reported number would be a lie. The include glob is the single most important knob in this feature.

                              Fallback metric — module test-adjacency (available today, no test run, no dependency):

                              Fraction of source modules that have a sibling *.test.ts(x) file. Measured in this checkout right now: 681 / 1553 = 43.9%.

                              This is a filesystem scan that runs in milliseconds and is defensible here because co-location is near-universal (738 test files, exactly one outside its module's directory). It must be labelled "modules with a sibling test file", never "coverage". It is what the panel shows when no coverage snapshot exists or the snapshot is stale — and it is what makes Deliverable A useful on day one before the provider question is settled.

                              B1. Producing coverage

                              Hard prerequisite: pnpm add -Dw @vitest/coverage-v8@4.1.9 — the version must match vitest exactly (declared as '@vitest/coverage-v8': 4.1.9 in the peer block at pnpm-lock.yaml:10174-10175).

                              Because the root run is broken (see Problem), collect per package and merge:

                              vp run -r --concurrency-limit 2 test \
                              --coverage --coverage.provider=v8 \
                              --coverage.include='src/**/*.{ts,tsx}' --coverage.exclude='**/*.test.*' \
                              --coverage.reporter=json --coverage.reporter=json-summary \
                              --coverage.reportOnFailure \
                              --testTimeout=120000 --hookTimeout=120000
                              

                              Extra args forward through vp run <task> -- <args> — the fork's own CI already relies on this (.github/workflows/t3x-ci.yml:75: vp run test --testTimeout=120000 --hookTimeout=120000). This produces <pkg>/coverage/coverage-summary.json (per-file plus a total object with lines/statements/functions/branches each {total, covered, skipped, pct}) and <pkg>/coverage/coverage-final.json (Istanbul statementMap/s/fnMap/f/branchMap/b).

                              Merging: either sum counts from the summaries, or merge the coverage-final.json files with istanbul-lib-coverage's createCoverageMap().merge() and re-summarise (this also gives per-file data for a heatmap). Paths in these files are absolute and must be normalised to repo-relative before merging.

                              Also required: add coverage/ to .gitignore — it is absent today, so any coverage run immediately dirties the working tree.

                              B2. Triggering the run from inside the app

                              This is a background job, never computed on panel open. The full suite already needs --concurrency-limit 2 plus raised timeouts on this machine, and v8 adds instrumentation and source-map remapping on top.

                              v1: the panel shows a copyable command plus a "last snapshot: 3 days ago" staleness badge, and reads the artifacts off disk via projectsReadFile. Optionally add one entry to t3.jsonscripts[] ({ "name": "Test Coverage", "command": "vp run -r ... --coverage ..." , "icon": "..." }) — one line, and t3.json + .gitignore have combined churn 4 — which makes it runnable from T3's existing project-script runner. Note that project scripts execute by writing the command into a pty terminal (apps/server/src/project/ProjectSetupScriptRunner.ts), so output is raw terminal bytes; the structured result comes from reading the JSON artifacts afterwards, not from the run. Do not expose apps/server/src/processRunner.ts over RPC for this — the artifact-on-disk route is far cheaper.

                              B3. The overlay itself

                              The two halves compose because coverage-summary.json is per-file and the graph is per-package: aggregate file coverage up to its owning workspace package, then tint each node. Concretely:

                              • Node fill = coverage bucket (a diverging scale, accessible in light and dark; keep it colour-blind safe and pair it with a numeric label — do not rely on hue alone).
                              • Node border weight = inbound dependent count — this is the "weak link" the user actually wants. packages/contracts at low coverage with 5 inbound dependents must look categorically worse than apps/marketing at the same percentage.
                              • An explicit risk list below the map: nodes sorted by inboundDependents × (1 - coverage), which is the shippable expression of "weak links that have not been thoroughly tested".
                              • Missing snapshot → node renders with the test-adjacency proxy and a distinct hatched/neutral style, plus a legend entry saying so. Degrading silently to a fake number is the failure mode to avoid.

                              Suggested file layout (all fork-owned, churn 0)

                              apps/web/src/t3x/rightPanel/
                              T3xSurfacePanel.tsx # dispatches on surface.resourceId; the ONLY thing ChatView imports
                              icons.ts # icon-name -> lucide component, read by SurfaceIcon's one new case
                              architecture/
                              graph.ts # pnpm-workspace.yaml + package.json -> {nodes, edges}
                              layout.ts # layered DAG layout, dependency-free
                              ArchitectureMap.tsx # SVG renderer + selection + coverage tint
                              coverage/
                              snapshot.ts # read + merge coverage-summary.json across packages
                              adjacency.ts # sibling-test-file proxy
                              CoverageOverlay.tsx # legend, totals, denominator, staleness badge, risk list
                              

                              Why this matters

                              For the human. The repo has 1553 source modules and 738 test files. Right now the only way to answer "is the thing the agent is about to rewrite tested, and what breaks if it is wrong" is to grep. A map with a coverage tint and a dependents × (1 - coverage) risk list answers it in one glance, in the panel that is already open next to the thread.

                              For the agent workflow specifically. T3 Code's whole premise is that models write most of the code. The failure mode of that premise is confident edits to load-bearing, untested modules — and this fork already runs unattended work (apps/server/src/t3x/autoResume/Reactor.ts dispatches turns with no human in the loop). A visible, honest weak-link list is the cheapest guard against that: it turns "the tests pass" into "the tests pass, and here is what they never touched."

                              It bootstraps a missing capability, not just a view. There is currently no coverage number in this repo at all, in CI or locally. Deliverable B lands the provider, the per-package-then-merge recipe, and the include-set decision — all of which are reusable by CI, by the seam ledger discipline, and by any future quality gate, independent of whether anyone ever opens the panel.

                              It pays for itself across future fork panels. The generic "t3x" surface kind means every subsequent fork-owned right-panel feature costs zero additional upstream churn. That is the same aggregator discipline apps/server/src/t3x/index.ts already enforces server-side, applied to the web right panel for the first time.

                              It is the safest of the fork's queued features to build. Duplicate research across all 1615 upstream issues and all 21 fork issues found no duplicate for either half (see References). Unlike the scheduling and orchestration ideas, nothing upstream is mid-flight in this area, so there is no "parallel paths" hazard where a fork path silently bypasses new upstream guards.

                              Smallest useful scope

                              Smallest genuinely shippable first pass = Deliverable A + the free proxy metric. No new dependency, no lockfile change, no server change.

                              1. Add the "t3x" surface kind — the three upstream files listed in the proposal, plus both affordance lists in RightPanelTabs.tsx.
                              2. apps/web/src/t3x/rightPanel/T3xSurfacePanel.tsx dispatching on resourceId, lazy-imported from ChatView.tsx.
                              3. t3x:architecture renders the package-level DAG (~15 nodes, ~30 edges) from pnpm-workspace.yaml + workspace: protocol deps, read client-side over the existing projectsListEntries / projectsReadFile RPCs, laid out and drawn as hand-rolled SVG.
                              4. Nodes tinted by the module test-adjacency proxy (sibling *.test.ts(x) scan — 681/1553 today), labelled as exactly that, plus the dependents × (1 - adjacency) risk list.
                              5. Update docs/t3x/SEAMS.md in the same commit: header totals + the new rows, and document the "t3x" aggregator as the reason no further right-panel rows will follow.

                              That is a complete, useful, honest feature on its own.

                              Second pass (Deliverable B), gated on the smoke test in Risks: add @vitest/coverage-v8@4.1.9, add coverage/ to .gitignore, land the per-package collect + merge script and the include/exclude decision, and swap the proxy tint for real merged statement coverage with the denominator shown and a staleness badge. Optionally add the t3.json script entry.

                              Explicitly out of scope for both passes: file-level import graphs (needs a new static-analysis dependency), the Effect layer graph, a keybinding or command-palette entry, mobile (apps/mobile has its own unrelated ThreadInspectorMode = "route" | "git" | "files" inspector at apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4 and shares nothing with the web right panel), and any CI gate or coverage threshold.

                              On splitting: the two deliverables share the surface kind, the mount point, the node identity scheme and the risk-list UI, so splitting them means the diagram ships as a decorative graph and the coverage work ships as a number with no context — two half-features. But if maintainers prefer, the natural cut is exactly the pass boundary above: pass 1 as "add the t3x right-panel surface + architecture map", pass 2 as "coverage snapshot pipeline + overlay". Pass 2 depends on pass 1; pass 1 does not depend on pass 2.

                              Alternatives considered

                              Put it on a project-level route instead of the right panel. Coverage and architecture are properties of the workspace, not the thread, and every right-panel surface is keyed by ScopedThreadRef and persisted per thread (rightPanelStore.ts:52byThreadKey). A route would dodge the RightPanelTabs / ChatView seams entirely. Rejected because the user's explicit requirement is "view it on the same right hand screen" while a turn runs — the value is adjacency to the conversation. Worth revisiting if the panel proves to be the wrong home; the fork-owned modules would port unchanged.

                              Host it in settings.diagnostics.tsx / DiagnosticsSettingsPanel. Those already exist (apps/web/src/routes/settings.diagnostics.tsx) and would be the lowest-seam host of all — but settings is where you go to check something once, not where you keep a map open while reviewing a diff.

                              Render as Mermaid instead of hand-rolled SVG. Upstream pingdotgg#4571 requests Mermaid in the markdown renderer, with PR pingdotgg#4989 open. Cheaper to author, but: (a) it is not landed, so this feature would block on someone else's PR; (b) Mermaid produces a static image — no node selection, no coverage tinting, no drill-down, no dependent-count borders, which is the entire point of Deliverable B; (c) it adds mermaid to apps/web/package.json (churn 19) and pnpm-lock.yaml (churn 64 — the fork's #2 risk row). If a static export is later wanted, emitting Mermaid text from the same graph.ts is a trivial add-on.

                              Use reactflow / elkjs / dagre for layout. Genuinely better for large graphs. Not worth a lockfile-scale conflict for ~15 nodes; revisit only if Tier 2 (file-level import graph) is ever built, where hand-rolled layout would stop being viable.

                              Have the model generate the diagram. Ask the agent to emit Mermaid describing the architecture. Zero infrastructure, but non-deterministic, unverifiable, immediately stale, and impossible to join to per-file coverage data. Rejected.

                              Adopt madge / dependency-cruiser for a real import graph. This is the right tool for Tier 2 and should be reconsidered when file-level granularity is actually needed. For v1 the workspace graph is free and already correct.

                              Commit a coverage-summary.json refreshed by CI, and have the UI just read it. Makes the feature instant and offline, and sidesteps the "who runs the suite" problem entirely. Rejected for v1 because it adds a churny generated artifact to a fork that already fights lockfile-scale merge conflicts — but it is a very reasonable v2 if local runs prove too slow.

                              Wait for upstream pingdotgg#1377 (internal panel host / panelRegistry.ts). That issue proposes exactly the registration mechanism this feature wants, and scopes itself to the right-panel slot first. It is open and unimplemented. If it lands, the "t3x" kind collapses into a registry entry and the three upstream edits go away — so this proposal is deliberately shaped to be a one-line migration to it. Waiting is not viable now since there is no timeline.

                              Do nothing and rely on vp test run --coverage in a terminal. Does not work today (no provider installed, root run broken), and even once fixed it produces a flat per-file table with no structural context — which is precisely the gap this issue is about.

                              Risks or tradeoffs

                              Seam cost (the main one)

                              Per docs/t3x/SEAMS.md, risk = (fork lines changed) × (upstream commits touching that file in the 60 days before the merge-base). Churn re-measured today against merge-base 64bf01619:

                              Upstream filechurnest. fork Δest. added risk
                              apps/web/src/components/ChatView.tsx63~10 lines (1 lazy import + 1 ternary branch)~630
                              apps/web/src/components/RightPanelTabs.tsx12~30 lines (2 switch cases + 2 affordance entries)~360
                              apps/web/src/rightPanelStore.ts9~12 lines~108
                              .gitignore / t3.json (deliverable B only)4 combined~2 lines~8
                              pnpm-lock.yaml (deliverable B only)64provider addadds to the existing 5312 row

                              Two things must be said plainly:

                              1. ChatView.tsx is already the fork's worst row at risk 11466 (SEAMS.md:41, +181/-1 × churn 63). This adds to it. The mitigation is that the branch is a single delegation to a lazy fork-owned component, so the conflict when upstream rewrites the ternary chain is a one-line re-application, not a merge of feature logic.
                              2. SEAMS.md:21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." The ledger is at 34 rows. This feature adds rows 35 and 36 (rightPanelStore.ts, RightPanelTabs.tsx; ChatView.tsx already has a row). Filing this issue does not override that tripwire — the implementer must either re-isolate something else first, or get explicit sign-off, and must update SEAMS.md header totals plus both new rows in the same commit (the self-reference rule at SEAMS.md:24-28; that rule has already been violated once). The one-generic-kind design is the concession: it is the difference between "rows 35-36, once" and "two more rows per future fork panel".

                              Upstream-conflict hazard

                              The right-panel area is hot: 17 upstream commits touched rightPanelStore.ts or RightPanelTabs.tsx in the 60-day window, several restructuring the surface model itself (3a5ec9464 plan surface in the inline right panel pingdotgg#3118, e56bb200f bulk close + tab context menu pingdotgg#3116, de8bdc10f workspace file browser and preview panel pingdotgg#3087). Check open upstream PRs against the surface union before writing code — designing against a shape that is mid-rewrite is how this becomes a recurring sync conflict. Upstream pingdotgg#1377 is the specific one to watch.

                              Honest metric risks

                              • The headline number is entirely determined by the include/exclude globs, which are a product decision (see B0). Getting them wrong produces a confidently wrong number — worse than no number.
                              • Vitest 4 has no coverage.all. Forgetting --coverage.include silently drops untested modules from the denominator. This is the single most likely way this feature ships a lie.
                              • The test-adjacency proxy is a convention check, not coverage. A file with a sibling test that asserts nothing counts as covered. The fork has already been bitten by exactly this class of thing — a cleanly-merged fork test file that was semantically dead. Label it precisely and never sum it with real coverage.
                              • Aggregating file coverage up to package nodes hides intra-package variance. apps/server at 70% could be 95% in orchestration/ and 5% in provider/. Tier 1 drill-down (v1.5) is the mitigation; until then the panel should say so.

                              Performance / operational

                              • A full coverage run is a background job. The suite already needs --concurrency-limit 2 plus raised hook/test timeouts on this 16GB machine; v8 instrumentation and source-map remapping add more. Never compute on panel open; persist a snapshot and show staleness.
                              • Coverage artifacts dirty the working tree until .gitignore is updated.

                              UNVERIFIED — must be settled before Deliverable B is designed further

                              1. Will pnpm actually wire a root-level @vitest/coverage-v8 as the peer of the vitest instance that lives under vite-plus? vitest is transitive (root → vite-plusvitest@4.1.9), and under pnpm's strict layout an optional peer may not resolve from vitest's own node_modules. Experiment:pnpm add -Dw @vitest/coverage-v8@4.1.9, then run vp test run --coverage in one small package (packages/tailscale, 1 test file) and confirm a coverage/ directory appears. If this fails, Deliverable B is blocked and needs a different provider strategy. Do this before any other B work.
                              2. Does @vitest/coverage-v8@4.1.9 clear the repo's minimumReleaseAge gate, or does it need adding to minimumReleaseAgeExclude in pnpm-workspace.yaml? Falls out of experiment 1.
                              3. Does coverage work in apps/mobile? It has no vite.config.ts, inherits the root node-environment config, and holds 99 React Native test files. v8 source-map remapping through the Expo/Babel chain is unverified and this is the likeliest package to produce garbage file paths or fail outright. Experiment: run the per-package command in apps/mobile alone and inspect the paths in coverage-summary.json. If it fails, exclude mobile from the denominator and say so in the panel.
                              4. What is the wall-clock cost of a full instrumented run on this machine? Nobody has measured even the uninstrumented baseline. This determines whether the run is a project script, a scheduled job, or CI-only. Experiment:time vp run -r --concurrency-limit 2 test first, then the same with --coverage.
                              5. Do the per-package config divergences break a merged view?apps/server pins fileParallelism: false with 120s timeouts, apps/web uses a named unit project with 15s timeouts (and its own test script already passes --project unit), root pins 60s. Forwarding coverage flags through vp run -r across those is plausible but untested.
                              6. Layout quality of hand-rolled SVG at ~15 nodes with ~30 edges is asserted, not demonstrated. Experiment: build graph.ts first, dump the node/edge counts and a ranked layout to a test snapshot, and eyeball it before committing to the renderer. If crossings are unmanageable, that is the moment to reconsider elkjs.

                              Examples or references

                              Duplicate / related issues

                              A sweep of all 1,615 upstream (pingdotgg/t3code) issues, open and closed — dumped locally and grepped across ~80 term variants, plus body-level gh search issues and a gh search prs pass — and all 21 radroid/t3code fork issues found no duplicate for either half of this feature. Terms tried included: test coverage, coverage report, architecture diagram, codebase map, dependency graph, visualize codebase, code map panel, repo overview, istanbul, vitest coverage, code health, hotspot, untested, test panel, architecture view, system diagram, module graph. Upstream Discussions are disabled, so issues are the complete search surface.

                              Adjacent, not duplicate:

                              Repo evidence (all verified in this checkout at main, merge-base 64bf01619)

                              Panel plumbing:

                              • apps/web/src/rightPanelStore.ts:17export const RIGHT_PANEL_KINDS = ["plan", "diff", "files", "file", "preview", "terminal"] as const;
                              • apps/web/src/rightPanelStore.ts:20-40 — the RightPanelSurface union; :43RIGHT_PANEL_STORAGE_VERSION = 7; :52byThreadKey; :85-96singletonSurface; :187 migration passthrough
                              • apps/web/src/components/RightPanelTabs.tsx:98-131 — hard-coded empty-state const actions = [...] (Browser / Terminal / Files / Diff)
                              • apps/web/src/components/RightPanelTabs.tsx:189surfaceTitle (declared : string), :237SurfaceIcon (no return annotation), :446-473 the + menu's four <SurfaceMenuItem> entries
                              • apps/web/src/components/ChatView.tsx:396-397lazy() template for DiffPanel / FilePreviewPanel
                              • apps/web/src/components/ChatView.tsx:5831-5881 — the rightPanelContent ternary chain; :6299 (mode="inline") and :6326 (mode="sheet") — the two RightPanelTabs call sites
                              • apps/web/src/previewStateStore.ts:451-454isPreviewSupportedInRuntime() gating the browser tab on window.desktopBridge.preview (why PreviewPanel is the wrong template)
                              • packages/contracts/src/keybindings.ts:50-73, :86 — the closed keybinding-command union (why v1 skips a shortcut)
                              • apps/mobile/src/features/threads/thread-inspector-content-stack.tsx:4ThreadInspectorMode = "route" | "git" | "files" (mobile shares nothing)

                              Fork seam:

                              • docs/t3x/SEAMS.md:5 — 34 rows, +1616/-187; :19-21 the "before adding row 35" tripwire; :24-28 the self-reference rule; :31-33 the risk formula; :41 the ChatView.tsx row at risk 11466
                              • apps/server/src/t3x/index.ts:71, :100T3xLayerLive / T3xRoutesLive aggregators
                              • apps/server/src/t3x/webPush/http.ts:1-13 — raw /api/t3x/* route template and its rationale
                              • apps/web/src/t3x/AutoResumeOverlay.tsx:14 — web-side caller for a fork route
                              • Churn, measured today (git log --oneline --since='60 days ago' 64bf01619 -- <path> | wc -l): rightPanelStore.ts9, RightPanelTabs.tsx12, ChatView.tsx63, apps/web/package.json19, pnpm-lock.yaml64, t3.json + .gitignore4 combined

                              Data sources:

                              • packages/contracts/src/rpc.ts:170-171projectsListEntries / projectsReadFile; :457WsProjectsReadFileRpc
                              • pnpm-workspace.yamlpackages: apps/*, infra/*, oxlint-plugin-t3code, packages/*, scripts
                              • workspace: edges verified: t3 → contracts, shared, tailscale, web, effect-acp, effect-codex-app-server · @t3tools/web → client-runtime, contracts, shared · @t3tools/shared → contracts
                              • apps/server/vite.config.tsrun.tasks.build.dependsOn: ["@t3tools/web#build"]; apps/desktop/vite.config.tsdependsOn: ["t3#build"]
                              • docs/internals/workspace-layout.md — prose ground truth to diff a generated diagram against

                              Coverage / testing:

                              • pnpm-lock.yaml:10163vitest@4.1.9:; :10174-10175'@vitest/coverage-istanbul': 4.1.9 / '@vitest/coverage-v8': 4.1.9, both optional: true
                              • Installed store contains only @types+istanbul-lib-coverage@2.0.6 — no provider
                              • grep -rn coverage vite.config.ts apps/web/vite.config.ts apps/server/vite.config.ts package.json → no output
                              • node_modules/.bin/vp test list at repo root → fails on apps/web/src/terminal/ghostty/vendor/ghostty-vt.wasm?inline (Plugin: vite:import-analysis) — re-run this before designing any single-process coverage run
                              • vp test --help --coverage--coverage.include documented as "By default only files covered by tests are included"; no --coverage.all in 4.1.9
                              • .github/workflows/t3x-ci.yml:75vp run test --testTimeout=120000 --hookTimeout=120000 (arg-forwarding precedent)
                              • Root package.json"test": "vp run -r test"; apps/web/package.json"test": "vp test run --passWithNoTests --project unit"; apps/server/vite.config.tsfileParallelism: false, 120s timeouts
                              • Counts measured today: 738 test files, 1553 source files (excl. tests and routeTree.gen.ts), 681 source modules with a sibling test file = 43.9%; exactly one non-co-located test (apps/server/test/ActivityPayloadProjection.test.ts)
                              • grep -c "mermaid\|reactflow\|cytoscape\|dagre\|elkjs" pnpm-lock.yaml0
                              • No madge / dependency-cruiser / @nx / turbo; no turbo.json
                              • .gitignore has no coverage entry
                              • t3.jsonscripts[] (currently one "Setup Worktree" entry); schema at packages/contracts/src/t3ProjectFile.ts; runner at apps/server/src/project/ProjectSetupScriptRunner.ts (pty-based, unstructured output)
                              • apps/server/src/processRunner.ts — structured ProcessRunOutput, server-internal only, deliberately not used here

                              Duplicate search performed before filing

                              Searched both repos exhaustively, not sampled.

                              Upstream (pingdotgg/t3code): dumped all issues via gh issue list --repo pingdotgg/t3code --state all --limit 6000 → 1,615 rows, matching gh api search/issues -f q='repo:pingdotgg/t3code is:issue' --jq .total_count → 1615. Grepped the title corpus for ~80 term variants and ran body-level gh search issues per term: "test coverage", "coverage report", "code coverage", "architecture diagram", "codebase map", "dependency graph", "visualize codebase", "code map panel", "repo overview", "istanbul", "vitest coverage", "code health", "hotspot", "untested", "test panel", "architecture view", "system diagram", "module graph", plus regex sweeps for coverage|diagram|architect|graph|visuali[sz]e|mermaid|dependency|call tree|code map. Also ran a gh search prs sweep.

                              Result: no duplicate. The regex sweep returned only four adjacent hits, all checked and none matching: pingdotgg#4571 (Mermaid render support in markdown — a render primitive, not a panel; PR pingdotgg#4989 open), pingdotgg#4808 (context-window visualiser — different subject), and pingdotgg#4564 / pingdotgg#671 (client/server architecture proposals, i.e. prose about how T3 should be built, not architecture visualisation).

                              The one genuinely related issue is pingdotgg#1377 "Internal panel host for first-party panel composition" (OPEN, unimplemented) — it is the registration mechanism a new right-panel tab would ideally use, not the feature itself. This issue explicitly designs around it and notes the migration path if it lands.

                              Fork (radroid/t3code):gh issue list --repo radroid/t3code --state all → 21 issues; searched "coverage", "architecture", "diagram", "test", "graph", "panel", "visual". No match. The nearest fork issues are #38 (Loop Watch) and #39 (auto-resume cancellation), which share only the apps/server/src/t3x/ aggregator and are otherwise unrelated.

                              Search surface is complete: upstream Discussions are disabled (gh api graphqlhasDiscussionsEnabled: false, discussions.totalCount: 0), so issues are the only venue. Caveat: upstream pull requests were swept by keyword but not exhaustively enumerated, and fork PRs were not checked (the fork has few PRs and none in this area).

                              Contribution

                              • I would be open to helping implement this.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                enhancementNew feature or request

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions