Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,15 @@ names are confidential.

The viewer works without a cloud backend. Deterministic scenes cover the system map,
request and asynchronous flows, complete data catalog and focused table ERD,
migrations, scheduled jobs, source files, risks, and Delivery & Runtime. Delivery
switches independently between development, staging, and production, so unrelated
environment topology is not mixed into one unreadable map.
migrations, scheduled jobs, source files, risks, deployment, runtime topology,
environment comparison, and configuration contracts.

Operations are deliberately separated. **Deployment** follows CI/CD jobs, Docker
build stages, images, and releases. **Runtime** follows ingress, services, workloads,
containers, ConfigMaps, and Secret names. Both switch independently between
development, staging, and production. **Environments** compares those scopes without
mixing their complete topologies into one unreadable map. Secret values are never
stored or displayed.

Large scenes use adaptive detail: off-screen elements are not rendered, distant
cards switch to a lightweight form, edge labels appear when useful, and animation
Expand Down
200 changes: 137 additions & 63 deletions assets/viewer/index.template.html

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions docs/CLAUDE-DESIGN-BRIEF.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ question, show direction clearly, and avoid an undifferentiated force-directed g
- Focused scene: schedule -> handler -> work -> data/events -> external effects.
- Explicit empty state when no jobs are detected.

7. **Delivery & Runtime**
- Deterministic columns: CI/CD -> build -> deploy -> network -> configuration.
- Mandatory segmented environment control: Development, Staging, Production.
- Add Compare mode for Staging vs Production, highlighting missing, added, and changed components.
7. **Operations**
- Keep Deployment, Runtime, Environments, and Configuration as separate navigation items.
- Deployment columns: workflow -> CI/CD jobs -> build stages -> image artifact -> deployment target.
- Runtime columns: public entry -> routing -> workloads -> containers -> configuration.
- Deployment and Runtime use the same segmented environment control: Development, Staging, Production.
- Environments is a comparison view: one row per detected environment with separate delivery, runtime, and configuration summaries.
- Show workflows/jobs, Docker stages/images, Compose services, Kubernetes workloads/containers, replicas, resources, probes, services, ingress, ConfigMaps, and Secret names.
- Never show environment or Secret values. Use “names only, values never stored” where needed.
- If an environment has no configuration, show that clearly while retaining shared CI/build stages.
- If an environment has no configuration, show that clearly while retaining shared CI/build stages where relevant.

