From 3f3f1ac486517a87199fdcd36e63a9edee784435 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Thu, 27 Aug 2026 12:23:50 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20the=20metric=20wiki=20=E2=80=94=20s?= =?UTF-8?q?ingle-source=20entries,=20WikiPanel,=20term=20chips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- graph-ui/src/App.tsx | 12 + graph-ui/src/components/DashboardTab.tsx | 43 ++- graph-ui/src/components/MetricChip.tsx | 27 ++ graph-ui/src/components/SymbolTab.tsx | 30 +- graph-ui/src/components/WikiPanel.test.tsx | 58 ++++ graph-ui/src/components/WikiPanel.tsx | 202 ++++++++++++ graph-ui/src/lib/i18n.ts | 18 ++ graph-ui/src/wiki/entries.test.ts | 52 ++++ graph-ui/src/wiki/entries.ts | 344 +++++++++++++++++++++ graph-ui/tsconfig.tsbuildinfo | 2 +- 10 files changed, 772 insertions(+), 16 deletions(-) create mode 100644 graph-ui/src/components/MetricChip.tsx create mode 100644 graph-ui/src/components/WikiPanel.test.tsx create mode 100644 graph-ui/src/components/WikiPanel.tsx create mode 100644 graph-ui/src/wiki/entries.test.ts create mode 100644 graph-ui/src/wiki/entries.ts diff --git a/graph-ui/src/App.tsx b/graph-ui/src/App.tsx index b31d3c79f..837fae46a 100644 --- a/graph-ui/src/App.tsx +++ b/graph-ui/src/App.tsx @@ -10,6 +10,7 @@ import { ChangesTab } from "./components/ChangesTab"; import { DashboardTab } from "./components/DashboardTab"; import { CommandK } from "./components/CommandK"; import { ProjectSwitcher } from "./components/ProjectSwitcher"; +import { WikiPanel } from "./components/WikiPanel"; import { PromptBasketProvider, PromptBasketDrawer, @@ -74,6 +75,8 @@ export function App() { const t = useUiMessages(); const [route, setRoute] = useState(readRoute); const [commandOpen, setCommandOpen] = useState(false); + /* The metric wiki: any dotted-underline term opens its entry here. */ + const [wikiSlug, setWikiSlug] = useState(null); /* The galaxy keeps its WebGL context and layout across tab switches: * mounted once visited, hidden with `invisible` (never display:none, which * would zero the canvas), frameloop paused while hidden. */ @@ -280,6 +283,7 @@ export function App() { project={selectedProject} onOpenSymbol={(ref) => openSymbol(ref)} onOpenModulesPath={(path) => navigate({ tab: "modules", path })} + onOpenWiki={setWikiSlug} /> ) : activeTab === "symbol" && selectedProject && symbolRef ? ( ) : activeTab === "control" ? ( @@ -303,6 +308,13 @@ export function App() { onClose={() => setCommandOpen(false)} onOpenSymbol={openSymbol} /> + {wikiSlug && ( + setWikiSlug(null)} + onNavigate={setWikiSlug} + /> + )} diff --git a/graph-ui/src/components/DashboardTab.tsx b/graph-ui/src/components/DashboardTab.tsx index 380387c23..fe729c7ec 100644 --- a/graph-ui/src/components/DashboardTab.tsx +++ b/graph-ui/src/components/DashboardTab.tsx @@ -4,9 +4,10 @@ * tones and trends; inventory facts live in the footer; histograms exist * only with a computed takeaway. No composite score — the cards are the * score. Everything derives from the graph and one bounded git-log pass. */ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState, type ReactNode } from "react"; import { ScrollArea } from "@/components/ui/scroll-area"; import { kneeCount, costSentence, findingKey, isDismissed, dismiss } from "../lib/findings"; +import { MetricChip } from "./MetricChip"; interface MetricEntry { qn?: string; @@ -54,6 +55,7 @@ interface DashboardTabProps { project: string; onOpenSymbol: (ref: { qn: string }) => void; onOpenModulesPath: (path: string) => void; + onOpenWiki: (slug: string) => void; } const CPLX_LABELS = ["1", "2–5", "6–10", "11–20", "21–50", ">50"]; @@ -90,7 +92,7 @@ function QualityCard({ tone, trend, }: { - label: string; + label: ReactNode; value: string; sub?: string; tone?: "warn" | "crit"; @@ -119,7 +121,7 @@ function TopList({ total, onOpenSymbol, }: { - title: string; + title: ReactNode; entries: MetricEntry[]; unit: string; /* Size of the population the list was ranked over — only when the payload @@ -173,7 +175,12 @@ function TopList({ ); } -export function DashboardTab({ project, onOpenSymbol, onOpenModulesPath }: DashboardTabProps) { +export function DashboardTab({ + project, + onOpenSymbol, + onOpenModulesPath, + onOpenWiki, +}: DashboardTabProps) { const [metrics, setMetrics] = useState(null); const [error, setError] = useState(null); const [showMuted, setShowMuted] = useState(false); @@ -270,7 +277,9 @@ export function DashboardTab({ project, onOpenSymbol, onOpenModulesPath }: Dashb

- Where the bugs live — churn × complexity + + Where the bugs live — churn × complexity +

{metrics.top_churn_complex.length > 0 && ( @@ -351,20 +360,32 @@ export function DashboardTab({ project, onOpenSymbol, onOpenModulesPath }: Dashb trend={history.map((entry) => 1 - entry.usage_share)} /> + Dead candidates + + } value={totals.dead.toLocaleString("en-US")} sub="no callers; entry/test/exported excluded" tone={totals.dead > 0 ? "warn" : undefined} trend={history.map((entry) => entry.dead)} /> + Tested symbols + + } value={`${(testedShare * 100).toFixed(0)}%`} sub={`${totals.tested_symbols.toLocaleString("en-US")} with TESTS edges`} trend={history.map((entry) => entry.tested)} /> + Documented exports + + } value={`${(docShare * 100).toFixed(0)}%`} sub={`${totals.documented_exported.toLocaleString("en-US")} of ${totals.exported.toLocaleString("en-US")} exported`} tone={docShare < 0.3 ? "warn" : undefined} @@ -404,7 +425,11 @@ export function DashboardTab({ project, onOpenSymbol, onOpenModulesPath }: Dashb {/* ── Drill-down lists ──────────────────────────────────── */}
+ Most complex + + } entries={metrics.top_complex} unit="cyclo" total={totals.callables} diff --git a/graph-ui/src/components/MetricChip.tsx b/graph-ui/src/components/MetricChip.tsx new file mode 100644 index 000000000..79d6eba22 --- /dev/null +++ b/graph-ui/src/components/MetricChip.tsx @@ -0,0 +1,27 @@ +/* The wiki affordance: any metric name in the UI becomes a dotted-underline + * chip that opens the metric wiki panel — the label itself is the door to + * the definition. Inherits the surrounding typography so wrapping an + * existing label changes nothing but the underline. */ +import { type ReactNode } from "react"; +import { wikiEntry } from "../wiki/entries"; + +export function MetricChip({ + slug, + onOpen, + children, +}: { + slug: string; + onOpen: (slug: string) => void; + children?: ReactNode; +}) { + return ( + + ); +} diff --git a/graph-ui/src/components/SymbolTab.tsx b/graph-ui/src/components/SymbolTab.tsx index 8e591b8ad..45bde11f9 100644 --- a/graph-ui/src/components/SymbolTab.tsx +++ b/graph-ui/src/components/SymbolTab.tsx @@ -13,6 +13,7 @@ import { type SymbolRef, } from "../lib/atlas"; import { AddToPromptButton } from "./PromptBasket"; +import { MetricChip } from "./MetricChip"; import { TriggerTree } from "./TriggerTree"; import type { RepoInfo } from "../lib/types"; @@ -21,6 +22,7 @@ interface SymbolTabProps { symbolRef: { id?: number; qn?: string }; onOpenSymbol: (ref: { id?: number; qn?: string }) => void; onOpenRegion: (regionId: number) => void; + onOpenWiki: (slug: string) => void; } const CONFIDENCE: Record = { @@ -37,12 +39,14 @@ function ConnectionList({ homeRegion, onLoadMore, onOpenSymbol, + onOpenWiki, }: { title: string; page: ConnectionPage; homeRegion?: number; onLoadMore: () => void; onOpenSymbol: (ref: { id?: number; qn?: string }) => void; + onOpenWiki: (slug: string) => void; }) { const shown = page.offset + page.items.length; /* A utility called from everywhere would carry a dot on every row — the @@ -68,9 +72,11 @@ function ConnectionList({ )} - {Object.entries(page.by_type) - .map(([type, count]) => `${CONFIDENCE[type]?.label ?? type.toLowerCase()} ${count}`) - .join(" · ")} + + {Object.entries(page.by_type) + .map(([type, count]) => `${CONFIDENCE[type]?.label ?? type.toLowerCase()} ${count}`) + .join(" · ")} +
@@ -133,7 +139,13 @@ function ConnectionList({ ); } -export function SymbolTab({ project, symbolRef, onOpenSymbol, onOpenRegion }: SymbolTabProps) { +export function SymbolTab({ + project, + symbolRef, + onOpenSymbol, + onOpenRegion, + onOpenWiki, +}: SymbolTabProps) { const [bundle, setBundle] = useState(null); const [error, setError] = useState(null); const [limit, setLimit] = useState(50); @@ -331,6 +343,7 @@ export function SymbolTab({ project, symbolRef, onOpenSymbol, onOpenRegion }: Sy homeRegion={bundle.region?.id} onLoadMore={() => setLimit((value) => value + 100)} onOpenSymbol={onOpenSymbol} + onOpenWiki={onOpenWiki} /> setLimit((value) => value + 100)} onOpenSymbol={onOpenSymbol} + onOpenWiki={onOpenWiki} />
@@ -430,7 +444,9 @@ export function SymbolTab({ project, symbolRef, onOpenSymbol, onOpenRegion }: Sy

- Tested by + + Tested by +

{(bundle.tests ?? []).map((test: SymbolRef) => ( + )} +
+ {stack.length > 1 && + stack.slice(0, -1).map((crumb, index) => ( + + + › + + ))} + + {entry?.term ?? slug} + +
+ +
+ + {!entry ? ( +

+ No wiki entry for “{slug}”. +

+ ) : ( + +
+ {/* Term + tier */} +
+

+ {entry.term} +

+ + {entry.tier} + +
+ + {/* The one-sentence definition — always present. */} +

+ {entry.sentence} +

+ + {entry.why && ( +
+ {t.wiki.whyMatters} +

+ {entry.why} +

+
+ )} + + {(entry.computedParts || entry.caps) && ( +
+ {t.wiki.howComputed} + {(entry.computedParts ?? []).map((part) => ( +

+ · {part} +

+ ))} + {(entry.caps ?? []).map((cap) => ( +

+ ⚠ {cap} +

+ ))} +
+ )} + + {entry.notCovered && ( +
+ {t.wiki.notCovered} +

+ {entry.notCovered} +

+
+ )} + + {entry.appearsIn.length > 0 && ( +
+ {t.wiki.whereAppears} + {entry.appearsIn.map((place) => ( +

+ {place} +

+ ))} +
+ )} + + {/* Footer: id + paired metric + see-also chips */} +
+ {entry.id} + {entry.pairedWith && ( + + {t.wiki.pairedWith}{" "} + + + )} + {(entry.seeAlso ?? []).map((other) => ( + + + + ))} +
+
+
+ )} +
+ ); +} diff --git a/graph-ui/src/lib/i18n.ts b/graph-ui/src/lib/i18n.ts index 970462f48..dab80c0c2 100644 --- a/graph-ui/src/lib/i18n.ts +++ b/graph-ui/src/lib/i18n.ts @@ -70,6 +70,15 @@ export const messages = { title: "Architecture Decision Record", lastUpdated: "Last updated", }, + wiki: { + whyMatters: "Why this matters", + howComputed: "How it's computed", + notCovered: "What this does not cover", + whereAppears: "Where it appears", + pairedWith: "Paired with", + back: "Back", + close: "Close", + }, control: { panel: "Control Panel", totalCpu: "Total CPU", @@ -151,6 +160,15 @@ export const messages = { title: "架构决策记录", lastUpdated: "最后更新", }, + wiki: { + whyMatters: "为什么重要", + howComputed: "如何计算", + notCovered: "不涵盖的内容", + whereAppears: "出现位置", + pairedWith: "配对指标", + back: "返回", + close: "关闭", + }, control: { panel: "控制面板", totalCpu: "总 CPU", diff --git a/graph-ui/src/wiki/entries.test.ts b/graph-ui/src/wiki/entries.test.ts new file mode 100644 index 000000000..1787a6630 --- /dev/null +++ b/graph-ui/src/wiki/entries.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import { WIKI_ENTRIES, wikiEntry, wikiEntryById } from "./entries"; + +describe("wiki entries", () => { + it("every sentence is present and tooltip-sized (≤30 words)", () => { + for (const entry of WIKI_ENTRIES) { + expect(entry.sentence.trim().length, entry.slug).toBeGreaterThan(0); + expect( + entry.sentence.trim().split(/\s+/).length, + `${entry.slug} sentence exceeds 30 words`, + ).toBeLessThanOrEqual(30); + } + }); + + it("ids and slugs are unique", () => { + const ids = WIKI_ENTRIES.map((entry) => entry.id); + const slugs = WIKI_ENTRIES.map((entry) => entry.slug); + expect(new Set(ids).size).toBe(ids.length); + expect(new Set(slugs).size).toBe(slugs.length); + }); + + it("every non-refused entry names what it does not cover", () => { + for (const entry of WIKI_ENTRIES) { + if (entry.tier === "refused") continue; + expect(entry.notCovered?.trim(), entry.slug).toBeTruthy(); + } + }); + + it("every pairedWith and seeAlso slug resolves to an existing entry", () => { + for (const entry of WIKI_ENTRIES) { + if (entry.pairedWith !== undefined) { + expect(wikiEntry(entry.pairedWith), `${entry.slug} → ${entry.pairedWith}`).toBeDefined(); + } + for (const other of entry.seeAlso ?? []) { + expect(wikiEntry(other), `${entry.slug} → ${other}`).toBeDefined(); + } + } + }); + + it("ids follow the M-… register format", () => { + for (const entry of WIKI_ENTRIES) { + expect(entry.id, entry.slug).toMatch(/^M-[A-Z-]+$/); + } + }); + + it("lookup helpers resolve by slug and by id", () => { + expect(wikiEntry("hotspot")?.id).toBe("M-HOTSPOT"); + expect(wikiEntryById("M-HOTSPOT")?.slug).toBe("hotspot"); + expect(wikiEntry("no-such-metric")).toBeUndefined(); + expect(wikiEntryById("M-NO-SUCH")).toBeUndefined(); + }); +}); diff --git a/graph-ui/src/wiki/entries.ts b/graph-ui/src/wiki/entries.ts new file mode 100644 index 000000000..71673d7e7 --- /dev/null +++ b/graph-ui/src/wiki/entries.ts @@ -0,0 +1,344 @@ +/* The metric wiki — every number Atlas shows, explained in one place: what + * it means (one tooltip-sized sentence), why it earns its spot, how it is + * computed, the named caps, and what it deliberately does not cover. The + * "refused" tier documents metrics Atlas will NOT show, and why. This file + * is the single source of truth; panels and chips render from it. */ + +export type WikiTier = "first-class" | "caveated" | "refused"; + +export interface WikiEntry { + id: string; + slug: string; + term: string; + tier: WikiTier; + /* ≤30 words — this IS the tooltip text. */ + sentence: string; + why?: string; + computedParts?: string[]; + /* Named caps, each "capped: … because …" style — rendered as warnings. */ + caps?: string[]; + notCovered?: string; + appearsIn: string[]; + pairedWith?: string; + seeAlso?: string[]; +} + +export const WIKI_ENTRIES: WikiEntry[] = [ + { + id: "M-CHURN", + slug: "churn", + term: "churn", + tier: "first-class", + sentence: + "How often this file changed in the last year — the strongest single predictor of where work concentrates.", + why: "Where code changed last year is where it will change next — and every change is a chance to break something. Process history predicts defects better than any measure of the code itself.", + caps: ["vendored and generated paths are excluded"], + notCovered: + "Raw commit counts, not size-adjusted; relative churn (per size) is the variant with peer-reviewed defect-prediction backing and is planned as the default axis.", + appearsIn: ["Dashboard", "Symbol file history", "Handout"], + pairedWith: "complexity", + }, + { + id: "M-COCHANGE", + slug: "co-change", + term: "co-change", + tier: "first-class", + sentence: + "Files that tend to change in the same commits as this one — coupling as history actually happened, not as imports suggest.", + why: "If two files always change together, they are one module the directory tree forgot. Editing one without its partner is a classic source of half-done changes.", + notCovered: + "Pairs below the minimum co-commit support are not shown; the observation window is stated on the panel.", + appearsIn: ["Symbol"], + pairedWith: "coupling", + }, + { + id: "M-FANIN", + slug: "fan", + term: "fan", + tier: "first-class", + sentence: + "How many symbols call this one (fan-in), and how many it calls (fan-out) — the raw material of blast radius.", + why: "High fan-in means many places notice when this breaks; high fan-out means this breaks when many places change. Both ends deserve tests and careful review.", + notCovered: + "Edges carry resolution confidence; unresolved dynamic calls are not counted.", + appearsIn: ["Symbol", "Galaxy"], + seeAlso: ["confidence", "blast-radius"], + }, + { + id: "M-HOTSPOT", + slug: "hotspot", + term: "hotspot", + tier: "first-class", + sentence: + "Files that change often and are hard to follow — where fixes cluster and edits cost the most.", + why: "A small set of hotspots typically carries a large share of defect-fixing work. Relative churn predicts defect density; raw line counts don't. Effort spent here beats effort spread evenly.", + computedParts: [ + "churn percentile of this repo", + "complexity percentile of this repo", + ], + caps: [ + "capped: files under 100 lines are excluded — too small to rank", + "capped: test files are excluded", + ], + notCovered: + "Bug density is not measured directly — there is no issue-tracker join yet. Generated or vendored files can inflate churn. A stable-but-tangled file with no recent commits won't appear here; see complexity alone for those.", + appearsIn: ["Dashboard hero", "Changes risk", "Handout"], + pairedWith: "tested", + seeAlso: ["churn", "complexity"], + }, + { + id: "M-CPLX", + slug: "complexity", + term: "complexity", + tier: "caveated", + sentence: + "The number of independent paths through a function — a decent proxy for hard-to-test, a weak one for hard-to-read.", + why: "Every independent path is a case a test must cover and a reader must simulate. High-percentile functions in this repository are where reviews slow down and tests multiply.", + notCovered: + "Cyclomatic complexity correlates heavily with size; the percentile shown is of this repository, never an absolute good/bad threshold.", + appearsIn: ["Dashboard", "Symbol"], + pairedWith: "churn", + seeAlso: ["cognitive-complexity"], + }, + { + id: "M-COG", + slug: "cognitive-complexity", + term: "cognitive complexity", + tier: "caveated", + sentence: + "How much nesting and control-flow interruption a reader must hold in their head to follow this function.", + why: "Deep nesting forces a reader to keep every enclosing condition in working memory. This measure was designed to match how hard code feels to read, and tracks that better than path counting.", + notCovered: + "Better matched to perceived readability than cyclomatic, but still size-confounded.", + appearsIn: ["Dashboard", "Symbol"], + seeAlso: ["complexity"], + }, + { + id: "M-COH", + slug: "cohesion", + term: "cohesion", + tier: "caveated", + sentence: + "How much this region's files actually work together, measured by internal connections.", + why: "A region whose files barely talk to each other is a folder, not a module — changes ripple outward instead of staying local. Read it beside coupling: shredding a module improves one at the cost of the other.", + notCovered: + "Structural cohesion measures failed empirical validation as quality predictors; a history-based co-change cohesion is the planned replacement. Always read beside coupling.", + appearsIn: ["Overview regions", "Modules"], + pairedWith: "coupling", + }, + { + id: "M-COUP", + slug: "coupling", + term: "coupling", + tier: "first-class", + sentence: + "How strongly two regions depend on each other, counted in cross-region calls.", + why: "The more two regions call each other, the harder they are to change, test, or understand apart. Heavy coupling between supposedly separate modules is the map's most actionable warning.", + notCovered: "Only statically resolved calls are counted; confidence applies.", + appearsIn: ["Overview", "Modules"], + pairedWith: "cohesion", + }, + { + id: "M-BRIDGE", + slug: "boundary-spanner", + term: "boundary spanner", + tier: "first-class", + sentence: + "A symbol that calls into several foreign regions — the joints of the architecture, and the first places a refactor hurts.", + why: "These symbols hold the architecture together — and any boundary refactor lands on them first. They are where an interface change fans out into foreign regions.", + caps: [ + "capped: outgoing calls only", + "capped: at least 2 foreign regions required", + "capped: test files are excluded", + ], + notCovered: + "Incoming cross-region popularity is a different signal (see fan-in).", + appearsIn: ["Overview"], + seeAlso: ["fan", "coupling"], + }, + { + id: "M-CONF", + slug: "confidence", + term: "confidence", + tier: "first-class", + sentence: + "How likely this call edge is real, given how the resolver found it.", + why: "Low-confidence hops can turn a trace into a plausible story that isn't true. The word and the number always appear together: certain ≥95, likely 75–94, uncertain 50–74, speculative <50.", + caps: [ + "capped: cross-language boundary → at most likely", + "reduced: target overload set larger than 1", + "speculative: dynamic dispatch unresolved", + ], + notCovered: + "Reflection, code generation, and runtime dispatch are not modelled. Coverage is best-effort.", + appearsIn: ["Flows", "Trace", "Symbol connections"], + }, + { + id: "M-DEAD", + slug: "dead-candidate", + term: "dead candidate", + tier: "caveated", + sentence: + "Symbols with no recorded callers — candidates for removal, with caveats you must check first.", + why: "The blocker to deleting dead code is fear of unintended breakage, not detection. This list is evidence, not a verdict — verify with a repo-wide search and tests before deleting.", + notCovered: + "Dynamic dispatch, reflection, exported API consumed outside this repository, and config-referenced entry points all look dead here and are not.", + appearsIn: ["Dashboard"], + seeAlso: ["fan", "confidence"], + }, + { + id: "M-TESTED", + slug: "tested", + term: "tested", + tier: "caveated", + sentence: + "A TESTS edge reaches this symbol — some test exercises it, directly or through calls.", + why: "A symbol no test reaches fails only in production. Reach is the floor of safety; hotspots without reach are the riskiest code in the repository.", + notCovered: + "Reached is not asserted: a test can execute this code without checking its result. Atlas deliberately shows reach, not a coverage percentage.", + appearsIn: ["Dashboard", "Symbol"], + pairedWith: "hotspot", + seeAlso: ["coverage-gates"], + }, + { + id: "M-DOC", + slug: "documented", + term: "documented", + tier: "caveated", + sentence: + "Exported symbols that carry a doc comment — a floor for explainability, not a measure of doc quality.", + why: "An undocumented export forces every caller to read the implementation. A doc comment is a cheap floor that pays off at every call site — and in every agent prompt.", + notCovered: + "Comment presence only; accuracy and usefulness are not assessed.", + appearsIn: ["Dashboard"], + }, + { + id: "M-DIVERG", + slug: "divergence", + term: "divergence", + tier: "caveated", + sentence: + "Copies of this code that have started to drift apart — the risk is the drift, not the duplication.", + why: "When copies drift, a bug fixed in one lives on in the other. Divergent near-clones are the copies that have already started disagreeing.", + notCovered: + "Duplication alone is a weak signal; identical, stable copies are mostly harmless. Divergence under change is the hazard this view ranks.", + appearsIn: ["Symbol near-clones"], + }, + { + id: "M-HELP", + slug: "who-can-help", + term: "who can help", + tier: "caveated", + sentence: + "People with recent, substantial history in this code — evidence for who to ask, never a performance measure.", + why: "The most-sought information in twenty years of developer studies is what coworkers know. When the graph runs out of answers, the right person is the fastest path — this shows who, with the evidence.", + notCovered: + "Atlas never ranks people against each other and never shows per-person productivity. Names appear only with their evidence: commits, recency, breadth.", + appearsIn: ["Symbol file history"], + }, + { + id: "M-REGION", + slug: "region", + term: "region", + tier: "first-class", + sentence: + "A cluster of files that call each other more than they call anything else — Atlas's inferred modules.", + why: "Modules inferred from actual call structure show the architecture as it is, not as the directory tree claims. Where the two disagree is usually worth a look.", + notCovered: + "Inferred from call structure, not from directory names; a region panel must answer why is this here — members, internal density, top external ties.", + appearsIn: ["Galaxy", "Overview", "Modules"], + seeAlso: ["cohesion", "coupling"], + }, + { + id: "M-HUB", + slug: "hub", + term: "hub", + tier: "first-class", + sentence: + "The most-connected symbols in the graph — the ones most other code depends on, directly or transitively.", + why: "A change to a hub is felt everywhere; an outage in one is an outage in many. Hubs deserve the strongest tests and the most careful review in the repository.", + caps: [ + "capped: top 12 by connectivity are haloed; the cutoff is a display choice, not a cliff", + ], + notCovered: "Connectivity is static; a rarely-executed hub still ranks.", + appearsIn: ["Galaxy halos", "Overview"], + seeAlso: ["fan"], + }, + { + id: "M-BLAST", + slug: "blast-radius", + term: "blast radius", + tier: "first-class", + sentence: + "Everything that can reach the code you changed — what could notice, grouped by region.", + why: "Most regressions ship because a change was tested where it was made, not where it is felt. The blast list is what to retest before pushing.", + notCovered: + "Computed on the uncommitted diff today; hypothetical per-symbol impact and tests-to-run are planned. Confidence caveats apply along every path.", + appearsIn: ["Changes"], + seeAlso: ["fan", "confidence"], + }, + { + id: "M-SCENT", + slug: "scent", + term: "scent", + tier: "first-class", + sentence: + "Where your search term concentrates across the map — follow the smell, not the guess.", + why: "Search results drawn on the map show where a concept lives and how concentrated it is — one bright region means a clean home; scattered hits mean a crosscutting concern.", + notCovered: + "Name and text matches only; File and Folder nodes are excluded from counts.", + appearsIn: ["Galaxy minimap", "Search"], + }, + { + id: "M-NEG-MI", + slug: "maintainability-index", + term: "maintainability index", + tier: "refused", + sentence: + "Atlas does not show a Maintainability Index: its weights are folklore, it double-counts size, and it failed empirical validation.", + why: "The MI's constants were fit to a handful of 1990s Hewlett-Packard systems and never revalidated; size dominates every term. What Atlas shows instead: churn, complexity and size separately, each with its own percentile.", + appearsIn: ["Wiki only"], + }, + { + id: "M-NEG-HAL", + slug: "halstead", + term: "Halstead", + tier: "refused", + sentence: + "Atlas does not compute Halstead metrics: token-counting science from 1977 that never replicated; size does its job better.", + why: "Operator-and-operand counting promised effort and bug prediction it never delivered under replication; every useful signal it carries, plain size carries better.", + appearsIn: ["Wiki only"], + }, + { + id: "M-NEG-COV", + slug: "coverage-gates", + term: "coverage gates", + tier: "refused", + sentence: + "Atlas shows which tests reach a symbol, never a coverage-percentage target: rigid targets produce assertion-free tests.", + why: "The correlation between coverage and post-release defects is weak and disappears above roughly 70–80%. Atlas shows test reach per symbol so you can judge the gap that matters.", + appearsIn: ["Wiki only"], + seeAlso: ["tested"], + }, + { + id: "M-NEG-GRADE", + slug: "quality-grades", + term: "quality grades", + tier: "refused", + sentence: + "No A–F, no 0–100: a percentile in this repository carries the same information with no invented scale to misread.", + why: "Published score curves still get read as linear; grades measurably undermine interest and performance relative to explanations; single scores with consequences invite gaming. Atlas ships percentiles, distributions and named caps instead.", + appearsIn: ["Wiki only"], + }, +]; + +const bySlug = new Map(WIKI_ENTRIES.map((entry) => [entry.slug, entry])); +const byId = new Map(WIKI_ENTRIES.map((entry) => [entry.id, entry])); + +export function wikiEntry(slug: string): WikiEntry | undefined { + return bySlug.get(slug); +} + +export function wikiEntryById(id: string): WikiEntry | undefined { + return byId.get(id); +} diff --git a/graph-ui/tsconfig.tsbuildinfo b/graph-ui/tsconfig.tsbuildinfo index a0fd99ce3..d93723852 100644 --- a/graph-ui/tsconfig.tsbuildinfo +++ b/graph-ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/rpc.ts","./src/components/changestab.tsx","./src/components/commandk.tsx","./src/components/controltab.tsx","./src/components/dashboardtab.tsx","./src/components/displaysettingsmenu.tsx","./src/components/edgelines.tsx","./src/components/errorboundary.tsx","./src/components/filterpanel.tsx","./src/components/flowstab.tsx","./src/components/graphloader.tsx","./src/components/graphscene.test.ts","./src/components/graphscene.tsx","./src/components/graphtab.deadcode.test.tsx","./src/components/graphtab.filters.test.tsx","./src/components/graphtab.regions.test.tsx","./src/components/graphtab.test.ts","./src/components/graphtab.tsx","./src/components/missedcallout.tsx","./src/components/modulestab.tsx","./src/components/nodecloud.tsx","./src/components/nodedetailpanel.showmore.test.tsx","./src/components/nodedetailpanel.test.tsx","./src/components/nodedetailpanel.tsx","./src/components/nodelabels.tsx","./src/components/nodetooltip.tsx","./src/components/overviewtab.tsx","./src/components/projectcard.tsx","./src/components/promptbasket.tsx","./src/components/regionpanel.tsx","./src/components/resizehandle.tsx","./src/components/sidebar.tsx","./src/components/statstab.test.tsx","./src/components/statstab.tsx","./src/components/symboltab.tsx","./src/components/tabbar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/hooks/usegraphdata.test.ts","./src/hooks/usegraphdata.ts","./src/hooks/useprojects.ts","./src/lib/atlas.test.ts","./src/lib/atlas.ts","./src/lib/colors.ts","./src/lib/composer.ts","./src/lib/density.test.ts","./src/lib/density.ts","./src/lib/findings.test.ts","./src/lib/findings.ts","./src/lib/firstread.ts","./src/lib/i18n.test.ts","./src/lib/i18n.ts","./src/lib/regions.test.ts","./src/lib/regions.ts","./src/lib/treemap.ts","./src/lib/types.ts","./src/lib/utils.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/rpc.ts","./src/components/changestab.tsx","./src/components/commandk.tsx","./src/components/controltab.tsx","./src/components/dashboardtab.tsx","./src/components/displaysettingsmenu.tsx","./src/components/edgelines.tsx","./src/components/errorboundary.tsx","./src/components/filterpanel.tsx","./src/components/flowstab.tsx","./src/components/graphloader.tsx","./src/components/graphscene.test.ts","./src/components/graphscene.tsx","./src/components/graphtab.deadcode.test.tsx","./src/components/graphtab.filters.test.tsx","./src/components/graphtab.regions.test.tsx","./src/components/graphtab.test.ts","./src/components/graphtab.tsx","./src/components/halolayer.tsx","./src/components/metricchip.tsx","./src/components/minimap.tsx","./src/components/missedcallout.tsx","./src/components/modulestab.tsx","./src/components/nodecloud.tsx","./src/components/nodedetailpanel.showmore.test.tsx","./src/components/nodedetailpanel.test.tsx","./src/components/nodedetailpanel.tsx","./src/components/nodelabels.tsx","./src/components/nodetooltip.tsx","./src/components/overviewtab.tsx","./src/components/projectcard.tsx","./src/components/projectswitcher.tsx","./src/components/promptbasket.tsx","./src/components/regionpanel.tsx","./src/components/regiontooltip.tsx","./src/components/resizehandle.tsx","./src/components/sidebar.tsx","./src/components/statstab.test.tsx","./src/components/statstab.tsx","./src/components/symboltab.tsx","./src/components/tabbar.tsx","./src/components/triggertree.tsx","./src/components/wikipanel.test.tsx","./src/components/wikipanel.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/hooks/usegraphdata.test.ts","./src/hooks/usegraphdata.ts","./src/hooks/useprojects.ts","./src/lib/atlas.test.ts","./src/lib/atlas.ts","./src/lib/colors.ts","./src/lib/composer.ts","./src/lib/density.test.ts","./src/lib/density.ts","./src/lib/findings.test.ts","./src/lib/findings.ts","./src/lib/firstread.ts","./src/lib/flowgroup.test.ts","./src/lib/flowgroup.ts","./src/lib/i18n.test.ts","./src/lib/i18n.ts","./src/lib/regions.test.ts","./src/lib/regions.ts","./src/lib/treemap.ts","./src/lib/types.ts","./src/lib/utils.ts","./src/lib/why.test.ts","./src/lib/why.ts","./src/lib/whyfetch.ts","./src/wiki/entries.test.ts","./src/wiki/entries.ts"],"version":"5.9.3"} \ No newline at end of file