feat(ui): the File view — outline in source order between two dependency rails (CG-46)
Clicking a file path now opens the file itself: what reaches into it, its symbols in source order, and what it reaches. The two rails count DEPENDENCIES, not import statements. The prototype drew `imports` edges; on this repo `src/graph/traversal.ts` imports two files and depends on four, because it reaches the LRU cache through a call no import names. A rail headed "Imports 2" would be quietly wrong about what changing the file would touch, which is the only question the screen answers — so the rails read `getFileDependencies` / `getFileDependents` and merge the import rows in for the symbol names. Imports that resolved to nothing indexed keep their own section rather than vanishing. The outline is windowed above 250 rows against a fixed 28px row: this repo's own fixtures hold a 1,681-symbol `.d.ts`, and paging it would hide the one thing an outline is for. `src/mcp/tools.ts` draws its 135 rows whole. `/api/file` gains `topLevel.calls` — module-level calls out of the file node — so a file that RUNS something offers the badge that opens it as a symbol, the only place code belonging to no symbol can be read. File results in the search palette and the entry-point list now land here rather than on the file node's Symbol view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2ad836d935
commit
58dad12f89
@@ -164,6 +164,57 @@ export interface WireSource {
|
||||
highlight?: WireHighlight;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- file view -- */
|
||||
|
||||
/** A row in the file outline — a symbol, its nesting and its edge counts. */
|
||||
export interface WireOutlineEntry extends WireNodeRef {
|
||||
/** Containing symbol within this file, or null for a top-level one. */
|
||||
parentId: string | null;
|
||||
/** Nesting depth from the top level of the file, starting at 0. */
|
||||
depth: number;
|
||||
fanIn: number;
|
||||
fanOut: number;
|
||||
}
|
||||
|
||||
/** One file at the far end of an import rail, with the symbols the edges name. */
|
||||
export interface WireImportRow {
|
||||
file: string;
|
||||
test: boolean;
|
||||
symbols: Array<{ id: string; name: string; kind: string; line: number }>;
|
||||
symbolCount: number;
|
||||
}
|
||||
|
||||
export interface WireFilePayload {
|
||||
file: {
|
||||
path: string;
|
||||
language: string;
|
||||
size: number;
|
||||
modifiedAt: number;
|
||||
indexedAt: number;
|
||||
contentHash: string;
|
||||
nodeCount: number;
|
||||
generated: boolean;
|
||||
test: boolean;
|
||||
errors: string[];
|
||||
/** The file node's own id, so the viewer can open the file AS a symbol. */
|
||||
id: string | null;
|
||||
};
|
||||
/** Calls made outside every definition — module-level code. */
|
||||
topLevel: { calls: number };
|
||||
/** The file changed on disk since it was indexed; the outline's lines shifted. */
|
||||
drift: boolean;
|
||||
outline: WireList<WireOutlineEntry>;
|
||||
/** `imports` edges only — a subset of `dependencies`, with symbol names. */
|
||||
imports: WireList<WireImportRow>;
|
||||
importedBy: WireList<WireImportRow>;
|
||||
/** Import statements that resolved to nothing indexed: packages, builtins. */
|
||||
unresolvedImports: Array<{ name: string; line: number }>;
|
||||
/** Every file this one reaches by any cross-file edge — `getFileDependencies`. */
|
||||
dependencies: string[];
|
||||
/** Every file that reaches into this one — `getFileDependents`. */
|
||||
dependents: string[];
|
||||
}
|
||||
|
||||
export interface WireBlastScale {
|
||||
maxDirect: number;
|
||||
maxWithinHops: number;
|
||||
@@ -354,6 +405,13 @@ export function fetchEntryPoints(
|
||||
return getJson<WireEntryPoints>(`api/entrypoints${query ? `?${query}` : ''}`, signal);
|
||||
}
|
||||
|
||||
export function fetchFile(path: string, signal?: AbortSignal): Promise<WireFilePayload> {
|
||||
// Paths carry '/'; encode per segment so `api/file/src/mcp/tools.ts` stays
|
||||
// readable and a segment with a reserved character still round-trips.
|
||||
const encoded = path.split('/').map(encodeURIComponent).join('/');
|
||||
return getJson<WireFilePayload>(`api/file/${encoded}`, signal);
|
||||
}
|
||||
|
||||
export function fetchSource(
|
||||
file: string,
|
||||
from: number,
|
||||
|
||||
Reference in New Issue
Block a user