An aborted incremental run preserves the previous index by design, but
the response said generic 'error — check repo_path exists', sending
people to debug a path that was fine. The abort and persist-failure
codes move to the public pipeline header (the contract always said
callers may distinguish them) and index_repository now answers with
what actually happened: aborted_previous_preserved (retry; the old
index still serves) vs persist_failed (disk/permissions) vs the
generic failure.
The incremental test helper goes fail-closed on error responses,
surfacing the real cause once at the source — a transient abort used
to slip past the non-NULL assert and cascade into dozens of mysterious
count failures downstream (the 99-test Windows pile-up).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Names as plain text with their evidence line (commits to this file this
year, repo-wide breadth, last touched when the retained commits prove
it), honest empty and no-history states, en+zh from birth. The
question index stops claiming the view is planned: F13's remaining gap
is the region-level rollup — and, as ever, never a leaderboard.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Wiki entries and question families carry co-located zh content
(sentence, why, caps, not-covered, hints, gaps — 24 entries, 18
questions) with per-field English fallback; metric terms stay
English-canonical with a zh gloss in the panel header, so the zh UI
keeps teaching the tokens users meet in code and issues. The impact,
history, and observed surfaces move their remaining hardcoded strings
into the message catalog (en+zh, same interpolation shapes). zh
sentences are budget-tested at ≤45 characters like their English
counterparts at ≤30 words; the refusal rationale of the four
refused-metric pages is translated at full bluntness.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
/api/who answers the most-sought information type in the developer-
questions literature for one file, from the churn scan the dashboard
already pays for: people with recorded history here, each with their
evidence — commits in this file, repo-wide breadth, and last-touched
where the retained newest commits prove it (absent otherwise, stated
honestly). Deliberate constraints hold: no cross-person performance
framing, no bare percentages, capped list with the distinct-author
denominator.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three test families leaned on ingest_traces being a discard-stub:
- the daemon-runtime usability probe sent traces:[] and grepped for the
stub's traces_received; it now sends a valid minimal ingest for the
seeded project (the daemon requires the project argument) and checks
the honest pairs_unmatched report
- cypher_wide_return_projection_bounded forked the sanitized runner for
crash isolation; a fork that then does real store setup is
byte-fragile on macOS — a 1.5KB schema addition got the child killed
before the query ran (bisected: any equal growth flips it, even with
a 1-column query). The parse-time bound is the contract; assert it
in-process. A regression still fails loudly, and the test now runs
on Windows too instead of skipping
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Both predispatch-cancel tests probed the follow-up request with
ingest_traces traces:[] and expected the stub's 'accepted'; empty
traces are a tool error under the real contract. The outer-scope test
now probes with a valid ingest (unresolved names store nothing); the
raw-index test — where no project exists by design — probes with the
project-free list_projects. Clean means what it always meant: the next
request runs unpoisoned by the cancel.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Trace hops and flow steps that actually fired render '▶ observed ×N'
with the run label and date; a fully-observed trace says so in one
line, and the footer states the honesty rule: unmarked hops are
possible, not dead — runs only cover what they exercised. The wiki
gains the observed entry (first-class, with its caps), and F5's gap
narrows to what is still missing (automatic capture).
Live-smoke fix on the C side: resolve_store hands handlers a
query-only connection, so ingest writes silently no-op'd against real
project stores — ingest_traces now takes the dedicated read-write
connection (open_store_for_write, né open_adr_store_for_write), whose
open also creates the observed tables in pre-existing databases.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The stub counted the traces array, answered 'accepted', and discarded
everything. Now: traces are call paths of qualified names (adjacent =
caller→callee; bare {caller, callee} pairs still accepted), stored in
observed_calls/observed_paths — SEPARATE from the graph tables and
keyed by qualified name, because node ids are reassigned on every
reindex and id-keyed observation would silently rot. Counts accumulate
per run label; whole runs age out together past the 200k-pair cap
(oldest first, deterministic under the one-second timestamp resolution
via rowid tiebreaks — eviction is never a timing lottery). The response
is honest about resolution: only pairs whose both endpoints are indexed
get stored, and up to ten unresolved names come back so producers can
fix their naming instead of silently losing data.
Atlas joins observation at read time: trace and flow-detail hops that
actually ran gain observed {count, label, last_seen}; absence adds
nothing — an unobserved hop is merely possible, never 'dead'.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cbm_mkdtemp copies the expanded path back into the caller's template;
on Windows that is %TEMP%\..., far longer than the literal. The two
literal-sized arrays (symbol-history, why) were stack overflows there —
caught by the real-Windows leg as an fopen failure. CBM_SZ_256 buffers,
like every other call site.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A worktree's .git is a pointer file into the primary repo's .git
directory, which lives outside the ..:/src mount — git inside the
container fails and every git-based contract (the line-ending guard)
dies with 'matched no files'. run.sh now detects the worktree case and
exports the primary git dir; the test-running services mount it read-
only at the same absolute path so the pointer resolves. Plain checkouts
keep a harmless no-op default mount.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The wildcard swallowed the longer path, answering symbol-history
requests with the symbol bundle's 'missing id or qn' error. Caught
live; route order now states the constraint.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The most-deferred developer question in the literature gets its evidence
surface: /api/symbol-history runs a bounded git log -L over the symbol's
line range (follows renames, 12 commits, on demand — one subprocess,
never automatic), and file history now carries the forge base URL
(git@/ssh remotes normalized to https). The symbol page grows a 'Why is
this here?' card: trace button, commit list with hashes and #123 refs
linked to the forge, honest empty/capped states, and a footnote that
titles are not fetched. The existing recent-commits list linkifies
through the same component. F7's gap narrows accordingly.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
/api/impact answers the strongest-demand developer question in the
information-needs literature: if I change this, what breaks and what
must I retest. A reverse CALLS adjacency joins the flow cache (built in
the same edge pass); a distance-labelled BFS from any symbol reports
reachers per hop, a region rollup, and — separately — the test
functions that reach the symbol, nearest first: the tests to run.
Honest caps (depth 10, 50k visits, both flagged) and a stated basis:
static CALLS only, so every count is a floor.
The symbol page grows an 'If you change this' card: headline sentence,
cumulative hop tiers, region chips, run-these-first test list with an
explicit warning when no test reaches the symbol, and the floor
footnote. Question family F3 flips to answered (7/8/3); the
blast-radius wiki entry now names its caps.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Twenty years of developer-information-needs research reduces to 18
question families; the Overview now opens with Atlas's scorecard against
them: 6 answered, 9 partial, 3 not yet — counts derived, never
hard-coded. Each row names the question, where to look, and for partial
and missing families exactly what's missing; rows with a home navigate
there. Status is glyph + word, color redundant. Chrome en+zh; the
question text itself awaits the content-i18n pass.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- every metric chip previews its wiki entry on dwell: term, tier, the
single-source sentence, click-for-more; pointer can enter the card,
Esc dismisses just the card, no auto-timeout; portaled + clamped
- tooltip delay is a display setting (150-800ms, persisted), read at
hover-start so changes apply to the next hover
- galaxy tooltips show the arithmetic already in the scene payload:
fan-in/out and lines per node; symbols, files, cohesion per region
- test setup restores jsdom's localStorage under Node's experimental
global, so storage persistence tests assert for real instead of
silently no-opping
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Evidence-audit fixes across the dashboard and friends:
- sparklines plot real time (indexed_at), break across gaps, and carry a
printed first→last delta; no line under 4 snapshots (a 2-point line is
a trend claim)
- the hero head cut is a Kneedle-style knee (unit square, smoothed, max
distance below the chord, clamped 3..15) — largest-relative-drop
collapsed to 'top 1' on power-law data
- hero rows show both axes (commits and complexity), caption carries the
denominator; 'Complexity mix' (né distribution) prints per-bin counts,
N, and its flag threshold
- Modules bars state their reference; Overview regions get a cohesion
strip with median tick; the Control gauge prints its cut-points with
non-color glyphs
- complexity bins live in one module (lib/complexity.ts); a test binds
the takeaway sentence's claims to the bin labels so they cannot drift
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The doc_url target: the hash opens the entry's panel over whatever view
the rest of the URL names, and hashchange keeps it live.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every number Atlas shows gets a wiki entry: one tooltip-sized sentence,
why it matters, how it is computed with named caps, what it deliberately
does not cover, where it appears, and its paired counter-metric. Four
'refused' entries document metrics Atlas will not show (Maintainability
Index, Halstead, coverage gates, quality grades) and why.
Entries live in one typed module (word-budget-enforced by test) and are
rendered by a docked WikiPanel with a navigation stack — terms inside the
panel are chips too, so concepts link without nested tooltips. Dotted-
underline MetricChips wire the Dashboard and Symbol surfaces in; section
headings ship en+zh.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A fixed 30 s startup budget races whatever else the host is doing;
under a parallel test wave a cold daemon loses through no fault of
its own and every client gives up with 'could not start within
30000 ms'. The env override (clamped to 1 s..10 min, malformed
values fall back to the default) lets loaded hosts widen the budget
without changing the default behavior anywhere else. Applied to the
MCP/CLI client bootstrap configs, the upgrade drain request, and
the client cohort-acquire deadline.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- Semantic zoom on the region scene: zooming INTO a region blob
opens it, guarded by the anti-annoyance set from the research
round - user wheel input within 600 ms only (programmatic fly-tos
never trigger), approach direction only, 1.5 s grace so backing
out doesn't bounce back in. The HUD hint says so.
- Flow steps carry the resolver confidence of their parent edge;
hops under 75% show a small warm approx marker with the number -
a misresolved hop now looks different from a certain one.
- Dashboard lists declare their caps honestly: 'top N of M' where
the payload names the population (callables, churned files),
plain 'top N' where it doesn't - never an invented M.
- FilterPanel All|None buttons became one tri-state master checkbox
(checked / unchecked / mixed dash, aria-checked, keyboard-native).
vitest 71/71, tsc clean, C suites 96/0, lint green.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- Landmark halos: the top-12 hubs carry a soft additive corona
(single Points layer, one sprite) so panning always has stable
anchors - the CodeCity landmark idea, kept sparse on purpose.
- Minimap gains a view marker: a ring at the camera's orbit target,
reported throttled (200 ms, movement-gated) from inside the
canvas, projected with the same bounds as the region dots.
- Constrained camera: orbit min/max distance follow the scene's
actual radius instead of fixed constants; scenes auto-frame on
load (the fixed default camera landed inside larger clouds) while
deep-linked nodes keep their focus fly-to.
- Minimap moved above the Prompt button (they overlapped).
vitest 71/71 incl. minimapProject bounds tests.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Atlas program added seven tabs of hardcoded English on top of an
en/zh UI. This translates the shell (tab labels, switcher strings)
so zh users get a coherent frame; panel-content i18n is a dedicated
follow-up pass, recorded.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- The Why view now sits directly under the symbol header, before the
flat caller/callee columns: the condition question is the primary
answer, the lists are reference (question-first IA per the
research synthesis).
- Cross-seam dots on connection rows invert into noise on utilities
called from everywhere; past half the rows the list now says it
once in the header ('79 of 83 cross regions') instead of dotting
nearly every row.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- /api/why entries carry candidates from the CALLS edge; the tree
shows a '1 of N targets' diamond chip on dynamic dispatch -
genuine resolver uncertainty made visible.
- Decision-table pivot on the trigger tree (>=3 guarded callers):
rows = callers, columns = distinct conditions, cells = when /
unless / none. The experimentally-winning format for
condition-combination lookup (Huysmans 2011).
- /api/flow&guards=1: flow-detail steps gain per-step guard chips,
computed from each step's parent call site - journeys read as
'then X, when ok && *cursor' instead of bare arrows.
- Number formatting unified to en-US (mixed browser locales
rendered 1.053 next to 1,053).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The condition→action abstraction (Pennington 1987: the mental model
with the highest error rates, and the one code text serves worst),
served on demand. Design per the research synthesis: thin caller-only
spine (thin slicing), closed question menu, guard text ON the edge
in words (information scent), glyphs always paired with text
(Reacher's 22% missed-icon failures), complexity-gated (Scanlan),
declared elision, both directions (Green), honest footer.
- /api/why?id|qn&dir=up|down: callers (or callees) with the
SYNTACTIC guard chain around each call site — enclosing
if/else/switch-case/loop/ternary/catch conditions, outermost
first, else-arm negation detected — extracted by parsing the few
caller files live with the embedded tree-sitter grammars from the
call-site line the CALLS edges already carry. Zero indexing cost;
works on every existing index. Per-request parse cache; 2 MB file
cap; unparseable sources reported as guards_unavailable, never
guessed.
- /api/trace&guards=1: A-to-B paths gain per-hop guard chips -
'reachable in 5 hops' becomes 'reachable when ...' (the exact gap
Reacher's users hit: they hand-derived conditionals).
- Symbol page: 'When does this run?' tree - guard chips (amber
unless / blue when), loop marker, unguarded honestly labeled,
per-caller upward expansion, direction toggle, single-line answer
when every call site is unguarded, footer stating guards are
syntactic and dispatch/events are invisible.
- Trace results in Flows render the same chips per hop.
Tests: nested-if chain outermost-first + else-arm negation + loop
flag + downward direction + callsite helper on a real temp-dir C
file (C); /api/why route wiring; formatGuard/chain + the
complexity gate (vitest). Trap fixed en route: the extension table
keys WITH the dot ('.c'), not 'c'.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The header's backdrop-filter creates a CSS stacking context, which
paints its entire subtree as ONE atomic layer. A child's z-index
cannot escape it: the body-level dim overlay (z-20) therefore sat
above the dropdown panel (z-30-inside-the-header) and dimmed the
panel together with the page - uniform darkening, zero separation.
Both the backdrop and the panel now portal to <body>, where the
paint order is explicit: backdrop z-20 dims everything, panel z-30
floats above it, anchored to the trigger button's rect (closes on
resize; Esc and backdrop-click close; click-away listener dropped -
the backdrop IS the click-away surface).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Review feedback: the dropdown blended into the page and offered no
way to search among many projects.
- Type-to-filter input at the top, autofocused on open; Enter picks
the first match, Esc closes.
- The panel is now a real layer: solid surface, stronger border,
deep shadow, and a viewport backdrop that dims the page. The
backdrop is portaled to <body> because the header's
backdrop-filter makes it the containing block for fixed children,
which silently clamped the first version to the header bar.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Review feedback: the top bar mixed project-scoped tabs with
app-level ones and left the project tabs visible-but-dead until a
project was chosen elsewhere.
- The project is now a first-class switcher directly after the
wordmark (dropdown: all indexed projects + a manage-and-index
row); picking one lands on its Overview.
- Project tabs (Overview...Dashboard) render only when a project is
selected - a control that cannot work does not render, same trust
rule as everywhere else. A divider separates context from views.
- App-level destinations sit quietly on the right: search (only
with a project) and Control. The redundant right-side project
chip is gone; the Projects tab is reachable through the switcher
and remains the no-project landing page.
UI-only change; vitest 66/66, tsc clean.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
GET /api/handout renders a self-contained, print-friendly HTML
document that explains the project to a newcomer: the region map
(cohesion as words, hubs), boundary spanners with a why-you-care
line, the main entry-to-effect journeys, and the churn x complexity
table. Linked from the Overview header ('Handout').
Honesty properties, tested: every graph-derived string is
HTML-escaped; no host paths leak into the artifact; caps are
declared inline ('20 of 33 regions shown', '10 of 60 journeys');
a project with no readable git history says so instead of skipping
silently; every fact is deterministic - the header states that no
model wrote any of it.
Exposing the same document as an MCP resource is recorded as a
follow-up (touches the MCP protocol surface; wants its own review).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
/api/blast buckets a file list by region (names included, 200-file
cap declared as truncated, every file accounted for: bucketed +
unmapped == files). The Changes tab feeds it the impacted symbols'
files (falling back to raw changed files) and renders the review
signal git cannot compute: 'your change reaches N files across K
regions' with per-region chips - disambiguated with the same
hub-suffix rule the galaxy uses, so two 'tests' communities don't
render as twins - and a seam warning at K>=3 suggesting a split or
citing the region names in the prompt.
Tests: blast bucketing + accounting + empty-input refusal (C),
/api/blast route wiring.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The D6 trust sweep: every capped or scoped surface says what it hid
and what it means.
- Symbol connections: when the page cuts the list, the hidden
remainder is grouped BY FILE ('src/auth/session.c x 12') next to
the Show-more button - a bare '+N more' hides the answer to 'who
calls this' on exactly the nodes where it matters.
- Connection rows carry the neighbor's region; a row whose edge
crosses an architectural seam gets a quiet accent dot.
- The Data-flows panel no longer renders on projects with no
DATA_FLOWS edges at all: a section that can only ever say 'none'
is noise, not honesty.
- /api/bridges: boundary spanners ranked by DISTINCT foreign regions
a node calls into (cross-call count as tiebreak), test files
excluded and inbound direction ignored - orchestrators like main
and cbm_daemon_host_run surface; utility sinks and test harnesses
(which the naive both-directions metric ranked first) do not.
Overview shows the top 5 with a why-you-care line.
- Staleness banner was built and then REMOVED: no server signal
means 'files changed since indexing' today, and detect_changes
(a git-base diff) would have shown the wrong number. Recorded as
a follow-up instead of shipped with wrong semantics.
Tests: bridges need >=2 foreign regions (a dense gamma community
mirrors the beta fixture pattern), overflow tail accounts for
exactly what the page cut, per-row regions + data-flow presence
flag, /api/bridges route wiring.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- The galaxy stays mounted once visited: hidden with invisible (never
display:none, which zeroes a canvas), WebGL context and layout kept,
frameloop paused via the scene's active prop - switching tabs no
longer drops and refetches the whole 3D scene.
- Flows list merges journeys by entry point: one row per entry
(richest first) with an 'x N journeys from <entry> - show all'
expander, so ten main() walks stop drowning the list.
- Modules gains a map|bars toggle (persisted): the same children as a
sorted horizontal-bar list - region-colored, symbol counts - for
when proportion matters less than ranking.
- Filter chips strike through when disabled (excluded, not missing).
- One-time in-place hint on the region scene; dismissing persists,
leaving without dismissing does not.
Tests: groupFlowsByEntry ordering/merge (vitest); suite green with
the keep-mounted change.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The D4 orientation package, mechanics adapted from the two reference
UIs studied in the screened snapshots:
- Search scent propagates up the hierarchy: a 'find a symbol' input on
the region sidebar lights matching regions in the 3D scene, pins hit
counts on the region rows and the minimap, and reports the total
with an outside-regions remainder. Served by /api/scent - a
case-insensitive name match bucketed per region via the cached
file->region map (File/Folder nodes excluded so a file never
double-counts its own symbols; sub-2-char queries refused).
- 2D minimap panel (bottom right, collapsible): every region as a dot
on a fixed top-down projection, sized by sqrt(members), colored by
region, open region ringed, scent counts above the dots, click to
open, and a Home button that fits the current scene.
- Region hover card: members/files, cohesion as a word (tightly /
moderately / loosely connected) with the score, and the hub.
- Node tooltip on graphite tokens (last pre-D1 hardcoded surface) and
in/out call degree - layout nodes now carry out_calls, which the
degree pass already computed and discarded.
- Honest scope note in the region detail panel: '+N callers outside
this region' whenever the full-graph in-degree exceeds the loaded
window.
Tests: scent bucketing + LIKE-escape refusal (C), /api/scent route
wiring, minimap projection + cohesion words (vitest), region-list
queries disambiguated from minimap titles.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The two questions the research says developers ask most and tooling
answers worst: why is this code the way it is (rationale via change
history and ownership), and does A reach B (LaToza reachability,
asked ~9x/day). Plus the user-requested data-flow lens.
- /api/trace: shortest A-to-B path over CALLS or DATA_FLOWS (BFS,
depth-capped at 12), endpoints by id or qualified name; the flow
cache gains a DATA_FLOWS CSR beside the CALLS one.
- Symbol bundle: file_history (recent commits, commits/year, top
author share from a bounded git log at metrics time, cached and
honest - available:false when no history was parsed) and
data_in/data_out with the stored edge detail.
- Flows tab: trace bar with symbol pickers and a control/data
toggle; the result path is clickable hop by hop.
- Symbol tab: 'Recent changes to this file' and 'Data flows'
panels with honest empty states.
- Tests: trace reachability both modes + endpoint errors, symbol
bundle data-flow/history shape, /api/trace route wiring, and a
fetchTrace client test.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The metrics screens now answer questions instead of displaying data
(dashboard canon: Few, Grafana, CodeScene — the Field Guide carries the
citations):
Overview
- Four opinionated slots on top: Needs attention (knee-point count over
churn×complexity), Direction (deltas vs the previous index), Riskiest
area (named, with its why), and an untoned Trust strip (index stamp,
missed files, unmapped symbols).
- Surprising couplings become finding sentences with evidence, an
'inspect' next click, and an 'intended' dismissal whose key buckets by
order of magnitude — a dismissed finding resurfaces only when it grows
10x (regression re-alert, CodeScene's goals idea in one line of code).
- Hubs, entry points and boundaries demote to a collapsed Reference
disclosure; inventory facts move to an untoned footer.
Dashboard
- Hero: the churn×complexity list with the knee-point head highlighted, a
cost sentence ('N files — X% of the codebase, Y% of all commits this
year'), raw commits shown per row, mute-as-intended dispositions, and
rows opening the file's folder in Modules.
- Quality signals (resolution, dead, tested, documented) keep tones and
trends; the complexity histogram now states its takeaway and names the
tail; averages and equal-weight inventory cards are gone — facts live
in the footer.
- Metrics payload gains raw commits per risky entry, churn totals and the
distinct-file count to make the cost sentence computable.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Visual language, rebuilt on the researched rules (Primer/Carbon/Material/
Radix/Linear; details in the Atlas Field Guide):
- Graphite surfaces with fixed step jobs (#0D0F12 canvas → #293038
selected) replace the saturated navy; elevation is a lighter surface
step, not a shadow; hover/selection are surface washes.
- One accent (sky #4FA8E0) reserved for interactivity; semantic
good/warn/crit separate; the type floor is 12px (labels), body 13-14px,
system font stacks (sans UI, mono identifiers only — zero vendored
bytes), tabular numerals, radius 6px.
- Region colors move to the Okabe-Ito color-blind-safe palette, assigned
server-side to the ten largest regions with a neutral tail — the code
graph is the only colorful thing on screen.
Trust repairs, because every wrong-looking item costs belief in every
right one:
- builtins filtered out of hub lists (a 'len' hub reads as a bug);
- a product-code lens hides test regions by default everywhere regions
appear, one honest toggle away ('18 test regions hidden — show');
- flow labels carry file context when the bare name is ambiguous
('datastructures.__init__ → …' instead of '__init__ → …').
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The lazy-schema fix was ineffective: the UI never sent include_details,
so list_projects returned names only, every card lacked totals, and the
>1M-node skip compared against undefined — each Projects visit still ran
a ~30 s kernel schema scan, and revisits stacked uncancelled sweeps that
kept the store busy (the felt slowness).
Now: list_projects(include_details:true) carries totals and indexed_at;
the enrichment loop is generation-guarded (unmount or refresh abandons
it), schemas are cached for the session, auto-fetch is capped at 20
projects of ≤200k nodes, and larger stores keep a card with totals and
no chips. After a Projects visit the daemon log shows only sub-second
requests.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Opening the /rpc allowlist revealed a pre-existing cost: the Projects tab
awaited get_graph_schema for EVERY project before rendering anything, and
a schema scan walks every node and edge — ~30 s on a 13 GB kernel index.
(Before #1663's fix those calls failed instantly, which is why the page
ever felt fast.)
list_projects already reports per-project node/edge totals, so the page
now renders from those immediately; per-label chips arrive lazily, one
schema at a time, patching each card as it lands — and projects above a
million nodes skip the schema scan entirely, keeping a card with totals
and no chips rather than burning half a minute of server time per load.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The numbers view of a project, with the code behind every number one
click away and no composite health score — the cards are the score:
- /api/metrics (cached per project+indexed_at): complexity and function-
length histograms and top lists straight from pass_complexity's stored
cyclomatic/cognitive/lines numbers, edge certainty (CALLS vs
CALL_REFERENCE vs USAGE), dead-code candidates (same exclusions as the
galaxy classifier), TESTS linkage, SIMILAR_TO duplication, missed
files, documented-exports coverage.
- Per-file churn from one bounded git log pass (1 year, 10k commits, the
pass_githistory bounds) computed at metrics time — ZERO cost added to
indexing — plus churn × complexity, the classic where-the-bugs-live
list.
- A per-index history sidecar (JSONL in the cache dir, one snapshot per
indexed_at) feeds trend sparklines across reindexes.
- Dashboard tab: stat cards with warn tones, histograms, top lists
linking to symbol pages; churn honestly marked unavailable when the
project has no readable git history.
The wired-routes test covers /api/metrics; the ui suite pins totals,
certainty, histogram completeness and history presence on a fixture.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The UI section now names the two interfaces (MCP for the agent, Atlas
for the human) and documents the screens: Overview, Modules, region-LOD
Galaxy, Flows, Changes, Symbol pages, search and the prompt composer,
plus deep-linking.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The human side of the Atlas program:
- Overview: the first read of a project — header numbers, region cards
with provenance and cohesion, hubs by fan-in, entry points, surprising
couplings with enumerated reasons, suggested questions with a why, and
the cross-package boundary table. Assembled client-side from the
read-only /rpc surface (get_architecture format:json) + the regions
payload; no new server code.
- Modules: a squarified treemap (in-house, no new dependency) of any
folder via /api/tree — sized by symbols, colored by dominant region,
hatched where the indexer missed files, breadcrumbed drill-down, file
panel listing symbols via search_graph.
- Symbol: one page per node via /api/symbol — docstring, flags, region,
editor/GitHub links, code on demand, callers/callees with true totals
and per-type confidence badges (resolved / reference / unproven),
tests, co-change partners, near-clones.
- Flows: the /api/flows journeys grouped across/within regions, each a
clickable indented call tree with truncation stated, mermaid export.
- Changes: detect_changes on the working tree grouped by risk, blast
radius citable into the prompt in one click.
- ⌘K: symbols (search_graph) + code text (search_code) in one box.
- Prompt composer: a basket of cited symbols/regions/flows/questions that
renders a prompt naming exact qualified names, file:line ranges and the
MCP calls that reproduce the context — the bridge back to the agent.
Routing carries every screen in the URL (tab, project, path, sym, flow,
node, region) so all of it deep-links. Fixed en route: the /api/flow
route pattern '/api/flow?*' never matched (req->path has no query
string) — now exact, pinned by a wired-routes test that distinguishes a
matched route's 400 from the static handler's 404.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
On Django the flow list was all setUpTestData/test_* entries: test
functions are natural call roots (no callers, many callees) so they
out-scored real entries. A test file is a journey into the code, not of
it — candidates in test paths score zero unless the indexer explicitly
flagged them as entry points.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three read-only services the Atlas screens consume, as pure JSON producers
over an open store (handlers stay thin; tests hit the functions directly):
- GET /api/tree?project&path — aggregated children of one folder: files
and symbol counts per child, dominant region (majority vote), missed-
coverage counts from the #963 shadow project, child cap reported rather
than silent. Feeds the Modules treemap at any project size without
shipping nodes.
- GET /api/symbol?project&id|qn — the symbol page in one response: node
with docstring and entry/test/export flags, region membership, callers
and callees with TRUE totals, per-edge-type counts and pagination
(CALLS before CALL_REFERENCE before USAGE), inbound TESTS, co-change
partners of the file (FILE_CHANGES_WITH), and near-clones (SIMILAR_TO).
- GET /api/flows?project, /api/flow?project&id — named entry→terminal call
journeys: entries scored from stored flags and call shape, bounded DFS
over CALLS (depth 6, 4 children per step, 40 steps), deduplicated by
entry/terminal pair, ranked sink-first, truncation counted; cached per
(project, indexed_at) like the region level. The busiest callee leads
each step so the trunk of the journey reads first.
The region cache gains an O(1) file→region lookup (hash-backed) shared by
tree, symbol and flows. Fixed en route: yyjson borrows object keys added
via add_*(), so the per-type count keys are now copied into the document
instead of dangling once their sqlite statement is finalized.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The graph tab becomes CBM Atlas and gains the region level as its default
for large projects:
- Projects above 5,000 nodes open on the REGION SCENE: one body per Leiden
call community (folder groups as fallback), weighted arcs between them,
a region list on the left and a region panel (provenance, cohesion, top
callables) on the right. Double-click or 'Open region' loads that
region's full-detail layout (scope=region:N) with a '< Regions'
breadcrumb; 'Load full galaxy' keeps the classic whole-graph view one
click away. Small projects skip the region hop entirely.
- Deep links (#564): ?node= and ?region= join ?tab=&project= — a selected
node or opened region survives reload and back/forward, and can be
shared.
- #1197: clicking a symbol row in the sidebar (tree or search) selects the
node itself — scene highlight AND detail panel; selecting a directory
clears a stale panel instead of leaving the previous node in it.
- #1198: connection lists page with 'Show N more' instead of a silent cap
of 25.
- Rename: the UI announces itself as CBM Atlas (header + title).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The galaxy loaded 'the first max_nodes nodes' of a project, which above
~10k nodes meant freezing the tab (#498 family) or truncating the graph.
Give it a real coarsest level instead: one body per REGION.
Regions are sets of files. Files are assigned by majority vote of their
callables' Leiden communities over the CALLS graph (seeded, deterministic);
files no kept community explains fall back to folder groups (first two
path components), and a final 'misc' bucket catches the rest. Every node
inherits its file's region, all edge types aggregate into region-to-region
weights, and each region carries its name's provenance ('call community:
202 files, 38% under tests/db_functions'), cohesion, member/file counts
and its top callables by degree.
GET /api/layout?project=X&level=regions -> region bodies + weighted arcs
GET /api/layout?project=X&scope=region:N -> full detail, that region only
Positions come from a golden-angle sphere seed refined by the shared
anchored optimizer (extracted to layout_internal.h alongside the new
cbm_layout_from_nodes core, so the region scope reuses the exact layout
path). Results are cached per (project, indexed_at): on a 48k-node index
the region scene computes in ~1.6s cold, <1ms cached; a 4.8k-node region
scope lays out in ~0.5s.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The graph UI could call only list_projects and get_code_snippet through
/rpc; every other tool answered 403 'UI RPC method is not allowed'. That
starved the browser of schema, search, tracing, architecture and impact
data the server already computes, and broke the Projects tab, which needs
get_graph_schema (#1663).
Allow the read-only tools (get_graph_schema, search_graph, search_code,
trace_path/trace_call_path, get_architecture, query_graph, detect_changes,
check_index_coverage, index_status) and manage_adr in its read modes only
(default get, sections). A duplicated or non-string mode refuses, so a
write cannot ride in behind a read. Mutating tools keep their 403; the
loopback-only boundary is unchanged.
Fixes#1663.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>