8. **Configuration contract**
- Environment variable names grouped by environment and owning component.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dthreads/atlas",
"version": "0.3.0",
"version": "0.4.0",
"description": "Architecture intelligence for NestJS codebases.",
"keywords": [
"nestjs",
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const ATLAS_VERSION = "0.3.0";
export const ATLAS_VERSION = "0.4.0";
10 changes: 7 additions & 3 deletions tests/project.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,14 @@ test("covers the complete NestJS MVP architecture surface", async () => {
assert.doesNotMatch(viewerHtml, /mod\.metrics\.routes/);
assert.match(viewerHtml, /No data structures detected/);
assert.match(viewerHtml, /Scheduled jobs/);
assert.match(viewerHtml, /Delivery & Runtime/);
assert.match(viewerHtml, /Deployment/);
assert.match(viewerHtml, /Runtime Topology/);
assert.match(viewerHtml, /Environment Comparison/);
assert.match(viewerHtml, /Configuration Contract/);
assert.match(viewerHtml, /Database Schema/);
assert.match(viewerHtml, /COMPLETE CROSS-SCHEMA ERD/);
assert.match(viewerHtml, /ClickHouse Architecture/);
assert.match(viewerHtml, /Staging vs production/);
assert.match(viewerHtml, /Each row compares delivery, runtime and configuration evidence/);
assert.match(viewerHtml, /every detected table/);
assert.match(viewerHtml, /context stays visible/);
assert.match(viewerHtml, /choose one section to inspect/);
Expand All @@ -329,7 +331,9 @@ test("covers the complete NestJS MVP architecture surface", async () => {
assert.match(viewerHtml, /SERVICES & USE CASES/);
assert.match(viewerHtml, /No methods are expanded here/);
assert.match(viewerHtml, /sceneScheduleOverview/);
assert.match(viewerHtml, /sceneDelivery/);
assert.match(viewerHtml, /sceneDeployment/);
assert.match(viewerHtml, /sceneRuntime/);
assert.match(viewerHtml, /sceneEnvironments/);
assert.match(viewerHtml, /sceneConfiguration/);
assert.doesNotMatch(viewerHtml, /<(?:svg|g|rect|foreignObject|text|line|path)\b[^>]*\s(?:viewBox|x|y|width|height|x1|y1|x2|y2|d|transform|opacity)="\{\{/i);
assert.doesNotMatch(viewerHtml, /shopcore|deleteUserItems|ItemsService/);
Expand Down
76 changes: 76 additions & 0 deletions tests/viewer-interactions.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,82 @@ test("module dependencies and internals are separate views", async () => {
assert.match(viewer.scene().status, /One-hop context/);
});

test("module landscape renders directional import relationships", async () => {
const viewer = await createViewer();
const modules = viewer.state.D.nodes.filter((node) => node.type === "module").slice(0, 2);
assert.equal(modules.length, 2, "fixture must contain at least two modules");
const domain = {
id: "test-boundary",
name: "Test boundary",
desc: "Test module boundary",
counts: "2 modules",
modules: modules.map((node) => node.id),
allModules: modules.map((node) => node.id),
};
viewer.state.D.domains = [domain];
viewer.state.D.edges.push({
from: modules[0].id,
to: modules[1].id,
verb: "imports",
relation: "imports",
kind: "sync",
confidence: 1,
source: "ast",
});

const scene = viewer.sceneModuleGrid(domain.id);
const relationship = scene.edges.find((edge) => edge.from === modules[0].id && edge.to === modules[1].id);
assert.ok(relationship);
assert.equal(relationship.color, "#7452a8");
assert.match(scene.status, /directional import relationships/);
});

test("operations navigation separates deployment, runtime and environments", async () => {
const viewer = await createViewer();
let values = viewer.renderVals();
const labels = values.navItems.filter((item) => item.isItem).map((item) => item.label);
assert.ok(labels.includes("Deployment"));
assert.ok(labels.includes("Runtime"));
assert.ok(labels.includes("Environments"));
assert.ok(!labels.includes("Delivery & Runtime"));

viewer.state = { ...viewer.state, mode: "deployment", deliveryEnv: "production", sel: null };
const deployment = viewer.scene();
assert.deepEqual(deployment.cols.map((column) => column.label), ["WORKFLOW", "CI / CD JOBS", "BUILD", "ARTIFACT", "DEPLOY"]);
assert.ok(deployment.nodes.every((node) => !["container", "ingress", "config_map", "secret"].includes(viewer.node(node.id)?.type)));

viewer.state = { ...viewer.state, mode: "runtime", deliveryEnv: "production", sel: null };
const runtime = viewer.scene();
assert.deepEqual(runtime.cols.map((column) => column.label), ["PUBLIC ENTRY", "ROUTING", "WORKLOADS", "CONTAINERS", "CONFIGURATION"]);
assert.ok(runtime.nodes.every((node) => !["workflow", "pipeline_job", "build_stage"].includes(viewer.node(node.id)?.type)));

viewer.state = { ...viewer.state, mode: "environments", sel: null };
const environments = viewer.scene();
assert.deepEqual(environments.cols.map((column) => column.label), ["ENVIRONMENT", "DELIVERY", "RUNTIME", "CONFIGURATION"]);
assert.ok(environments.nodes.some((node) => viewer.node(node.id)?.type === "environment"));
assert.ok(environments.edges.length > 0);
});

test("edge grammar distinguishes structure, delivery, configuration and async flow", async () => {
const viewer = await createViewer();
const a = { x: 0, y: 0, w: 100, h: 40 };
const b = { x: 200, y: 0, w: 100, h: 40 };
const edge = (relation, kind = "sync") => viewer.linkEdge(a, b, { from: "service:UsersService", to: "table:users", verb: relation, relation, kind }, false, false, true);

const imports = edge("imports");
assert.equal(imports.color, "#7452a8");
assert.equal(imports.moving, false);
const deploys = edge("deploys");
assert.equal(deploys.color, "#c56a22");
assert.equal(deploys.moving, true);
const configures = edge("configures");
assert.equal(configures.color, "#8b5ca8");
assert.equal(configures.moving, false);
const publishes = edge("publishes_to", "async");
assert.equal(publishes.color, "#0f7895");
assert.equal(publishes.moving, true);
});

test("HTTP flow explicitly passes through its controller and drills into context", async () => {
const viewer = await createViewer();
const candidate = Object.entries(viewer.state.D.flows).map(([id, flow]) => {
Expand Down