diff --git a/.yoi/tickets/00001KX7Z3WE6/item.md b/.yoi/tickets/00001KX7Z3WE6/item.md index 66c79193..8b96ed13 100644 --- a/.yoi/tickets/00001KX7Z3WE6/item.md +++ b/.yoi/tickets/00001KX7Z3WE6/item.md @@ -1,8 +1,8 @@ --- title: 'Disable Console links for archived Workers' -state: 'inprogress' +state: 'done' created_at: '2026-07-11T06:51:23Z' -updated_at: '2026-07-11T06:52:17Z' +updated_at: '2026-07-11T07:01:17Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-07-11T06:52:17Z' diff --git a/.yoi/tickets/00001KX7Z3WE6/thread.md b/.yoi/tickets/00001KX7Z3WE6/thread.md index c9a99f57..f9ba66ed 100644 --- a/.yoi/tickets/00001KX7Z3WE6/thread.md +++ b/.yoi/tickets/00001KX7Z3WE6/thread.md @@ -39,4 +39,33 @@ Ticket を `yoi ticket` が queued にしました。 State changed to `inprogress`. +--- + + + +## Implementation report + +Archived registry-only Workers no longer render as Console links. + +- Added shared `canOpenWorkerConsole` helper that rejects `state: archived` and `implementation.kind: backend_worker_registry`. +- Sidebar renders archived Workers as disabled readonly rows instead of anchors. +- Workers page shows `Archived` instead of `Open Console` for archived Workers. +- Added unit coverage for live vs archived Console availability. + +Validation: +- cd web/workspace && deno task check +- cd web/workspace && deno task test +- git diff --check +- nix build .#yoi --no-link + + +--- + + + +## State changed + +State changed to `done`. + + --- diff --git a/web/workspace/deno.json b/web/workspace/deno.json index aa67cda9..bec65306 100644 --- a/web/workspace/deno.json +++ b/web/workspace/deno.json @@ -6,7 +6,7 @@ "dev": "deno run -A npm:vite@7.2.7 dev", "dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --listen 127.0.0.1:8787", "check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json", - "test": "deno test --allow-read=src --allow-env=VSCODE_TEXTMATE_DEBUG src/lib/workspace-api/http.test.ts src/lib/workspace-console/markdown.test.ts src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts src/lib/workspace-sidebar/repository-nav.test.ts", + "test": "deno test --allow-read=src --allow-env=VSCODE_TEXTMATE_DEBUG src/lib/workspace-api/http.test.ts src/lib/workspace-console/markdown.test.ts src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/workers.test.ts src/lib/workspace-sidebar/worker-launch.test.ts src/lib/workspace-sidebar/repository-nav.test.ts", "build": "deno run -A npm:vite@7.2.7 build", "preview": "deno run -A npm:vite@7.2.7 preview" }, diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css index 562eaeb1..03028145 100644 --- a/web/workspace/src/app.css +++ b/web/workspace/src/app.css @@ -436,6 +436,15 @@ gap: 2px; } + .worker-nav-item.disabled { + cursor: default; + opacity: 0.62; + } + + .worker-nav-item.disabled .item-title { + color: var(--text-muted); + } + .worker-title-row { display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); diff --git a/web/workspace/src/lib/workspace-console/worker-console.ui.test.ts b/web/workspace/src/lib/workspace-console/worker-console.ui.test.ts index 292c0667..c7cd3ed4 100644 --- a/web/workspace/src/lib/workspace-console/worker-console.ui.test.ts +++ b/web/workspace/src/lib/workspace-console/worker-console.ui.test.ts @@ -14,7 +14,10 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async () new URL("./../../routes/w/[workspaceId]/+page.svelte", import.meta.url), ); const workersPage = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/workers/+page.svelte", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/workers/+page.svelte", + import.meta.url, + ), ); const workersNav = await Deno.readTextFile( new URL("../workspace-sidebar/WorkersNavSection.svelte", import.meta.url), @@ -24,8 +27,8 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async () ); assert( - workspacePage.includes('href={`/w/${workspaceId}/runtimes`}') && - workspacePage.includes('href={`/w/${workspaceId}/workers`}'), + workspacePage.includes("href={`/w/${workspaceId}/runtimes`}") && + workspacePage.includes("href={`/w/${workspaceId}/workers`}"), "top workspace page should link to Runtimes and Workers pages", ); assert( @@ -35,15 +38,15 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async () ); assert( workersPage.includes("workerConsoleHref(worker, data.workspaceId)") && - workersPage.includes("") && + workersPage.includes('
') && workersPage.includes("Open Console"), "dedicated Workers page should expose a table and attach action per Worker", ); assert( - workersNav.includes('href={`/w/${workspaceId}/workers`}') && - workersNav.includes("workerConsoleHref(worker, workspaceId)") && - workersNav.includes("aria-current"), - "Workers sidebar should link to the Worker list page and target Console routes", + workersNav.includes("href={`/w/${workspaceId}/workers`}") && + workersNav.includes("canOpenWorkerConsole(worker)") && + workersNav.includes('aria-disabled="true"'), + "Workers sidebar should link to the Worker list page and render archived Workers as disabled rows", ); assert( !sidebar.includes("CompanionNavSection") && @@ -54,12 +57,17 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async () Deno.test("Worker Console uses protocol observation events without transcript fetch", async () => { const consolePage = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte", + import.meta.url, + ), ); assert( consolePage.includes("seenObservationEventIds") && - consolePage.includes("rememberObservationEvent(frame.envelope.event_id)") && + consolePage.includes( + "rememberObservationEvent(frame.envelope.event_id)", + ) && consolePage.includes("projectConsole(observedEvents.map") && !consolePage.includes("/transcript") && !consolePage.includes("WorkerTranscriptProjection"), @@ -69,7 +77,10 @@ Deno.test("Worker Console uses protocol observation events without transcript fe Deno.test("Decodal source editor keeps imperative EditorView out of reactive state", async () => { const editor = await Deno.readTextFile( - new URL("../workspace-settings/DecodalSourceEditor.svelte", import.meta.url), + new URL( + "../workspace-settings/DecodalSourceEditor.svelte", + import.meta.url, + ), ); assert( @@ -89,18 +100,27 @@ Deno.test("workspace Runtime management pages expose Runtimes and Runtime-owned new URL("../workspace-sidebar/RuntimesNavSection.svelte", import.meta.url), ); const runtimesPage = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/runtimes/+page.svelte", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/runtimes/+page.svelte", + import.meta.url, + ), ); const workdirsPage = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.svelte", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.svelte", + import.meta.url, + ), ); const workdirsLoad = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.ts", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.ts", + import.meta.url, + ), ); assert( sidebar.includes("RuntimesNavSection") && - runtimesNav.includes('href={runtimesHref}') && + runtimesNav.includes("href={runtimesHref}") && runtimesNav.includes("/runtimes"), "sidebar should expose Runtime management navigation", ); @@ -123,18 +143,21 @@ Deno.test("workspace Worker sidebar links New to the dedicated create page", asy new URL("../workspace-sidebar/WorkersNavSection.svelte", import.meta.url), ); const newWorkerPage = await Deno.readTextFile( - new URL("./../../routes/w/[workspaceId]/workers/new/+page.svelte", import.meta.url), + new URL( + "./../../routes/w/[workspaceId]/workers/new/+page.svelte", + import.meta.url, + ), ); assert( - workersNav.includes('href={`/w/${workspaceId}/workers/new`}') && - !workersNav.includes('worker-launch-form') && - !workersNav.includes('createWorker()'), + workersNav.includes("href={`/w/${workspaceId}/workers/new`}") && + !workersNav.includes("worker-launch-form") && + !workersNav.includes("createWorker()"), "Workers sidebar should link to the dedicated New Worker page instead of owning the form", ); assert( - newWorkerPage.includes('worker-launch-form') && - newWorkerPage.includes('buildBrowserCreateWorkerRequest') && + newWorkerPage.includes("worker-launch-form") && + newWorkerPage.includes("buildBrowserCreateWorkerRequest") && newWorkerPage.includes("/workers/launch-options"), "New Worker page should own launch options and creation form behavior", ); diff --git a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte index 4cdbc4b3..1bb89c7c 100644 --- a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte +++ b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte @@ -1,6 +1,7 @@