Skip to content
Open
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ or other refresh handlers run. Shared discovery, accepted source evidence,
PackageInventory, rail launch edges, ordinary sessions and each agent's
Canvas/Steps retain their own consumers; they are not legacy project topology.

SAP-3090 first disconnects `WorkspaceGraphView` from the shell and removes the
older-protocol session handoff. The following layer deletes its now-unreachable
browser modules. `agent-map-authority.spec.ts` includes omitted-catalog recovery
SAP-3090 disconnects `WorkspaceGraphView` from the shell and removes the
older-protocol session handoff at `42fcaccf`. The following layer deletes the
renderer, parser, layout, loader, navigation, announcement state, API methods,
mock topology and graph-only tests. Shared viewport behavior and its tests now
live together in `graph-viewport.ts` / `graph-viewport.test.ts`; Agent Map owns
the labels and controls it still uses. `agent-map-authority.spec.ts` includes omitted-catalog recovery
and exact keyboard tabs; `project-altitude.spec.ts` preserves pane geometry,
Steps restoration, independent disclosure and map/agent Back/Forward navigation.

Expand All @@ -54,7 +57,7 @@ this file as evidence that a host or recovery exercise passed.

| Gate | Reproducible evidence |
| --- | --- |
| Missing identity, exact recovery, unchanged conversation and no old requests/events | `web/e2e/agent-map-authority.spec.ts`; counters intercept read, refresh and navigation before cache/delay, and check event invalidations. |
| Missing identity, exact recovery, unchanged conversation and no old requests/events | `web/e2e/agent-map-authority.spec.ts`; browser network observation starts before boot and counts old read, refresh and navigation requests. Old event frames must leave catalog/workflow fetch counts, selection and session actions unchanged. |
| Exact node navigation, error rejection and session parity | `web/e2e/agent-map-navigation.spec.ts`, including Claude, Codex, archived/no sessions, delayed responses, Info/resource inspection and mobile. |
| Current HTTP authority and retained root/descendant sessions | `src/server/studio-workspace-wiring.test.ts`; protected 410 on all three legacy routes, no graph read/refresh/watch, no retained graph owners. |
| Shared discovery still works without the legacy API | `src/server/system-graph-freshness.test.ts`, `workspace-rescan.test.ts` and core workspace-watch broker/watcher suites. Preserve cold reads, edits/renames/deletes, superseded scan budgets, repository boundaries, lease retirement and symlink deduplication. |
Expand Down Expand Up @@ -112,7 +115,7 @@ An unavailable identity must remain a bounded error throughout recovery.
| SAP-3082 | Catalog identity, saved selection, private implementation bindings and protected resolution. |
| SAP-3084 | Node inspection/navigation and ordinary conversation/Canvas behavior. |
| SAP-3087 / SAP-3088 | Discovery freshness and shared workspace watcher ownership. |
| SAP-3090 | Remove older-protocol browser rendering, loaders, API methods, announcements and fixtures after this gate is reviewed. |
| SAP-3090 | Browser rendering, loaders, API methods, announcements and fixtures are removed in two dependent layers. Human review follows the complete cleanup stack; implementation does not authorize release. |
| SAP-3091 | Remove the unreachable graph runtime/router/store/invocation wiring; retain shared discovery, rail and per-agent graph helpers. |
| SAP-3086 / E8 assignee | Package/upgrade evidence, release decision, recovery owner and out-of-hours approver. Approval must be recorded, not assumed. |

Expand Down
55 changes: 17 additions & 38 deletions packages/harness/web/e2e/agent-map-authority.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { expect, test, type Page } from "@playwright/test";

type Probe = {
identity: "ready" | "missing-id" | "missing-project" | "older-protocol";
reads: number;
refreshes: number;
navigation: number;
invalidations: number;
states: number;
workflows: number;
activeSessionId: string | null;
Expand All @@ -23,11 +19,22 @@ type TestWindow = Window & {
};
};

const legacyRequests = new WeakMap<Page, [number, number, number]>();

