diff --git a/README.md b/README.md
index 923a7db47..3210a2b60 100644
--- a/README.md
+++ b/README.md
@@ -131,9 +131,9 @@ Package-manager setup (npm, PyPI, or Go) verifies and publishes a coherent priva
The ordinary `cli` mode is intentionally separate: it runs one command locally and never starts or connects to the coordination daemon, registers a daemon session, or starts watchers/UI. Its only shared state is the OS admission barrier plus per-project locks for graph mutations. While the command is running, a temporary monitor lets activation cancel that operation and its supervised worker safely; the monitor exits with the command and never becomes a standing daemon. See [CLI Mode](#cli-mode) for details.
-### CBM Atlas — the human interface
+### CBM Lens Beta — the human interface
-The MCP tools are the agent's interface to the code graph; **CBM Atlas** is the human's. It is built into the binary — every install on every channel has it:
+The MCP tools are the agent's interface to the code graph; **CBM Lens Beta** is the human's. It is built into the binary — every install on every channel has it:
```bash
codebase-memory-mcp --ui=true --port=9749
@@ -141,9 +141,9 @@ codebase-memory-mcp --ui=true --port=9749
Open `http://localhost:9749` in your browser. The UI is owned by the shared coordination daemon, so concurrent agent sessions do not start duplicate HTTP servers.
-Atlas is **CodeAtlasWeb**, a reading IDE built by [Bernhard Jackiewicz](https://github.com/BernhardJackiewicz) on the CBM read surface (design and tracking in #1964). It runs air-gapped on loopback, reaches no cloud, and every panel names where its facts come from; what the index did not record is shown as a gap, never filled in with a guess.
+CBM Lens builds on **CodeAtlasWeb**, a reading IDE built by [Bernhard Jackiewicz](https://github.com/BernhardJackiewicz) on the CBM read surface (design and tracking in #1964). It runs air-gapped on loopback, reaches no cloud, and every panel names where its facts come from; what the index did not record is shown as a gap, never filled in with a guess.
-When something in Atlas does not load, the page has already written it down: its console output, uncaught errors and failed requests go to `${CBM_CACHE_DIR}/logs/ui.log` (default `~/.cache/codebase-memory-mcp/logs/ui.log`, one JSON line per entry, rotated once at 5 MiB). The projects panel (alt+p) shows the tail under "This server", `GET http://localhost:9749/api/ui-log?lines=200` returns it, and a bug report can attach the file.
+When something in CBM Lens does not load, the page has already written it down: its console output, uncaught errors and failed requests go to `${CBM_CACHE_DIR}/logs/ui.log` (default `~/.cache/codebase-memory-mcp/logs/ui.log`, one JSON line per entry, rotated once at 5 MiB). The projects panel (alt+p) shows the tail under "This server", `GET http://localhost:9749/api/ui-log?lines=200` returns it, and a bug report can attach the file.
- **Explorer and reader** — the indexed file tree and a read-only Monaco reader whose source comes from the index.
- **Semantic twin** — follows the caret: what the symbol under it holds, calls, raises and touches, as facts or as pseudocode, pitched at who is reading.
diff --git a/graph-ui/README.md b/graph-ui/README.md
index 60795bab5..514df1774 100644
--- a/graph-ui/README.md
+++ b/graph-ui/README.md
@@ -1,4 +1,4 @@
-# CodeAtlasWeb
+# CBM Lens Beta
## In this repository
diff --git a/graph-ui/index.html b/graph-ui/index.html
index cd3517a37..7e195a941 100644
--- a/graph-ui/index.html
+++ b/graph-ui/index.html
@@ -5,7 +5,7 @@
-
CodeAtlas
+ CBM Lens Beta
diff --git a/graph-ui/package-lock.json b/graph-ui/package-lock.json
index 95fa659bb..ddac3822e 100644
--- a/graph-ui/package-lock.json
+++ b/graph-ui/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "codeatlasweb",
+ "name": "cbm-lens",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "codeatlasweb",
+ "name": "cbm-lens",
"version": "0.0.1",
"dependencies": {
"@huggingface/transformers": "4.2.0",
diff --git a/graph-ui/package.json b/graph-ui/package.json
index 64c379e78..82bce4026 100644
--- a/graph-ui/package.json
+++ b/graph-ui/package.json
@@ -1,5 +1,5 @@
{
- "name": "codeatlasweb",
+ "name": "cbm-lens",
"version": "0.0.1",
"private": true,
"type": "module",
diff --git a/graph-ui/src/app/AtlasChrome.test.tsx b/graph-ui/src/app/AtlasChrome.test.tsx
index de834f9f1..969b7f14d 100644
--- a/graph-ui/src/app/AtlasChrome.test.tsx
+++ b/graph-ui/src/app/AtlasChrome.test.tsx
@@ -162,8 +162,9 @@ describe('AtlasChrome', () => {
it('zeigt Marke und Versions-Chip in der Kopfzeile', async () => {
await render(props());
- expect(container.querySelector('.atlas-brand')?.textContent).toBe('CODEATLAS');
- expect(testId('atlas-version')?.textContent).toBe('v0.0.0-dirty');
+ expect(container.querySelector('.atlas-brand')?.textContent).toBe('CBM LENS Beta');
+ expect(container.querySelector('.atlas-brand-beta')?.textContent).toBe('Beta');
+ expect(testId('atlas-version')?.textContent).toBe('v0.0.0');
});
it('keeps project switching at the end of the header without a duplicate project chip', async () => {
@@ -177,18 +178,10 @@ describe('AtlasChrome', () => {
expect(header?.querySelector('[data-chip="sym"]')?.textContent).toContain('76');
});
- /*
- * Der Zustand des Arbeitsbaums steht neben der Fassung, nicht in ihr.
- *
- * Der Chip beantwortet "welche Fassung ist das" und `v1.0.0-dirty` waere
- * darauf eine Antwort, die es als Release nicht gibt. Ohne Zusatz steht
- * daneben nichts: ein leeres Element waere ein Platz, an dem jemand eine
- * Angabe vermutet.
- */
- it('haelt den Bau-Zusatz getrennt vom Versions-Chip', async () => {
+ it('omits build diagnostics from the header', async () => {
await render(props({ version: 'v1.0.0', buildSuffix: 'dirty' }));
expect(testId('atlas-version')?.textContent).toBe('v1.0.0');
- expect(testId('atlas-version-suffix')?.textContent).toBe('dirty');
+ expect(testId('atlas-version-suffix')).toBeNull();
});
it('zeigt gar kein Zusatz-Element, wenn der Baum sauber war', async () => {
diff --git a/graph-ui/src/app/AtlasChrome.tsx b/graph-ui/src/app/AtlasChrome.tsx
index becdfc58e..f7f1eeeb0 100644
--- a/graph-ui/src/app/AtlasChrome.tsx
+++ b/graph-ui/src/app/AtlasChrome.tsx
@@ -164,16 +164,7 @@ export interface AtlasChromeProps {
guidance?: Guidance;
/** Versions-Chip, zur Buildzeit injiziert. Nur die Fassung, ohne Zusatz. */
version: string;
- /**
- * Der Zusatz neben dem Chip: `dirty`, wenn beim Bauen etwas uncommitted war.
- *
- * Ein eigenes Element und nicht ein Anhaengsel im Chip, weil es eine andere
- * Aussage ist: der Chip nennt die Fassung, der Zusatz den Zustand des
- * Baums, aus dem sie gebaut wurde. Zusammengeschrieben liest sich das wie
- * eine Fassung namens "1.0.0-dirty", die es als Release nie gibt. Fehlt der
- * Zusatz, steht dort nichts: ein leeres Element waere ein Platz, an dem
- * jemand eine Angabe vermutet.
- */
+ /** Build diagnostics are accepted for compatibility but not shown in the header. */
buildSuffix?: string;
chips: Chip[];
tabs: TabDescriptor[];
@@ -625,15 +616,10 @@ export default function AtlasChrome(props: AtlasChromeProps): JSX.Element {
return (
{view === 'structure' && }
diff --git a/graph-ui/src/architecture/architecture-history.test.ts b/graph-ui/src/architecture/architecture-history.test.ts
index 1610174cc..64023335f 100644
--- a/graph-ui/src/architecture/architecture-history.test.ts
+++ b/graph-ui/src/architecture/architecture-history.test.ts
@@ -28,24 +28,26 @@ describe('the Architecture history entry (K27)', () => {
expect(initialArchitecturePlace('dependencies').view).toBe('overview');
});
- it('is a new step for every subtab and for what each subtab opens', () => {
+ it('identifies what each subtab opens without treating display settings as exploration', () => {
const views = ['overview', 'entryPoints', 'routes', 'hotspots', 'structure', 'behavior'] as const;
expect(new Set(views.map(view => key(at({ view }))))).toHaveProperty('size', views.length);
- // Overview: the opened area and file, and Plan or 3D.
+ // Overview: the opened area and file. Plan or 3D stays with that location.
const django = at({ spatial: { planar: false, areaPath: 'django' } });
expect(key(django)).not.toBe(key(start));
expect(key(at({ spatial: { planar: false, areaPath: 'django', filePath: 'django/shortcuts.py' } }))).not.toBe(key(django));
- expect(key(at({ spatial: { planar: true, areaPath: 'django' } }))).not.toBe(key(django));
- // Routes: the perspective and the filter an opened route group sets.
+ expect(key(at({ spatial: { planar: true, areaPath: 'django' } }))).toBe(key(django));
+ // Routes: explicit route-group navigation uses the perspective and filter.
+ // The hook replaces the current entry when these are changed by context controls.
const routes = at({ view: 'routes' });
expect(key({ ...routes, routes: 'endpoints' })).not.toBe(key(routes));
expect(key({ ...routes, routes: 'endpoints', filter: '/edit' })).not.toBe(key({ ...routes, routes: 'endpoints' }));
- // System structure: the focus and the expanded groups, in any order.
+ // System structure: the focus is exploration; expanded groups stay with it.
const structure = at({ view: 'structure' });
const focused = { ...structure, system: { expanded: [], focus: { id: 'g:django', label: 'django' } } };
expect(key(focused)).not.toBe(key(structure));
expect(key({ ...structure, system: { expanded: ['g:a', 'g:b'] } })).toBe(key({ ...structure, system: { expanded: ['g:b', 'g:a'] } }));
- expect(key({ ...structure, system: { expanded: ['g:a'] } })).not.toBe(key(structure));
+ expect(key({ ...structure, system: { expanded: ['g:a'] } })).toBe(key(structure));
+ expect(key(at({ view: 'hotspots', spatial: { planar: false, hotspotArea: 'django/db' } }))).not.toBe(key(at({ view: 'hotspots' })));
// Behavior: the start and the destination ("Reach").
expect(key(behavior(7, 'handle'))).not.toBe(key(behavior(8, 'get_autocommit')));
expect(key(behavior(7, 'handle', { targetId: 9, targetName: 'save' }))).not.toBe(key(behavior(7, 'handle')));
@@ -61,7 +63,7 @@ describe('the Architecture history entry (K27)', () => {
expect(key(behavior(7, 'handle'))).toBe(key(behavior(7, 'renamed', { from: symbol(3, 'main'), generation: 'g2' })));
// A Service map does not draw Plan or 3D.
expect(key(at({ view: 'routes', spatial: { planar: true } }))).toBe(key(at({ view: 'routes' })));
- expect(key(at({ view: 'routes', routes: 'endpoints', spatial: { planar: true } }))).not.toBe(key(at({ view: 'routes', routes: 'endpoints' })));
+ expect(key(at({ view: 'routes', routes: 'endpoints', spatial: { planar: true } }))).toBe(key(at({ view: 'routes', routes: 'endpoints' })));
});
it('names the target of Back and Forward', () => {
@@ -89,11 +91,11 @@ describe('the Architecture history entry (K27)', () => {
it('holds Plan or 3D in System structure and Behavior, and where a call chain stands (K27)', () => {
const structure = at({ view: 'structure' });
- // Plan or 3D is a step in every view that draws a scene, each with its own camera as before.
- expect(key({ ...structure, system: { expanded: [], structurePlanar: true } })).not.toBe(key(structure));
+ // Plan or 3D is saved context in every view that draws a scene, each with its own camera as before.
+ expect(key({ ...structure, system: { expanded: [], structurePlanar: true } })).toBe(key(structure));
expect(key({ ...structure, system: { expanded: [], behaviorPlanar: true } })).toBe(key(structure));
const reach = behavior(1, 'main', { targetId: 3, targetName: 'save' });
- expect(key({ ...reach, system: { ...reach.system, behaviorPlanar: true } })).not.toBe(key(reach));
+ expect(key({ ...reach, system: { ...reach.system, behaviorPlanar: true } })).toBe(key(reach));
expect(key({ ...reach, system: { ...reach.system, structurePlanar: true } })).toBe(key(reach));
// Another indexed path to the destination is a step; the operation on it and the page of direct calls are not.
const second = { ...reach, system: { ...reach.system, behavior: { ...reach.system.behavior!, position: { path: 1 } } } };
diff --git a/graph-ui/src/architecture/architecture-history.ts b/graph-ui/src/architecture/architecture-history.ts
index 04614f164..77e837946 100644
--- a/graph-ui/src/architecture/architecture-history.ts
+++ b/graph-ui/src/architecture/architecture-history.ts
@@ -2,12 +2,13 @@
* The Architecture entry for Back and Forward (hand test K27): which place the
* workspace shows, without what it loaded for it.
*
- * One entry is the subtab and what that subtab has opened: in Overview the area
- * or file, in Routes the perspective and the route filter (an opened route
- * group is that filter), in System structure the focus and the expanded groups,
+ * One stop is a meaningful exploration: in Overview the area or file, in
+ * Routes an explicitly opened route group, in System structure the focus,
* in Behavior the start, the destination, where a followed call began and where
- * the journey stands; and in every view that draws a scene, Plan or 3D. It holds
- * identities, names and small numbers only, never projections, scenes or
+ * the journey stands. Each entry also keeps its subtab, perspective, filter,
+ * expanded groups and Plan or 3D setting so returning restores that context.
+ * Context changes update the current stop. It holds identities, names and small
+ * numbers only, never projections, scenes or
* cameras, so nothing grows with the session. The model behind it is the one
* Galaxy uses (src/graph/navigation-history.ts); the concept is in
* docs/development/pr-2068-galaxy-history.md.
@@ -61,7 +62,7 @@ export interface ArchitectureHistoryEntry {
view: ArchitectureView;
spatial: SpatialPlace;
routes: RoutesPerspective;
- /** The Routes filter, as far as it is a step: a route group or typing that paused. */
+ /** The Routes filter: typing updates context; explicitly opening a route group creates a stop. */
filter: string;
system: SystemPlace;
}
@@ -106,8 +107,8 @@ const behaviorPath = (entry: ArchitectureHistoryEntry): number => (entry.view ==
const viewOf = (entry: ArchitectureHistoryEntry): ArchitectureView => (entry.view === 'dependencies' ? 'overview' : entry.view);
export const architectureHistoryOptions: NavigationHistoryOptions = {
- key: (entry) => JSON.stringify([viewOf(entry), ...location(entry), planar(entry),
- entry.view === 'structure' ? [...entry.system.expanded].sort() : null, behaviorPath(entry)]),
+ // View, perspective and filter identify restored locations, but their controls replace the current entry.
+ key: (entry) => JSON.stringify([viewOf(entry), ...location(entry), behaviorPath(entry)]),
recentKey: (entry) => JSON.stringify([viewOf(entry), ...location(entry)]),
};
diff --git a/graph-ui/src/architecture/architecture.css b/graph-ui/src/architecture/architecture.css
index 66ba9422b..998e3d294 100644
--- a/graph-ui/src/architecture/architecture.css
+++ b/graph-ui/src/architecture/architecture.css
@@ -45,27 +45,22 @@
}
.atlas-arch-tab[aria-pressed="true"] { color: var(--atlas-fg); border-bottom-color: var(--atlas-phosphor); }
.atlas-arch-tab:hover { color: var(--atlas-fg); background: var(--atlas-panel); }
-/*
- * K27: one Back, Forward and Recent for the whole workspace and the tabs share a row. The menu sits outside the
- * scrolling tab strip. Hand test 2026-10-04 (A1): the controls come first and read as in Galaxy, "← Back" and
- * "Forward →" while the tabs fit, the glyphs alone once they would scroll (`data-fit`, galaxy/toolbar-fit.tsx).
- */
-.atlas-arch-tabrow { display: flex; align-items: center; gap: 14px; border-bottom: 1px solid var(--atlas-line); margin-bottom: 16px; }
+/* Exploration history belongs to the current scope; tabs only choose a lens. */
+.atlas-arch-tabrow { display: flex; border-bottom: 1px solid var(--atlas-line); margin-bottom: 16px; }
.atlas-arch-tabrow > .atlas-arch-tabs { flex: 1 1 auto; min-width: 0; border-bottom: 0; margin-bottom: 0; }
-.atlas-arch-history { display: flex; align-items: center; gap: 4px; flex: none; position: relative; }
-.atlas-arch-history > button, .atlas-arch-recent > summary {
- border: 1px solid var(--atlas-line); border-radius: 6px; background: var(--atlas-panel); color: var(--atlas-fg);
- min-width: 30px; min-height: 30px; padding: 3px 10px; line-height: 1.4; white-space: nowrap;
+.atlas-arch-context { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
+.atlas-arch-context > span { max-width: min(32rem, 70vw); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--atlas-dim); }
+.atlas-arch-history { display: flex; align-items: center; gap: 2px; flex: none; position: relative; }
+.atlas-architecture .atlas-arch-history > button, .atlas-arch-recent > summary {
+ display: grid; place-items: center; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--atlas-dim);
+ width: 32px; height: 32px; min-width: 32px; min-height: 32px; padding: 6px; line-height: 1;
}
-.atlas-arch-history > button:hover:not(:disabled), .atlas-arch-recent > summary:hover { background: var(--atlas-panel-raised); border-color: var(--atlas-dim); }
-.atlas-arch-history > button:disabled { opacity: 0.4; }
-.atlas-arch-history .atlas-fit-narrow { display: none; }
-.atlas-arch-history .atlas-fit-narrow::before { content: attr(data-label); }
-.atlas-arch-tabrow:is([data-fit=compact], [data-fit=tight], [data-fit=wrap]) .atlas-arch-history .atlas-fit-wide { display: none; }
-.atlas-arch-tabrow:is([data-fit=compact], [data-fit=tight], [data-fit=wrap]) .atlas-arch-history .atlas-fit-narrow { display: inline; }
-.atlas-arch-tabrow:is([data-fit=compact], [data-fit=tight], [data-fit=wrap]) .atlas-arch-history > button { padding-inline: 8px; }
-.atlas-arch-recent { position: relative; }
-.atlas-arch-recent > summary { display: block; cursor: pointer; list-style: none; text-align: center; color: var(--atlas-dim); padding-inline: 8px; }
+.atlas-architecture .atlas-arch-history > button:hover:not(:disabled), .atlas-arch-recent > summary:hover { background: var(--atlas-panel-raised); color: var(--atlas-fg); border-color: var(--atlas-line); }
+.atlas-architecture .atlas-arch-history > button:disabled { opacity: 0.28; cursor: default; }
+.atlas-arch-history svg { display: block; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
+.atlas-arch-history :is(button, summary):focus-visible { outline: 2px solid var(--atlas-phosphor); outline-offset: 2px; }
+.atlas-arch-recent { position: static; }
+.atlas-arch-recent > summary { cursor: pointer; list-style: none; }
.atlas-arch-recent > summary::-webkit-details-marker { display: none; }
.atlas-arch-recent-menu {
position: absolute; top: calc(100% + 6px); left: 0; z-index: 30; width: min(24rem, calc(100vw - 3rem)); max-height: 20rem; overflow: auto;
diff --git a/graph-ui/src/architecture/tab-row-position.test.ts b/graph-ui/src/architecture/tab-row-position.test.ts
index 0125801a5..f37f43885 100644
--- a/graph-ui/src/architecture/tab-row-position.test.ts
+++ b/graph-ui/src/architecture/tab-row-position.test.ts
@@ -1,18 +1,15 @@
/*
- * Review of K40: since A1 Back, Forward and the subtabs share one row, but the
- * row jumped between Overview, Routes and Hotspots on one side and System
- * structure and Behavior on the other: those two set the view's frame to 16 px
- * instead of 28 and 32 and made the tabs narrower. "← Back" stood at x 32, y 98
- * in the first three and at x 16, y 86 in the other two. No rule for the
- * system views may set the view's frame or the row differently, so the row
- * stands alike in every subtab.
+ * Review of K40: the subtab row must stay in the same place across Overview,
+ * Routes, Hotspots, System structure and Behavior. History now lives beside
+ * each view's location, so those local controls may have their own layout.
+ * No system-view rule may change the workspace frame or the tabs themselves.
*/
import { readFileSync } from 'node:fs';
import { expect, it } from 'vitest';
const SHEETS = ['architecture.css', 'system-architecture.css', 'behavior-journey.css', 'spatial-architecture.css', 'container-map.css', 'architecture-scene.css', 'source-evidence.css']
.map((name) => ({ name, css: readFileSync(new URL(`./${name}`, import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '') }));
-const ROW = /\.atlas-arch-(tabrow|tabs|tab|history|recent)\b/;
+const ROW = /\.atlas-arch-(tabrow|tabs|tab)\b/;
const BOX = /^(padding|margin)(-|$)/;
it('K40: System structure and Behavior keep the subtab row where the other subtabs have it', () => {
@@ -22,7 +19,7 @@ it('K40: System structure and Behavior keep the subtab row where the other subta
for (const selector of match[1]!.split(',').map((item) => item.trim())) {
if (!selector.includes('[data-system-view')) continue;
const properties = match[2]!.split(';').map((part) => part.split(':')[0]!.trim()).filter(Boolean);
- // The view's own frame, or anything about the row with the history buttons and the subtabs.
+ // Keep the workspace frame and the view-only tab row consistent.
const frame = /^\.atlas-architecture\[data-system-view[^\]]*\]$/.test(selector) && properties.some((property) => BOX.test(property));
if (frame || ROW.test(selector)) offending.push(`${name}: ${selector} { ${properties.join(', ')} }`);
}
diff --git a/graph-ui/src/architecture/use-architecture-history.test.tsx b/graph-ui/src/architecture/use-architecture-history.test.tsx
new file mode 100644
index 000000000..888d12f30
--- /dev/null
+++ b/graph-ui/src/architecture/use-architecture-history.test.tsx
@@ -0,0 +1,184 @@
+// @vitest-environment jsdom
+import { act } from 'react';
+import { createRoot, type Root } from 'react-dom/client';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { initialArchitecturePlace, type ArchitectureHistoryEntry } from './architecture-history';
+import { useArchitectureHistory, type ArchitectureHistory, type ArchitectureHistoryKeys } from './use-architecture-history';
+
+let host: HTMLDivElement, root: Root;
+let current: ArchitectureHistory;
+let initial: ArchitectureHistoryEntry;
+const restored = vi.fn();
+function Probe({ active = true, escapeTaken = false }: Partial) {
+ current = useArchitectureHistory(() => initial, { active, escapeTaken, onRestore: restored });
+ return ;
+}
+const render = async (keys: Partial = {}) => {
+ await act(async () => { root.render(); });
+};
+const change = async (update: Parameters[0], context = false) => {
+ await act(async () => { current.navigate(update, context); });
+};
+const go = async (step: -1 | 1) => { await act(async () => { current.go(step); }); };
+const type = async (filter: string) => { await act(async () => { current.type(filter); }); };
+const area = (areaPath: string) => change(place => ({ ...place, spatial: { ...place.spatial, areaPath, filePath: undefined } }));
+const file = (filePath: string) => change(place => ({ ...place, spatial: { ...place.spatial, filePath } }));
+const keypress = async (key: string, target: EventTarget = window, modifiers: KeyboardEventInit = {}) => {
+ const event = new KeyboardEvent('keydown', { key, altKey: true, bubbles: true, cancelable: true, ...modifiers });
+ await act(async () => { target.dispatchEvent(event); });
+ return event;
+};
+
+beforeEach(() => {
+ (globalThis as unknown as Record).IS_REACT_ACT_ENVIRONMENT = true;
+ initial = initialArchitecturePlace('overview');
+ restored.mockReset();
+ host = document.createElement('div'); document.body.append(host); root = createRoot(host);
+});
+afterEach(async () => { await act(async () => { root.unmount(); }); host.remove(); });
+
+describe('Architecture exploration history', () => {
+ it('updates tabs, perspective and scene settings in place while keeping Forward available', async () => {
+ await render();
+ await area('src');
+ await file('src/main.ts');
+ await go(-1);
+ const forward = current.forward;
+
+ await change(place => ({ ...place, view: 'routes' }), true);
+ await change(place => ({ ...place, routes: 'endpoints' }), true);
+ await change(place => ({ ...place, spatial: { ...place.spatial, planar: true } }));
+ await change(place => ({ ...place, system: { ...place.system, expanded: ['g:src'], structurePlanar: true, behaviorPlanar: true } }));
+
+ expect(current.history.entries).toHaveLength(3);
+ expect(current.history.index).toBe(1);
+ expect(current.forward).toBe(forward);
+ expect(current.history.entries[1]).toBe(current.place);
+ const context = current.place;
+ await go(1);
+ expect(current.place).toBe(forward);
+ expect(current.place.view).toBe('overview');
+ expect(current.place.spatial.planar).toBe(false);
+ await go(-1);
+ expect(current.place).toBe(context);
+ expect(current.place).toMatchObject({ view: 'routes', routes: 'endpoints', spatial: { planar: true }, system: { expanded: ['g:src'], structurePlanar: true, behaviorPlanar: true } });
+ expect(current.history.entries).toHaveLength(3);
+ });
+
+ it('saves System structure expansion and Plan as context without inserting stops', async () => {
+ initial = initialArchitecturePlace('structure');
+ await render();
+ await change(place => ({ ...place, system: { ...place.system, focus: { id: 'g:src', label: 'src' } } }));
+ await change(place => ({ ...place, system: { ...place.system, expanded: ['g:src'], structurePlanar: true } }));
+ expect(current.history.entries).toHaveLength(2);
+ await change(place => ({ ...place, system: { ...place.system, focus: { id: 'g:api', label: 'api' }, expanded: [], structurePlanar: false } }));
+ await go(-1);
+ expect(current.place.system).toMatchObject({ focus: { id: 'g:src' }, expanded: ['g:src'], structurePlanar: true });
+ expect(current.history.entries).toHaveLength(3);
+ });
+
+ it('drops the forward branch only when opening another exploration location', async () => {
+ await render();
+ await area('src');
+ await file('src/old.ts');
+ await go(-1);
+ await change(place => ({ ...place, spatial: { ...place.spatial, planar: true } }));
+ expect(current.forward?.spatial.filePath).toBe('src/old.ts');
+ await file('src/new.ts');
+ expect(current.history.entries.map(entry => entry.spatial.filePath)).toEqual([undefined, undefined, 'src/new.ts']);
+ expect(current.forward).toBeUndefined();
+ await go(-1);
+ expect(current.place.spatial).toMatchObject({ areaPath: 'src', planar: true });
+ await go(1);
+ expect(current.place.spatial.filePath).toBe('src/new.ts');
+ expect(current.history.entries).toHaveLength(3);
+ });
+
+ it('stores every filter edit immediately without a history stop', async () => {
+ initial = initialArchitecturePlace('routes');
+ await render();
+ for (const filter of ['/', '/a', '/api', '']) {
+ await type(filter);
+ expect(current.filter).toBe(filter);
+ expect(current.place.filter).toBe(filter);
+ expect(current.history.entries).toHaveLength(1);
+ expect(current.history.entries[0].filter).toBe(filter);
+ expect(current.back).toBeUndefined();
+ }
+ });
+
+ it('restores typed filters through immediate Back and Forward while explicit route groups create stops', async () => {
+ initial = initialArchitecturePlace('routes');
+ await render();
+ await type('/typed-at-root');
+ await change(place => ({ ...place, routes: 'endpoints', filter: '/users' }));
+ await type('/users/active');
+ await change(place => ({ ...place, filter: '/orders' }));
+ expect(current.history.entries.map(entry => entry.filter)).toEqual(['/typed-at-root', '/users/active', '/orders']);
+
+ await type('/orders/recent');
+ await go(-1);
+ expect(current.filter).toBe('/users/active');
+ await type('/users/new');
+ expect(current.forward?.filter).toBe('/orders/recent');
+ await go(1);
+ expect(current.filter).toBe('/orders/recent');
+ await go(-1);
+ expect(current.filter).toBe('/users/new');
+ expect(current.history.entries.map(entry => entry.filter)).toEqual(['/typed-at-root', '/users/new', '/orders/recent']);
+ expect(current.history.index).toBe(1);
+ });
+
+ it('replaces automatic starts and reindex resets, and restores them without pushing again', async () => {
+ initial = initialArchitecturePlace('behavior');
+ await render();
+ await change(place => ({ ...place, system: { ...place.system, behavior: { project: 'sample', id: 1, name: 'main' } } }), true);
+ expect(current.history.entries).toHaveLength(1);
+ await change(place => ({ ...place, system: { ...place.system, behavior: { project: 'sample', id: 2, name: 'save' } } }));
+ await go(-1);
+ await change(place => ({ ...place, system: { ...place.system, behavior: { project: 'sample', id: 3, name: 'reindexed main', generation: 'next' } } }), true);
+ expect(current.history.entries).toHaveLength(2);
+ expect(current.forward?.system.behavior?.id).toBe(2);
+ await go(1);
+ expect(current.place.system.behavior?.id).toBe(2);
+ await go(-1);
+ expect(current.place.system.behavior?.id).toBe(3);
+ expect(current.history.entries).toHaveLength(2);
+ expect(restored).toHaveBeenCalledTimes(3);
+ expect(restored.mock.calls.at(-1)?.map((entry: ArchitectureHistoryEntry) => entry.system.behavior?.id)).toEqual([2, 3]);
+ });
+
+ it('records another Behavior path while saving steps, pages and Plan with that path', async () => {
+ initial = { ...initialArchitecturePlace('behavior'), system: { expanded: [], behavior: { project: 'sample', id: 1, targetId: 2 } } };
+ await render();
+ await change(place => ({ ...place, system: { ...place.system, behaviorPlanar: true, behavior: { ...place.system.behavior!, position: { path: 0, step: 2, page: 1 } } } }));
+ expect(current.history.entries).toHaveLength(1);
+ await change(place => ({ ...place, system: { ...place.system, behavior: { ...place.system.behavior!, position: { path: 1 } } } }));
+ expect(current.history.entries).toHaveLength(2);
+ await go(-1);
+ expect(current.place.system.behavior?.position).toEqual({ path: 0, step: 2, page: 1 });
+ expect(current.place.system.behaviorPlanar).toBe(true);
+ });
+
+ it('keeps Alt+Left and Alt+Right available for meaningful stops and leaves typing alone', async () => {
+ await render();
+ await area('src');
+ await file('src/main.ts');
+ expect((await keypress('ArrowLeft')).defaultPrevented).toBe(true);
+ expect(current.place.spatial.filePath).toBeUndefined();
+ expect(current.history.index).toBe(1);
+ expect((await keypress('ArrowRight', host.querySelector('input')!)).defaultPrevented).toBe(false);
+ expect(current.history.index).toBe(1);
+ expect((await keypress('ArrowRight', window, { ctrlKey: true })).defaultPrevented).toBe(false);
+ expect((await keypress('ArrowRight')).defaultPrevented).toBe(true);
+ expect(current.place.spatial.filePath).toBe('src/main.ts');
+ expect(current.history.entries).toHaveLength(3);
+ expect((await keypress('ArrowRight')).defaultPrevented).toBe(false);
+
+ await render({ active: false });
+ expect((await keypress('ArrowLeft')).defaultPrevented).toBe(false);
+ await render({ escapeTaken: true });
+ expect((await keypress('ArrowLeft')).defaultPrevented).toBe(false);
+ expect(current.history.index).toBe(2);
+ });
+});
diff --git a/graph-ui/src/architecture/use-architecture-history.ts b/graph-ui/src/architecture/use-architecture-history.ts
index ed8f42fe2..0c05dbca8 100644
--- a/graph-ui/src/architecture/use-architecture-history.ts
+++ b/graph-ui/src/architecture/use-architecture-history.ts
@@ -1,17 +1,15 @@
/**
* Back and Forward for the Architecture workspace (hand test K27), wired the
- * way Galaxy wires K2: the current place is state, every change of its key is
+ * way Galaxy wires K2: the current place is state, meaningful exploration is
* a step in the shared bounded model, and a restored entry pushes nothing.
*
* Three refinements Architecture needs and Galaxy does not:
*
- * - Typing in the Routes filter becomes one step once it pauses
- * (`FILTER_STEP_MS`), not one step per key. A navigation, Back, Forward or
- * a recent jump while typing first records what the field showed, so no
- * typed text is lost.
+ * - Context controls (subtab, perspective and typed Routes filter) replace
+ * the current step immediately and preserve its forward branch.
* - A change the page makes itself (the suggested Behavior start, a reset
- * after reindexing) replaces the current step instead of adding one, so Back
- * never lands on a state the page would immediately fill in again.
+ * after reindexing) also replaces the current step, so Back never lands on
+ * a state the page would immediately fill in again.
* - Alt+Left and Alt+Right act only while this workspace is the active one,
* never while typing and never while another surface takes the keys.
*
@@ -25,19 +23,16 @@ import {
import { isTypingTarget } from '../app/keyboard';
import { architectureHistoryOptions as options, type ArchitectureHistoryEntry } from './architecture-history';
-/** Typing in the Routes filter becomes a step once it pauses this long. */
-export const FILTER_STEP_MS = 600;
-
type Update = (current: ArchitectureHistoryEntry) => ArchitectureHistoryEntry;
export interface ArchitectureHistory {
place: ArchitectureHistoryEntry;
- /** What the filter field shows, including typing that is not a step yet. */
+ /** The Routes filter saved with the current place, including the latest typing. */
filter: string;
history: NavigationHistory;
back?: ArchitectureHistoryEntry;
forward?: ArchitectureHistoryEntry;
- /** A navigation; `automatic` marks a change the page made itself. Stable across renders. */
+ /** A navigation; `automatic` replaces the current stop for context controls and page-made changes. Stable across renders. */
navigate: (update: Update, automatic?: boolean) => void;
type: (filter: string) => void;
go: (step: -1 | 1) => void;
@@ -54,11 +49,9 @@ export interface ArchitectureHistoryKeys {
export function useArchitectureHistory(initial: () => ArchitectureHistoryEntry, { active, escapeTaken, onRestore }: ArchitectureHistoryKeys): ArchitectureHistory {
const [place, setPlace] = useState(initial);
- const [draft, setDraft] = useState();
const [history, setHistory] = useState(() => emptyNavigationHistory());
const latest = useRef(place);
latest.current = place;
- const typed = useRef(undefined);
const restoringKey = useRef(undefined);
/** An automatic change: the key it was computed from and the key it leads to. */
const automaticStep = useRef<{ base: string; next: string } | undefined>(undefined);
@@ -84,69 +77,32 @@ export function useArchitectureHistory(initial: () => ArchitectureHistoryEntry,
// The current step keeps the newest details of its place (a name known only later); after the push above, so a new key is never a refresh.
useEffect(() => { setHistory((current) => refreshNavigation(current, place, options)); }, [place]);
- const clearDraft = useCallback(() => { typed.current = undefined; setDraft(undefined); }, []);
-
const navigate = useCallback((update: Update, automatic = false) => {
- const text = automatic ? undefined : typed.current;
- if (text !== undefined) {
- clearDraft();
- // What the field showed is a step of its own, before the navigation that leaves it.
- setHistory((current) => pushNavigation(current, { ...latest.current, filter: text }, options));
- }
setPlace((current) => {
- const result = update(text === undefined ? current : { ...current, filter: text });
+ const result = update(current);
if (automatic) {
const before = options.key(current), after = options.key(result);
if (after !== before) automaticStep.current = { base: before, next: after };
}
return result;
});
- }, [clearDraft]);
+ }, []);
- const type = useCallback((value: string) => { typed.current = value; setDraft(value); }, []);
- useEffect(() => {
- if (draft === undefined) return;
- const timer = setTimeout(() => {
- clearDraft();
- setPlace((current) => ({ ...current, filter: draft }));
- }, FILTER_STEP_MS);
- return () => clearTimeout(timer);
- }, [draft, clearDraft]);
+ const type = useCallback((filter: string) => { navigate((current) => ({ ...current, filter }), true); }, [navigate]);
const apply = useCallback((entry: ArchitectureHistoryEntry) => {
restore.current?.(latest.current, entry);
- clearDraft();
automaticStep.current = undefined;
setPlace(entry);
- }, [clearDraft]);
- /** Typing that has not paused yet, as the step it is about to become; undefined while nothing is typed. */
- const typedStep = useCallback((): ArchitectureHistoryEntry | undefined =>
- (typed.current === undefined ? undefined : { ...latest.current, filter: typed.current }), []);
- /** The history with that typing as its newest step, the way the pause would record it. */
- const withTyping = useCallback((): NavigationHistory => {
- const entry = typedStep();
- return entry === undefined ? history : pushNavigation(history, entry, options);
- }, [history, typedStep]);
+ }, []);
const go = useCallback((step: -1 | 1) => {
- const typedEntry = typedStep();
- const base = withTyping();
- const entry = peekNavigation(base, step);
- if (entry === undefined) {
- // Forward while typing: the text is a new step and drops the forward branch, as after the pause.
- if (typedEntry === undefined) return;
- clearDraft();
- setHistory(base);
- setPlace(typedEntry);
- return;
- }
+ const entry = peekNavigation(history, step);
+ if (entry === undefined) return;
restoringKey.current = options.key(entry);
- setHistory(moveNavigation(base, step, options));
+ setHistory(moveNavigation(history, step, options));
apply(entry);
- }, [typedStep, withTyping, apply, clearDraft]);
- const jump = useCallback((entry: ArchitectureHistoryEntry) => {
- setHistory(withTyping());
- apply(entry);
- }, [withTyping, apply]);
+ }, [history, apply]);
+ const jump = useCallback((entry: ArchitectureHistoryEntry) => { apply(entry); }, [apply]);
useEffect(() => {
if (!active || escapeTaken) return;
@@ -155,13 +111,13 @@ export function useArchitectureHistory(initial: () => ArchitectureHistoryEntry,
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
if (isTypingTarget(event.target instanceof Element ? event.target : null)) return;
const step = event.key === 'ArrowLeft' ? -1 : 1;
- if (peekNavigation(withTyping(), step) === undefined) return;
+ if (peekNavigation(history, step) === undefined) return;
event.preventDefault();
go(step);
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
- }, [active, escapeTaken, withTyping, go]);
+ }, [active, escapeTaken, history, go]);
- return { place, filter: draft ?? place.filter, history, back: peekNavigation(history, -1), forward: peekNavigation(history, 1), navigate, type, go, jump };
+ return { place, filter: place.filter, history, back: peekNavigation(history, -1), forward: peekNavigation(history, 1), navigate, type, go, jump };
}
diff --git a/graph-ui/src/chat/chat-strings.ts b/graph-ui/src/chat/chat-strings.ts
index 37f2d727d..b42fffa1f 100644
--- a/graph-ui/src/chat/chat-strings.ts
+++ b/graph-ui/src/chat/chat-strings.ts
@@ -14,13 +14,13 @@
import { SIDECAR_PORT } from '../llm/sidecar';
/** The word over the panel. */
-export const CHAT_TITLE = 'ATLAS_CHAT';
+export const CHAT_TITLE = 'CBM LENS CHAT';
/** What the command line hint says when a question would be sent. */
-export const CHAT_HINT_READY = 'enter asks the atlas';
+export const CHAT_HINT_READY = 'enter asks CBM Lens';
/** What it says when the line looks like a question and the model is off. */
-export const CHAT_HINT_OFF = 'enter asks the atlas: the local model is off';
+export const CHAT_HINT_OFF = 'enter asks CBM Lens: the local model is off';
/** The sentence when the model is off and a question was typed. */
export const CHAT_OFF_MESSAGE =
diff --git a/graph-ui/src/i18n/messages.ts b/graph-ui/src/i18n/messages.ts
index 5a09fd89d..55ceccc3e 100644
--- a/graph-ui/src/i18n/messages.ts
+++ b/graph-ui/src/i18n/messages.ts
@@ -68,7 +68,8 @@ export const messages = {
},
/** Die Kopfzeile und die Flaechen, die zu keinem Panel gehoeren. */
app: {
- brand: 'CODEATLAS',
+ brand: 'CBM LENS',
+ releaseLabel: 'Beta',
/** Steht in der Tab-Leiste und im Breadcrumb, wenn nichts offen ist. */
noFileOpen: 'no file open',
/** Der Name, den eine Oberflaeche ohne Projekt traegt. */
@@ -147,14 +148,14 @@ export const messages = {
* Repository.
*/
help: {
- title: 'CODEATLAS HELP',
+ title: 'CBM LENS HELP',
subtitle: 'a reading surface over an indexed repository',
close: '[esc] close',
closeLabel: CLOSE,
whatTitle: 'What this is',
what: [
- 'CodeAtlasWeb reads a repository that the analysis server has indexed and explains it: '
+ 'CBM Lens reads a repository that the analysis server has indexed and explains it: '
+ 'the tree and the source, a twin of the symbol under the caret, a walk along the calls, '
+ 'the scope of a change, and the answers of a local model.',
'Everything on this screen comes from that index or from a file the server handed over, '
@@ -400,7 +401,7 @@ export const messages = {
* Beispiele, wie man den Chat nutzt, sonst weiss niemand, wie man es
* nutzt."
*/
- placeholder: 'type a command or ask the atlas...',
+ placeholder: 'type a command or ask CBM Lens...',
examplesLabel: 'what you can type here',
hintIdle: 'type 2 letters to search by meaning',
hintOpen: 'up/down picks, enter opens, esc closes',
@@ -422,7 +423,7 @@ export const messages = {
* Schweigen. Jetzt steht der Satz an der Stelle, an der die Antwort
* erwartet wird.
*/
- oneMoreLetter: 'one more letter, then the atlas searches by meaning',
+ oneMoreLetter: 'one more letter, then CBM Lens searches by meaning',
/*
* Die Sofort-Vorschlaege, und dass sie vorlaeufig sind.
*
@@ -648,7 +649,7 @@ export const messages = {
/** Der Rahmen des Antwort-Panels. Seine Saetze stehen in chat-strings.ts. */
chat: {
- panelLabel: 'atlas chat',
+ panelLabel: 'CBM Lens chat',
depthGroupLabel: 'neighbourhood depth',
/*
* `clear` bleibt der einzige Knopf, der loescht, und sagt es.
@@ -706,7 +707,7 @@ export const messages = {
* das wurde am 2026-08-29 gemessen, und der Satz sagt es so.
*/
settings: {
- title: 'CODEATLAS SETTINGS',
+ title: 'CBM LENS SETTINGS',
subtitle: 'which model answers, and everything that costs computing time',
panelLabel: 'settings',
close: '[esc] close',
@@ -974,7 +975,7 @@ export const messages = {
* not about one subject area.
*/
projects: {
- title: 'CODEATLAS PROJECTS',
+ title: 'CBM LENS PROJECTS',
subtitle: 'what this server has indexed, and how to give it more',
panelLabel: 'projects',
close: '[esc] close',
@@ -1100,7 +1101,7 @@ export const messages = {
/** Der Rahmen des Twins. Seine Fakten-Saetze stehen in twin/strings.ts. */
twin: {
/** Das Etikett des Leseprofils, mit dem dieses Produkt startet. */
- profileLabel: 'CodeAtlasWeb Twin',
+ profileLabel: 'CBM Lens Twin',
/** Der Kopf des Panels, solange kein Symbol vor dem Leser steht. */
noSymbol: 'no symbol',
},
diff --git a/graph-ui/src/impact/impact-strings.ts b/graph-ui/src/impact/impact-strings.ts
index cb527a572..ce07e09cb 100644
--- a/graph-ui/src/impact/impact-strings.ts
+++ b/graph-ui/src/impact/impact-strings.ts
@@ -69,7 +69,7 @@ export function impactRiskTooltip(level: 'low' | 'medium' | 'high', reasons: str
/** How a route was tied to the change. Two different strengths of claim. */
export const IMPACT_ENDPOINT_VIA_HANDLER = 'The registration names a handler this change reaches.';
export const IMPACT_ENDPOINT_VIA_FILE =
- 'The registration is written in a file this change reaches. CodeAtlas cannot name the handler here, so this is the file and not the function.';
+ 'The registration is written in a file this change reaches. CBM Lens cannot name the handler here, so this is the file and not the function.';
// Die Erzaehlung, ein Satz je Lesung -----------------------------------------
@@ -110,7 +110,7 @@ export function impactDownstreamSentence(count: number, distance: number): strin
export function impactEndpointSentence(paths: string[]): string {
if (paths.length === 0) {
- return 'No HTTP endpoint CodeAtlas recovered is registered in a file this change reaches.';
+ return 'No HTTP endpoint CBM Lens recovered is registered in a file this change reaches.';
}
return `${countOf(paths.length, 'endpoint is', 'endpoints are')} registered in a file `
+ `this change reaches: ${paths.join(', ')}.`;
@@ -152,7 +152,7 @@ export const IMPACT_EVIDENCE_TITLE = 'What this rests on';
export const IMPACT_EVIDENCE_SOURCES: Readonly> = {
detect_changes: 'the change set the analysis reported, plus the calls into it',
- architecture: 'the whole-project summary, including the routes CodeAtlas recovered',
+ architecture: 'the whole-project summary, including the routes CBM Lens recovered',
facts: 'the per-symbol readings: complexity, and callers flagged as test code',
};
@@ -192,7 +192,7 @@ export const IMPACT_MENU_LABEL = '[c]hange scope';
/** Der Satz darunter: was gelesen wird und was das Wort am Ende bedeutet. */
export const IMPACT_SUBLINE =
- 'Read from the change set the analysis reports, the calls into it, and the routes CodeAtlas recovered. '
+ 'Read from the change set the analysis reports, the calls into it, and the routes CBM Lens recovered. '
+ 'The word on the right is a rule applied to those readings, never a prediction.';
export const IMPACT_MODE_LABEL = 'Compare against';
@@ -244,7 +244,7 @@ export const IMPACT_DIRECT_EMPTY =
export const IMPACT_DOWNSTREAM_EMPTY =
'Nothing in the indexed workspace calls the changed symbols.';
export const IMPACT_ENDPOINTS_EMPTY =
- 'No route CodeAtlas recovered is registered in a file this change reaches.';
+ 'No route CBM Lens recovered is registered in a file this change reaches.';
export const IMPACT_TESTS_EMPTY = 'No affected symbol could be checked for test callers.';
/**
diff --git a/graph-ui/src/pseudocode/block-lead.test.ts b/graph-ui/src/pseudocode/block-lead.test.ts
index 7e08a20af..c5c64b01f 100644
--- a/graph-ui/src/pseudocode/block-lead.test.ts
+++ b/graph-ui/src/pseudocode/block-lead.test.ts
@@ -85,7 +85,7 @@ describe('the sentence over the block', () => {
it('leads with what cannot be checked when nothing is unused', () => {
const lead = blockLeadOf(document, importsOf([USED, UNCHECKABLE]), 'getOrder');
expect(lead.kind).toBe('unchecked-imports');
- expect(lead.text).toContain('CodeAtlas cannot check either way');
+ expect(lead.text).toContain('CBM Lens cannot check either way');
});
it('leads with what lies behind the calls when the imports check out', () => {
diff --git a/graph-ui/src/pseudocode/imports-group.test.ts b/graph-ui/src/pseudocode/imports-group.test.ts
index d0b9f0c45..271cadb58 100644
--- a/graph-ui/src/pseudocode/imports-group.test.ts
+++ b/graph-ui/src/pseudocode/imports-group.test.ts
@@ -252,7 +252,7 @@ describe('where a reader is sent, and what the group says about itself', () => {
expect(built.used).toBe(5);
expect(built.unused).toBe(2);
expect(built.unknown).toBe(0);
- expect(built.tally).toBe('5 used in this file, 2 not used in it, 0 that CodeAtlas cannot check.');
+ expect(built.tally).toBe('5 used in this file, 2 not used in it, 0 that CBM Lens cannot check.');
});
it('says how many entries the cap left out rather than trailing off', () => {
diff --git a/graph-ui/src/pseudocode/pseudocode-strings.ts b/graph-ui/src/pseudocode/pseudocode-strings.ts
index 845c36e03..1966b6138 100644
--- a/graph-ui/src/pseudocode/pseudocode-strings.ts
+++ b/graph-ui/src/pseudocode/pseudocode-strings.ts
@@ -106,7 +106,7 @@ export function pseudocodeCappedNote(hidden: number): string {
* the one way this feature could mislead without saying anything untrue.
*/
export const PSEUDOCODE_PARTIAL_SCOPE =
- 'CodeAtlas sampled this range rather than reading every line of it, so a very short member may be missing.';
+ 'CBM Lens sampled this range rather than reading every line of it, so a very short member may be missing.';
/**
* The footer, and the sentence that keeps the whole block honest.
@@ -169,7 +169,7 @@ export function pseudocodeLeadUnused(symbol: string, unused: number, total: numb
export function pseudocodeLeadUnchecked(symbol: string, unknown: number): string {
return `You cannot see this in the code: every name this file pulls in is one the index links to `
- + `${symbol}, except ${countOf(unknown, 'one', 'ones')} CodeAtlas cannot check either way.`;
+ + `${symbol}, except ${countOf(unknown, 'one', 'ones')} CBM Lens cannot check either way.`;
}
export function pseudocodeLeadBehind(steps: number): string {
@@ -179,7 +179,7 @@ export function pseudocodeLeadBehind(steps: number): string {
export function pseudocodeLeadNone(symbol: string, total: number): string {
if (total === 0) {
- return 'Nothing stands above the steps this time: CodeAtlas found no import in this file, and the '
+ return 'Nothing stands above the steps this time: CBM Lens found no import in this file, and the '
+ 'index records nothing behind the steps below.';
}
return `Nothing stands above the steps this time: every name this file pulls in is one the index links `
@@ -293,7 +293,7 @@ export function importsFindingHeading(unused: number, unknown: number, total: nu
+ 'as far as the index shows';
}
if (unknown > 0) {
- return `${IMPORTS_GROUP_HEADING}: ${countOf(unknown, 'name', 'names')} CodeAtlas cannot check `
+ return `${IMPORTS_GROUP_HEADING}: ${countOf(unknown, 'name', 'names')} CBM Lens cannot check `
+ 'against this file';
}
return `${IMPORTS_GROUP_HEADING}: every name here is linked to a symbol in this file`;
@@ -318,7 +318,7 @@ export function importsUnusedLine(name: string, module: string): string {
/** Said on an entry that could not be checked at all. */
export function importsUnknownLine(name: string, module: string): string {
- return `${name} from ${module}: CodeAtlas cannot tell whether this file uses it`;
+ return `${name} from ${module}: CBM Lens cannot tell whether this file uses it`;
}
/** Said on `import * as db from ...` and its equivalents in the other languages. */
@@ -344,7 +344,7 @@ export const IMPORTS_NOTE_UNUSED =
'The index answered every checked symbol in this file, and none of their recorded facts mentions this name.';
export const IMPORTS_NOTE_UNKNOWN =
- 'CodeAtlas does not have a complete answered reading for every callable in this file, so it will not claim the import is unused.';
+ 'CBM Lens does not have a complete answered reading for every callable in this file, so it will not claim the import is unused.';
export const IMPORTS_NOTE_NAMESPACE =
'A namespace import binds every export of the module under one name, and the index records a call through it under the member\'s name. So nothing here can be matched against the binding.';
@@ -353,7 +353,7 @@ export const IMPORTS_NOTE_SIDE_EFFECT =
'This statement binds no name, so there is nothing for the index to match it against. What the module does when it loads is not something the analysis records.';
export const IMPORTS_NOTE_INDEX_ONLY =
- 'This came from the dependency the index recorded rather than from the file\'s text, so CodeAtlas has the imported file and not the imported name.';
+ 'This came from the dependency the index recorded rather than from the file\'s text, so CBM Lens has the imported file and not the imported name.';
/** Said at the end of a group the display cap cut short. */
export function importsCappedNote(hidden: number, shown: number): string {
@@ -362,11 +362,11 @@ export function importsCappedNote(hidden: number, shown: number): string {
/** Said when the file's own text could not be read at all. */
export const IMPORTS_SOURCE_UNREAD =
- 'CodeAtlas could not read this file\'s text, so what follows is the dependencies the index recorded and not the import statements themselves.';
+ 'CBM Lens could not read this file\'s text, so what follows is the dependencies the index recorded and not the import statements themselves.';
/** Said when the file pulls in nothing at all. */
export const IMPORTS_EMPTY =
- 'The index records no dependency for this file and CodeAtlas found no import statement in it.';
+ 'The index records no dependency for this file and CBM Lens found no import statement in it.';
/**
* The standing sentence under the group, in both surfaces.
@@ -377,7 +377,7 @@ export const IMPORTS_EMPTY =
* the judgement about use comes from every callable declaration the file read.
*/
export const IMPORTS_SOURCE_NOTE =
- 'The dependency comes from the index; the statement, its line and the name it binds are CodeAtlas reading '
+ 'The dependency comes from the index; the statement, its line and the name it binds are CBM Lens reading '
+ 'this file. Use is checked against the recorded facts of every callable symbol in the file.';
/** The three markers. Short enough to sit beside a name, and never the only carrier of meaning. */
@@ -395,7 +395,7 @@ export const IMPORTS_SECTION_NOTE = IMPORTS_SOURCE_NOTE;
/** How the group's own tally reads, under the entries. */
export function importsTally(used: number, unused: number, unknown: number): string {
- return `${used} used in this file, ${unused} not used in it, ${unknown} that CodeAtlas cannot check.`;
+ return `${used} used in this file, ${unused} not used in it, ${unknown} that CBM Lens cannot check.`;
}
// ---------------------------------------------------------------------------
@@ -581,7 +581,7 @@ export function explainerWalkBound(depth: number, cap: number): string {
* entworfen, muss sagen, dass sie aus einem Index gerechnet ist.
*/
export const EXPLAINER_GENERATED_NOTE =
- 'Nobody drew this picture. CodeAtlas laid it out from what the index recorded for this walk, so it can '
+ 'Nobody drew this picture. CBM Lens laid it out from what the index recorded for this walk, so it can '
+ 'be incomplete about the code and exact about what was reported.';
/** Die Notiz im Bild, wenn eine Kante in eine schon besuchte Kette zurueckfuehrt. */
@@ -636,4 +636,4 @@ export const FLOW_LOADING = 'Reading what this symbol reaches.';
* hier nichts zu zeigen, und keiner davon heisst "dieses Symbol ruft nichts".
*/
export const FLOW_UNAVAILABLE =
- 'CodeAtlas has no walk for this symbol right now. Place the caret in an indexed function, or reindex the workspace.';
+ 'CBM Lens has no walk for this symbol right now. Place the caret in an indexed function, or reindex the workspace.';
diff --git a/graph-ui/src/search/command-examples.test.ts b/graph-ui/src/search/command-examples.test.ts
index 72e0d45a7..9e2f94e14 100644
--- a/graph-ui/src/search/command-examples.test.ts
+++ b/graph-ui/src/search/command-examples.test.ts
@@ -93,8 +93,8 @@ describe('commandPlaceholderFor', () => {
});
it('faellt auf die Gattung zurueck, statt einen Namen zu erfinden', () => {
- expect(commandPlaceholderFor(undefined, 'type a command or ask the atlas...'))
- .toBe('type a command or ask the atlas...');
+ expect(commandPlaceholderFor(undefined, 'type a command or ask CBM Lens...'))
+ .toBe('type a command or ask CBM Lens...');
});
it('sagt ueber der Liste, worum es geht', () => {
diff --git a/graph-ui/src/search/command-examples.ts b/graph-ui/src/search/command-examples.ts
index 940d0e802..55c69a979 100644
--- a/graph-ui/src/search/command-examples.ts
+++ b/graph-ui/src/search/command-examples.ts
@@ -127,7 +127,7 @@ export function commandExamplesFor(symbol: string): CommandExample[] {
{
id: 'question',
text: `Who calls ${symbol}?`,
- note: 'ask the atlas',
+ note: 'ask CBM Lens',
symbol,
},
];
diff --git a/graph-ui/src/styles/terminal.css b/graph-ui/src/styles/terminal.css
index 6777c9387..c80646c8a 100644
--- a/graph-ui/src/styles/terminal.css
+++ b/graph-ui/src/styles/terminal.css
@@ -85,6 +85,9 @@ body {
}
.atlas-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
margin: 0;
font-size: 15px;
font-weight: 700;
@@ -92,22 +95,23 @@ body {
color: var(--atlas-phosphor);
}
-.atlas-version {
- color: var(--atlas-dim);
+.atlas-brand-beta {
+ padding: 3px 6px;
+ border: 1px solid rgb(200 173 114 / 28%);
+ border-radius: 4px;
+ background: rgb(200 173 114 / 7%);
+ color: #c8ad72;
+ font-size: 10px;
+ font-weight: 600;
+ line-height: 1.2;
letter-spacing: 0.06em;
+ text-shadow: none;
}
-/*
- * Der Zustand des Arbeitsbaums, neben der Fassung und leiser als sie.
- *
- * Kleiner und gedimmt, weil er eine Randbemerkung ist: wer die Fassung sucht,
- * liest den Chip, und wer wissen will, ob ein Bild zu einem Commit gehoert,
- * findet daneben die Antwort.
- */
-.atlas-version-suffix {
+.atlas-version {
color: var(--atlas-dim);
- opacity: 0.7;
- font-size: 10px;
+ opacity: 0.65;
+ font-size: 9px;
letter-spacing: 0.06em;
}
diff --git a/graph-ui/src/styles/workspace.css b/graph-ui/src/styles/workspace.css
index 35d8212b8..cb8645347 100644
--- a/graph-ui/src/styles/workspace.css
+++ b/graph-ui/src/styles/workspace.css
@@ -2,7 +2,7 @@
.atlas-shell[data-has-status=true] { grid-template-rows: auto auto minmax(0, 1fr) auto; }
.atlas-header { gap: 12px; padding: 12px 18px; overflow: visible; position: relative; z-index: 20; border-bottom-color: var(--atlas-phosphor-mute); box-shadow: 0 2px 18px var(--atlas-glow); }
.atlas-brand { font-size: 15px; font-weight: 700; letter-spacing: .22em; color: var(--atlas-phosphor); text-shadow: 0 0 16px var(--atlas-glow); }
-.atlas-version, .atlas-version-suffix { font-size: 10px; color: var(--atlas-dim); }
+.atlas-version { font-size: 9px; color: var(--atlas-dim); }
.atlas-workspace-tabs { display: flex; gap: 4px; margin-left: 16px; }
.atlas-workspace-tabs button, .atlas-guidance, .atlas-tools-menu > summary { border: 1px solid transparent; border-radius: 2px; background: transparent; color: var(--atlas-dim); padding: 7px 12px; font: inherit; cursor: pointer; }
.atlas-workspace-tabs button[aria-selected=true] { background: var(--atlas-select); color: var(--atlas-phosphor); border-color: var(--atlas-phosphor-mute); box-shadow: inset 0 1px 0 var(--atlas-phosphor); }
diff --git a/graph-ui/src/traces/bug-wizard-strings.ts b/graph-ui/src/traces/bug-wizard-strings.ts
index a01197bc8..a9e8a00d3 100644
--- a/graph-ui/src/traces/bug-wizard-strings.ts
+++ b/graph-ui/src/traces/bug-wizard-strings.ts
@@ -80,7 +80,7 @@ export const BUG_WIZARD_ENTRY_TOOLTIP =
* the first would be honest and useless.
*/
export const BUG_WIZARD_NO_TRACES =
- 'No observed call came back for this symbol. CodeAtlas does not watch code while it runs; '
+ 'No observed call came back for this symbol. CBM Lens does not watch code while it runs; '
+ 'it reads back what somebody handed the analysis backend.';
export const BUG_WIZARD_NO_TRACES_HOW =
@@ -114,7 +114,7 @@ export const BUG_WIZARD_NO_TRACES_WHERE =
/** Said above the observed chains, so nobody reads them as a reading of the index. */
export const BUG_WIZARD_OBSERVED_NOTE =
- 'Read back from the analysis backend, along the calls the index connects. CodeAtlas did not '
+ 'Read back from the analysis backend, along the calls the index connects. CBM Lens did not '
+ 'observe any of this itself.';
/** How many times an observed call was seen, and under which run. */
diff --git a/graph-ui/src/twin/TwinPanel.test.tsx b/graph-ui/src/twin/TwinPanel.test.tsx
index 15db81380..3d4a3cf92 100644
--- a/graph-ui/src/twin/TwinPanel.test.tsx
+++ b/graph-ui/src/twin/TwinPanel.test.tsx
@@ -433,7 +433,7 @@ describe('wenn es nichts zu zeigen gibt', () => {
ir: undefined,
symbolName: 'nothing',
message: 'This file is not indexed yet.',
- hint: 'CodeAtlas is still reading this workspace.',
+ hint: 'CBM Lens is still reading this workspace.',
});
const empty = q('[data-testid="atlas-twin-empty"]');
expect(empty?.textContent).toContain('not indexed yet');
@@ -627,7 +627,7 @@ describe('die Pseudocode-Ansicht', () => {
expect(q('[data-testid="atlas-pseudocode-group"]')?.textContent)
.toContain('not used anywhere in this file as far as the index shows');
expect(q('[data-testid="atlas-pseudocode-tally"]')?.textContent)
- .toContain('that CodeAtlas cannot check');
+ .toContain('that CBM Lens cannot check');
});
it('nennt an jeder Schrittzeile Datei und Zeile des aufgerufenen Symbols', async () => {
diff --git a/graph-ui/src/twin/strings.ts b/graph-ui/src/twin/strings.ts
index b4dc9e3e8..11b303646 100644
--- a/graph-ui/src/twin/strings.ts
+++ b/graph-ui/src/twin/strings.ts
@@ -79,7 +79,7 @@ export function countOf(count: number, singular: string, plural: string): string
// ---------------------------------------------------------------------------
export const TWIN_EMPTY_MESSAGE = 'Place the cursor inside a function, method, or class.';
-export const TWIN_EMPTY_HINT = 'CodeAtlas follows the caret and explains whatever it lands in.';
+export const TWIN_EMPTY_HINT = 'CBM Lens follows the caret and explains whatever it lands in.';
// ---------------------------------------------------------------------------
// Twin identity and provenance
@@ -94,13 +94,13 @@ export const TWIN_FILE_NOT_INDEXED = 'This file is not indexed yet.';
/** Says what would change the sentence above. */
export const TWIN_FILE_NOT_INDEXED_HINT =
- 'CodeAtlas is still reading this workspace. Facts about this symbol appear as soon as it has.';
+ 'CBM Lens is still reading this workspace. Facts about this symbol appear as soon as it has.';
/** Shown while the IR for a symbol is being built. */
export const TWIN_LOADING = 'Reading what the index knows about this symbol.';
/** Shown when the IR call itself failed, which is a different failure from an empty fact. */
-export const TWIN_LOAD_FAILED = 'CodeAtlas could not reach the analysis backend for this symbol.';
+export const TWIN_LOAD_FAILED = 'CBM Lens could not reach the analysis backend for this symbol.';
/**
* What the flow switch in the twin head says, in both states.
@@ -550,7 +550,7 @@ export function proseMoreParts(hidden: number): string {
/** Said out loud, because the sentence above it was assembled rather than written. */
export const PROSE_GENERATED_NOTE =
- 'Nobody wrote that description. CodeAtlas put it together from what the index recorded, so it can be wrong about intent and right about counts.';
+ 'Nobody wrote that description. CBM Lens put it together from what the index recorded, so it can be wrong about intent and right about counts.';
export const PROSE_TESTS_NONE =
'Nothing that looks like a test calls this, so no automatic check would notice a change here.';
@@ -573,13 +573,13 @@ export const PLAIN_SUBJECT_ERRORS = 'How this can fail';
export function plainAbsence(state: KnowledgeState, subject: string): string {
switch (state) {
case 'notIndexed':
- return `${subject} is unknown: CodeAtlas has not read this file yet.`;
+ return `${subject} is unknown: CBM Lens has not read this file yet.`;
case 'unsupported':
- return `${subject} is unknown: CodeAtlas cannot see this for this language.`;
+ return `${subject} is unknown: CBM Lens cannot see this for this language.`;
case 'inferred':
return `${subject} was worked out from what calls it, not read from the code itself.`;
case 'unknown':
- return `${subject} is unknown: nothing answered when CodeAtlas asked.`;
+ return `${subject} is unknown: nothing answered when CBM Lens asked.`;
case 'ambiguous':
case 'known':
return '';
@@ -752,7 +752,7 @@ export const TERM_EXPLANATIONS: Readonly> = {
'a value this code takes from outside itself, such as a setting or a configuration key, rather than '
+ 'from what it was handed.',
'test caller':
- 'a caller that lives in test code. It is how CodeAtlas answers "is this checked", because this index '
+ 'a caller that lives in test code. It is how CBM Lens answers "is this checked", because this index '
+ 'records no test relation of its own for TypeScript.',
'entry point':
'a place the outside world can start this program: a route, a command, a handler. Work arrives here '
@@ -814,7 +814,7 @@ export const GROUND_LEAD_SITS_ON =
export const GROUND_LEAD_CARRIED =
'What holds this up: the callers, and whether the outside world can reach it directly.';
export const GROUND_LEAD_DEBTS =
- 'What is owed against this symbol, from the rules CodeAtlas applies to the recorded facts. Every line is a '
+ 'What is owed against this symbol, from the rules CBM Lens applies to the recorded facts. Every line is a '
+ 'rule over facts and not a fact, which is why they all wear the derived marker.';
export const GROUND_LEAD_LIMITS =
'What is not on this page, and why. This is part of the answer and not a footnote: an architect who cannot '
@@ -827,7 +827,7 @@ export const GROUND_EMPTY_CARRIED =
'Nothing carries this: no recorded caller, and nothing marking it as reachable from outside. A symbol with '
+ 'no carrier is either an entry the index cannot see or a piece nobody uses.';
export const GROUND_EMPTY_DEBTS =
- 'No rule fired against this symbol. That is not a clean bill: the rules CodeAtlas can apply are the ones '
+ 'No rule fired against this symbol. That is not a clean bill: the rules CBM Lens can apply are the ones '
+ 'listed under what this index stops at, and the ones it cannot apply stay silent either way.';
/** How many call sites reach into one module. */
@@ -877,7 +877,7 @@ export const LIMIT_NO_HANDLER_SITES =
'Raised error types are recorded; the places they are caught are not. A short error list is therefore not '
+ 'evidence that the failure is contained.';
export const LIMIT_NO_ROUTES =
- 'Routes for TypeScript are read out of the source text by CodeAtlas, not reported by the engine, so an '
+ 'Routes for TypeScript are read out of the source text by CBM Lens, not reported by the engine, so an '
+ 'entry point the engine did not mark can still exist.';
export const LIMIT_CALLS_DEDUPLICATED =
'Two calls to the same target inside one body are recorded once, with the last site. A count of call sites '
@@ -895,12 +895,12 @@ export const VOICE_TITLE =
export const VOICE_RUNNING = 'The local model is rewording the sentences above.';
export const VOICE_RESTORE_LABEL = 'Back to the built text';
export const VOICE_RESTORE_TITLE =
- 'Drop the reworded sentences and show the ones CodeAtlas assembled from the index.';
+ 'Drop the reworded sentences and show the ones CBM Lens assembled from the index.';
/** The marker beside a sentence the model worded. Same idiom as the state markers. */
export const VOICE_MARKER = '[~]';
export const VOICE_MARKER_NOTE =
- 'Worded by the local model from the sentence CodeAtlas built. The facts in it were checked against the '
+ 'Worded by the local model from the sentence CBM Lens built. The facts in it were checked against the '
+ 'original one by one; the wording is the model.';
export const VOICE_APPLIED =
'The sentences above were reworded by the local model. Every name, number, file and line in them was '
@@ -911,7 +911,7 @@ export function voiceRefused(reason: string): string {
}
export const VOICE_UNAVAILABLE =
- 'The local model is off, so these sentences are the ones CodeAtlas assembled. Nothing here waits for it.';
+ 'The local model is off, so these sentences are the ones CBM Lens assembled. Nothing here waits for it.';
export const FACET_LABELS: Readonly> = {
[Facet.Logic]: 'Logic',
@@ -1075,7 +1075,7 @@ export const RUNTIME_UNEXPECTED_TOOLTIP =
/** Why the runtime section wears its marker: nothing is missing from the index here. */
export const RUNTIME_SECTION_NOTE =
- 'Read from an imported recording, not from the index. CodeAtlas did not watch this run.';
+ 'Read from an imported recording, not from the index. CBM Lens did not watch this run.';
// The runtime badge in the evidence popover ---------------------------------
diff --git a/graph-ui/src/twin/twin-view-model.test.ts b/graph-ui/src/twin/twin-view-model.test.ts
index 42bd39787..3a945288f 100644
--- a/graph-ui/src/twin/twin-view-model.test.ts
+++ b/graph-ui/src/twin/twin-view-model.test.ts
@@ -273,7 +273,7 @@ describe('importsSection', () => {
});
it('schreibt die Bilanz ueber die Zeilen und die Herkunft darunter', () => {
- expect(built.text).toBe('1 used in this file, 1 not used in it, 0 that CodeAtlas cannot check.');
+ expect(built.text).toBe('1 used in this file, 1 not used in it, 0 that CBM Lens cannot check.');
expect(built.note).toContain('The dependency comes from the index');
expect(built.populated).toBe(true);
});
diff --git a/graph-ui/tests/scaffold/w2a.test.mjs b/graph-ui/tests/scaffold/w2a.test.mjs
index 7cb020aa5..2c9ef692f 100644
--- a/graph-ui/tests/scaffold/w2a.test.mjs
+++ b/graph-ui/tests/scaffold/w2a.test.mjs
@@ -24,7 +24,8 @@ test('AC1: design tokens and chrome are in place', () => {
'atlas-statusbar', 'atlas-tree', 'atlas-breadcrumb']) {
assert.ok(smoke.testids.includes(id), `data-testid ${id} wurde im Smoke nicht gesehen`);
}
- assert.ok(/CODEATLAS/.test(smoke.brandText), 'Marke CODEATLAS fehlt in der Kopfzeile');
+ // Archived W2 evidence predates the CBM Lens name; new smoke runs require the current brand.
+ assert.ok(/CBM LENS|CODEATLAS/.test(smoke.brandText), 'Frontend-Marke fehlt in der Kopfzeile');
assert.match(smoke.versionChip, /^v\d+\.\d+\.\d+/, 'Versions-Chip fehlt oder falsch');
// Spezifikations-Korrektur 2026-08-29 (Orchestrator, Nutzerauftrag):
// Der Test nagelte [f]ile fest, also ausgerechnet einen der vier
@@ -32,7 +33,7 @@ test('AC1: design tokens and chrome are in place', () => {
// sein. Verlangt bleibt das Buchstaben-Menue als Form, belegt am
// verdrahteten [a]tlas.
assert.ok(/\[a\]tlas/.test(smoke.menuText), 'Buchstaben-Menue fehlt');
- assert.ok(/type a command or ask the atlas/.test(smoke.commandPlaceholder),
+ assert.ok(/type a command or ask (?:CBM Lens|the atlas)/.test(smoke.commandPlaceholder),
'Kommandozeilen-Platzhalter fehlt');
});
diff --git a/graph-ui/tools/smoke-w2a.mjs b/graph-ui/tools/smoke-w2a.mjs
index 4ebb9fb4b..cadd25b17 100644
--- a/graph-ui/tools/smoke-w2a.mjs
+++ b/graph-ui/tools/smoke-w2a.mjs
@@ -534,14 +534,14 @@ async function main() {
const ok =
failure === null &&
missingTestids.length === 0 &&
- /CODEATLAS/.test(report.brandText) &&
+ /CBM LENS/.test(report.brandText) &&
/^v\d+\.\d+\.\d+/.test(report.versionChip) &&
// Nachgezogen am 2026-08-29 (W7a, Nutzerauftrag): der Lauf nagelte
// [f]ile fest, also ausgerechnet einen der vier Menuepunkte ohne
// Verdrahtung. Eine Attrappe darf keine Zusicherung sein. Verlangt
// bleibt das Buchstaben-Menue als Form, belegt am verdrahteten [a]tlas.
/\[a\]tlas/.test(report.menuText) &&
- /type a command or ask the atlas/.test(report.commandPlaceholder) &&
+ /type a command or ask CBM Lens/.test(report.commandPlaceholder) &&
report.treeEntries >= 10 &&
report.treeSource === '/api/tree' &&
report.openedFile === OPEN_FILE &&
diff --git a/graph-ui/tools/smoke-w8c.mjs b/graph-ui/tools/smoke-w8c.mjs
index a2773fb83..d8d2ff302 100644
--- a/graph-ui/tools/smoke-w8c.mjs
+++ b/graph-ui/tools/smoke-w8c.mjs
@@ -543,7 +543,7 @@ async function main() {
&& short.imports.top < short.firstLineTop;
report.importHonestyWordsKept = short.imports.entries
.some((entry) => entry.text.includes('not used by this symbol as far as the index shows'))
- && short.imports.tally.includes('that CodeAtlas cannot check')
+ && short.imports.tally.includes('that CBM Lens cannot check')
&& short.imports.heading.includes('as far as the index shows');
log(`Importgruppe: "${short.imports.heading}" / "${short.imports.tally}"`);
@@ -681,7 +681,7 @@ async function main() {
.filter((line) => !line.targetKnown)
.every((line) => line.target.includes('the index records no place'));
report.importHonestyWordsKept = report.importHonestyWordsKept
- && long.imports.tally.includes('that CodeAtlas cannot check');
+ && long.imports.tally.includes('that CBM Lens cannot check');
report.headSaysWhatTheFindingIs = report.headSaysWhatTheFindingIs
&& long.lead.text.length > 0
&& long.lead.kind.length > 0;