async function open(
page: Page,
identity: Probe["identity"] = "ready",
project = "acme-app",
) {
// Observe real browser requests before boot, including accidental reads that
// would bypass a mock method or a deleted loader.
const requests: [number, number, number] = [0, 0, 0];
legacyRequests.set(page, requests);
page.on("request", (request) => {
const path = new URL(request.url()).pathname;
if (!/^\/api\/workspaces\/[^/]+\/system-graph(?:\/|$)/.test(path)) return;
requests[path.endsWith("/refresh") ? 1 : path.endsWith("/navigation") ? 2 : 0]++;
});
const setupErrors: string[] = [];
const recordPageError = (error: Error) => setupErrors.push(error.message);
page.on("pageerror", recordPageError);
Expand All @@ -52,8 +59,7 @@ async function open(
),
});
});
// Instrument entry to each legacy API method, before cache hits/delays. A
// successful map alone cannot prove an obsolete background read didn't run.
// Retained catalog reads and session actions remain independently observed.
await page.route("**/src/lib/api.ts", async (route) => {
const response = await route.fetch();
await route.fulfill({
Expand All @@ -64,26 +70,15 @@ async function open(
if (typeof MockApi !== "function") {
throw new Error("Authority fixture: api.ts no longer defines MockApi");
}
for (const method of ["getSystemGraph", "getSystemGraphNavigation", "getState", "getStudioCurrentWorkspace", "listWorkflows"]) {
for (const method of ["getState", "getStudioCurrentWorkspace", "listWorkflows"]) {
if (typeof MockApi.prototype[method] !== "function") {
throw new Error("Authority fixture: missing MockApi." + method);
}
}
const authority = window.__authority = {
identity: ${JSON.stringify(identity)}, reads: 0, refreshes: 0,
navigation: 0, invalidations: 0, states: 0, workflows: 0,
identity: ${JSON.stringify(identity)}, states: 0, workflows: 0,
projects: {}, preferenceReads: [], holdStates: false, heldStates: [], completedStates: 0,
};
const graphRead = MockApi.prototype.getSystemGraph;
MockApi.prototype.getSystemGraph = function(key, options) {
authority[options?.refresh ? "refreshes" : "reads"]++;
return graphRead.call(this, key, options);
};
const navigationRead = MockApi.prototype.getSystemGraphNavigation;
MockApi.prototype.getSystemGraphNavigation = function(...args) {
authority.navigation++;
return navigationRead.apply(this, args);
};
const stateRead = MockApi.prototype.getState;
MockApi.prototype.getState = async function() {
authority.states++;
Expand Down Expand Up @@ -132,14 +127,9 @@ MockApi.prototype.listWorkflows = function() {
}

async function evidence(page: Page) {
return page.evaluate(() => {
const result = await page.evaluate(() => {
const win = window as TestWindow;
return {
legacy: [
win.__authority.reads,
win.__authority.refreshes,
win.__authority.navigation,
],
session: win.__authority.activeSessionId,
actions: [
"createSessionCalls",
Expand All @@ -152,6 +142,7 @@ async function evidence(page: Page) {
),
};
});
return { ...result, legacy: [...legacyRequests.get(page)!] };
}

for (const identity of ["missing-id", "missing-project", "older-protocol"] as const) {
Expand Down Expand Up @@ -365,15 +356,8 @@ test("durable map ignores old graph events and keeps exact navigation and sessio
await open(page);
await expect(page.getByTestId("agent-map-live")).toBeVisible();
const before = await evidence(page);
const eventsBefore = await page.evaluate(async () => {
const eventsBefore = await page.evaluate(() => {
const win = window as TestWindow;
const { systemGraphLoader } =
await import("/src/lib/system-graph-loader.ts");
const invalidate = systemGraphLoader.invalidate.bind(systemGraphLoader);
systemGraphLoader.invalidate = (...args: unknown[]) => {
win.__authority.invalidations++;
return invalidate(...args);
};
const counts = [win.__authority.states, win.__authority.workflows];
for (const workspaceKey of [
"workspace-mock-1",
Expand All @@ -389,11 +373,6 @@ test("durable map ignores old graph events and keeps exact navigation and sessio
}
return counts;
});
await expect
.poll(() =>
page.evaluate(() => (window as TestWindow).__authority.invalidations),
)
.toBe(0);
expect(
await page.evaluate(() => {
const probe = (window as TestWindow).__authority;
Expand Down
60 changes: 60 additions & 0 deletions packages/harness/web/e2e/agent-map-metadata.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect, test } from "@playwright/test";

for (const theme of ["light", "dark"] as const) {
test(`map metadata uses the muted monospace role in ${theme} mode`, async ({
page,
}) => {
await page.goto(
"/?seed=0&mockFixtures=deep&mockStudioProjects=present&mockAgentMapGolden=1",
);
await expect(page.getByTestId("session-context")).toBeVisible();
await page.getByTestId("project-select-acme-app").click();
await expect(page.getByTestId("agent-map-canvas")).toHaveAttribute(
"data-layout-state",
"ready",
);
await page
.getByTestId("agent-map-info-node_00000000-0000-7000-8000-000000000101")
.click();
await expect(page.getByTestId("agent-map-inspector")).toBeVisible();
await page.evaluate((value) => {
document.documentElement.dataset.theme = value;
}, theme);

// Resolve the design roles in the browser, so this checks the cascade and
// rem/theme resolution at each consumer rather than matching CSS source.
const expected = await page.evaluate(() => {
const reference = document.createElement("span");
reference.style.cssText =
"color:var(--text-faint);font-family:var(--font-mono);font-size:var(--type-meta)";
document.body.append(reference);
const style = getComputedStyle(reference);
const result = {
color: style.color,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
};
reference.remove();
return result;
});
for (const selector of [
".agent-map-live-header .agent-map-node-meta",
".agent-map-node .agent-map-node-meta",
".agent-map-inspector .agent-map-node-meta",
]) {
const metadata = page.locator(selector);
await expect(metadata.first()).toBeVisible();
const actual = await metadata.evaluateAll((elements) =>
elements.map((element) => {
const style = getComputedStyle(element);
return {
color: style.color,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
};
}),
);
for (const style of actual) expect(style).toEqual(expected);
}
});
}
14 changes: 7 additions & 7 deletions packages/harness/web/src/components/AgentMapCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export function AgentMapCanvas({
>
{!layout && (
<EmptyState
className="system-graph-state"
className="agent-map-state"
testId={
computed.state === "error"
? "agent-map-layout-error"
Expand Down Expand Up @@ -349,7 +349,7 @@ export function AgentMapCanvas({
markerEnd={`url(#${markerId})`}
/>
<text
className="system-graph-edge-label agent-map-edge-label"
className="agent-map-edge-label"
x={edge.labelX}
y={edge.labelY}
textAnchor="middle"
Expand Down Expand Up @@ -403,9 +403,9 @@ export function AgentMapCanvas({
>
<span className="agent-map-node-heading">
<Icon name={KIND_ICON[node.kind]} size={14} />
<span className="system-graph-node-label">{node.name}</span>
<span className="agent-map-node-label">{node.name}</span>
</span>
<span className="system-graph-node-meta">
<span className="agent-map-node-meta">
{deployment && (
<>
<span
Expand Down Expand Up @@ -444,13 +444,13 @@ export function AgentMapCanvas({
})}
</div>
<div
className="system-graph-controls agent-map-controls"
className="agent-map-controls"
style={!layout ? { display: "none" } : undefined}
role="group"
aria-label="Agent Map view controls"
>
{computed.state !== "ready" && (
<span className="system-graph-node-meta" role="status">
<span className="agent-map-node-meta" role="status">
Arranging…
</span>
)}
Expand All @@ -474,7 +474,7 @@ export function AgentMapCanvas({
</button>
<button
type="button"
className="theme-toggle system-graph-zoom-reset"
className="theme-toggle agent-map-zoom-reset"
aria-label="Reset Agent Map view"
onClick={() => {
followsUpdates.current = false;
Expand Down
2 changes: 1 addition & 1 deletion packages/harness/web/src/components/AgentMapInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function AgentMapInspector({
>
<div className="agent-map-inspector-heading">
<div>
<p className="system-graph-node-meta">{node.kind}</p>
<p className="agent-map-node-meta">{node.kind}</p>
<h3>{node.name}</h3>
{deployment && (
<span
Expand Down
2 changes: 1 addition & 1 deletion packages/harness/web/src/components/AgentMapPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ function PopulatedAgentMap({
}}
>
<div className="agent-map-live-header">
<span className="system-graph-node-meta">
<span className="agent-map-node-meta">
Version {proposal.version}
</span>
{failed.length > 0 && (
Expand Down
Loading
Loading