diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 9ff23033..f77148ac 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade}; use axum::extract::{Path as AxumPath, Query, State}; -use axum::http::header::CONTENT_TYPE; +use axum::http::header::{CONTENT_TYPE, LOCATION}; use axum::http::{StatusCode, Uri}; use axum::response::{IntoResponse, Response}; use axum::routing::{delete, get, post}; @@ -2737,6 +2737,12 @@ async fn static_or_spa_fallback(State(api): State, uri: Uri) -> Re } } + if let Some(location) = + unscoped_workspace_ui_redirect(uri.path(), uri.query(), api.workspace_id()) + { + return (StatusCode::TEMPORARY_REDIRECT, [(LOCATION, location)]).into_response(); + } + let Some(static_root) = api.config.static_assets_dir.as_ref() else { return StatusCode::NOT_FOUND.into_response(); }; @@ -2753,6 +2759,30 @@ async fn static_or_spa_fallback(State(api): State, uri: Uri) -> Re } } +fn unscoped_workspace_ui_redirect( + path: &str, + query: Option<&str>, + workspace_id: &str, +) -> Option { + let scoped_tail = if path == "/" { + "" + } else if ["/repositories", "/objectives", "/settings", "/runtimes"] + .iter() + .any(|prefix| path == *prefix || path.starts_with(&format!("{prefix}/"))) + { + path + } else { + return None; + }; + + let mut location = format!("/w/{}{}", encode_path_segment(workspace_id), scoped_tail); + if let Some(query) = query.filter(|query| !query.is_empty()) { + location.push('?'); + location.push_str(query); + } + Some(location) +} + fn workspace_id_from_ui_path(path: &str) -> Option<&str> { let tail = path.strip_prefix("/w/")?; let workspace_id = tail.split('/').next().unwrap_or_default(); @@ -3598,6 +3628,26 @@ mod tests { .contains(dir.path().to_string_lossy().as_ref()) ); + let unscoped_objectives = app + .clone() + .oneshot( + Request::builder() + .uri("/objectives?focus=active") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(unscoped_objectives.status(), StatusCode::TEMPORARY_REDIRECT); + let expected_location = format!("/w/{TEST_WORKSPACE_ID}/objectives?focus=active"); + assert_eq!( + unscoped_objectives + .headers() + .get(LOCATION) + .and_then(|value| value.to_str().ok()), + Some(expected_location.as_str()) + ); + let tickets = get_json(app.clone(), "/api/tickets").await; assert_eq!(tickets["items"][0]["id"], "00000000001J2"); assert_eq!(tickets["items"][0]["state"], "ready"); diff --git a/web/workspace/src/lib/workspace-api/http.test.ts b/web/workspace/src/lib/workspace-api/http.test.ts index 99f1d73e..d18f7d43 100644 --- a/web/workspace/src/lib/workspace-api/http.test.ts +++ b/web/workspace/src/lib/workspace-api/http.test.ts @@ -1,7 +1,8 @@ import { workspaceApiPath, workspaceRoute } from "./http.ts"; declare const Deno: { - test(name: string, fn: () => void): void; + test(name: string, fn: () => Promise | void): void; + readTextFile(path: string | URL): Promise; }; function assertEquals(actual: T, expected: T): void { @@ -12,6 +13,12 @@ function assertEquals(actual: T, expected: T): void { } } +function assert(condition: unknown, message: string): asserts condition { + if (!condition) { + throw new Error(message); + } +} + Deno.test("workspace route helpers scope browser routes and API by immutable workspace id", () => { assertEquals(workspaceRoute("workspace 1"), "/w/workspace%201"); assertEquals(workspaceRoute("workspace 1", "/objectives"), "/w/workspace%201/objectives"); @@ -20,3 +27,29 @@ Deno.test("workspace route helpers scope browser routes and API by immutable wor "/api/w/workspace%201/repositories/repo-a", ); }); + +Deno.test("unscoped layout bootstraps then redirects instead of loading unscoped workspace data", async () => { + const layout = await Deno.readTextFile(new URL("../../routes/+layout.ts", import.meta.url)); + assert( + layout.includes('loadJson(fetch, "/api/workspace")'), + "unscoped layout may use only the workspace-id bootstrap endpoint", + ); + assert( + layout.includes("throw redirect(307") && layout.includes("workspaceRoute("), + "unscoped layout should redirect to the scoped workspace route", + ); + assert( + !layout.includes('`/api${path}`') && !layout.includes('"/api/repositories"'), + "layout must not fall back to unscoped workspace-scoped API calls", + ); + + const unscopedSettings = await Deno.readTextFile( + new URL("../../routes/settings/+page.svelte", import.meta.url), + ); + assert( + unscopedSettings.includes("Redirecting to scoped settings") && + !unscopedSettings.includes("fetch(") && + !unscopedSettings.includes("settingsApiPath"), + "unscoped settings route should remain a thin redirect shim, not a data/control surface", + ); +}); 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 a688f57f..2027b115 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 @@ -11,7 +11,7 @@ function assert(condition: unknown, message: string): asserts condition { Deno.test("workspace Worker list and sidebar attach through Worker Console hrefs", async () => { const workspacePage = await Deno.readTextFile( - new URL("./../../routes/+page.svelte", import.meta.url), + new URL("./../../routes/w/[workspaceId]/+page.svelte", import.meta.url), ); const workersNav = await Deno.readTextFile( new URL("../workspace-sidebar/WorkersNavSection.svelte", import.meta.url), diff --git a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte index 150f9459..170d5cb9 100644 --- a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte +++ b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte @@ -37,6 +37,13 @@ let initialText = $state(''); $effect(() => { + if (!workspaceId) { + loading = false; + workers = []; + options = null; + return; + } + const controller = new AbortController(); void loadWorkers(controller.signal); void loadLaunchOptions(controller.signal); @@ -102,6 +109,11 @@ } async function createWorker() { + if (!workspaceId) { + submitError = 'workspace id is unavailable'; + return; + } + submitError = null; submitting = true; try { diff --git a/web/workspace/src/routes/+layout.ts b/web/workspace/src/routes/+layout.ts index 4fdaf406..f3e63f06 100644 --- a/web/workspace/src/routes/+layout.ts +++ b/web/workspace/src/routes/+layout.ts @@ -1,17 +1,32 @@ -import { loadJson, workspaceApiPath } from "$lib/workspace-api/http"; -import type { - RepositoryListResponse, - WorkspaceResponse, -} from "$lib/workspace-sidebar/types"; +import { redirect } from "@sveltejs/kit"; +import { loadJson, workspaceApiPath, workspaceRoute } from "$lib/workspace-api/http"; +import type { RepositoryListResponse, WorkspaceResponse } from "$lib/workspace-sidebar/types"; import type { LayoutLoad } from "./$types"; export const ssr = false; export const prerender = false; -export const load: LayoutLoad = async ({ fetch, params }) => { +export const load: LayoutLoad = async ({ fetch, params, url }) => { const workspaceId = params.workspaceId; - const apiPath = (path: string) => - workspaceId ? workspaceApiPath(workspaceId, path) : `/api${path}`; + + if (!workspaceId) { + const workspace = await loadJson(fetch, "/api/workspace"); + if (workspace.data) { + const scopedPath = workspaceRoute( + workspace.data.workspace_id, + url.pathname === "/" ? "" : url.pathname, + ); + throw redirect(307, `${scopedPath}${url.search}`); + } + return { + workspace: null, + workspaceError: workspace.error, + repositories: null, + repositoriesError: null, + }; + } + + const apiPath = (path: string) => workspaceApiPath(workspaceId, path); const [workspace, repositories] = await Promise.all([ loadJson(fetch, apiPath("/workspace")), diff --git a/web/workspace/src/routes/+page.svelte b/web/workspace/src/routes/+page.svelte index 34ca4a6a..87fceed3 100644 --- a/web/workspace/src/routes/+page.svelte +++ b/web/workspace/src/routes/+page.svelte @@ -1,133 +1,6 @@ - - - - Yoi Workspace Control Plane - - - -
-

Workspace

- {#if data.workspace} -
-
-
ID
-
{data.workspace.workspace_id}
-
-
-
Name
-
{data.workspace.display_name}
-
-
-
Record authority
-
{data.workspace.record_authority}
-
-
-
Host / Worker bridge
-
{data.workspace.extension_points.host_worker_bridge.status}
-
-
- {:else if data.workspaceError} -

{data.workspaceError}

- {:else} -

Waiting for /api/workspace

- {/if} -
- -
-
-

Hosts

- {#if data.hosts} - {#if data.hosts.items.length === 0} -

No local Hosts are visible.

- {:else} -
- {#each data.hosts.items as host} -
-
- {host.label} - {host.status} -
-
-
-
ID
-
{host.host_id}
-
-
-
Kind
-
{host.kind}
-
-
-
Runtime
-
{host.runtime_id}
-
-
-
Scope
-
{host.capabilities.workspace_scope}
-
-
-
Platform
-
{host.capabilities.os} / {host.capabilities.arch}
-
-
-
- {/each} -
- {/if} - {:else if data.hostsError} -

{data.hostsError}

- {:else} -

Waiting for /api/hosts

- {/if} -
- -
-

Workers

- {#if data.workers} - {#if data.workers.items.length === 0} -

No local Workers are visible.

- {:else} -
- - - - - - - - - - - - - {#each data.workers.items as worker} - - - - - - - - - {/each} - -
WorkerHostStateWorkspaceImplementationAttach
- {worker.label} - {#if worker.role || worker.profile} - {worker.role ?? 'role unknown'} / {worker.profile ?? 'profile unknown'} - {/if} - {worker.host_id}{worker.state} · {worker.status}{worker.workspace.visibility} · {worker.workspace.identity}{worker.implementation.kind}Open Console
-
- {/if} - {:else if data.workersError} -

{data.workersError}

- {:else} -

Waiting for /api/workers

- {/if} -
-
+
+
+

Redirecting to scoped workspace…

+

This compatibility route bootstraps the current workspace id and redirects to the canonical /w/<workspace-id> route.

+
+
diff --git a/web/workspace/src/routes/+page.ts b/web/workspace/src/routes/+page.ts index 9d2ca562..7a066b58 100644 --- a/web/workspace/src/routes/+page.ts +++ b/web/workspace/src/routes/+page.ts @@ -1,21 +1,3 @@ -import { loadJson, workspaceApiPath } from "$lib/workspace-api/http"; -import type { Host, ListResponse, Worker } from "$lib/workspace-sidebar/types"; import type { PageLoad } from "./$types"; -export const load: PageLoad = async ({ fetch, parent }) => { - const layout = await parent(); - const workspaceId = layout.workspace?.workspace_id ?? ""; - const apiPath = (path: string) => workspaceApiPath(workspaceId, path); - - const [hosts, workers] = await Promise.all([ - loadJson>(fetch, apiPath("/hosts")), - loadJson>(fetch, apiPath("/workers")), - ]); - - return { - hosts: hosts.data, - hostsError: hosts.error, - workers: workers.data, - workersError: workers.error, - }; -}; +export const load: PageLoad = async () => ({}); diff --git a/web/workspace/src/routes/objectives/+page.svelte b/web/workspace/src/routes/objectives/+page.svelte index 088e66ec..c3cbc837 100644 --- a/web/workspace/src/routes/objectives/+page.svelte +++ b/web/workspace/src/routes/objectives/+page.svelte @@ -1,46 +1,6 @@ - - - - Objectives · Yoi Workspace - - -
-

Objectives

-

Objectives are read from canonical filesystem records through /api/objectives.

- {#if data.objectives} - {#if data.objectives.items.length === 0} -

No Objective records are present.

- {:else} - - {/if} - {#if data.objectives.invalid_records.length > 0} -

{data.objectives.invalid_records.length} invalid objective record(s) hidden.

- {/if} - {:else if data.objectivesError} -

{data.objectivesError}

- {:else} -

Waiting for /api/objectives

- {/if} -
+
+
+

Redirecting to scoped objectives…

+

Objectives are available at the canonical /w/<workspace-id>/objectives route.

+
+
diff --git a/web/workspace/src/routes/objectives/+page.ts b/web/workspace/src/routes/objectives/+page.ts index 92690c1f..7a066b58 100644 --- a/web/workspace/src/routes/objectives/+page.ts +++ b/web/workspace/src/routes/objectives/+page.ts @@ -1,17 +1,3 @@ -import { loadJson, workspaceApiPath } from "$lib/workspace-api/http"; -import type { ObjectiveListResponse } from "$lib/workspace-sidebar/types"; import type { PageLoad } from "./$types"; -export const load: PageLoad = async ({ fetch, parent }) => { - const layout = await parent(); - const workspaceId = layout.workspace?.workspace_id ?? ""; - const objectives = await loadJson( - fetch, - workspaceApiPath(workspaceId, "/objectives"), - ); - return { - workspaceId, - objectives: objectives.data, - objectivesError: objectives.error, - }; -}; +export const load: PageLoad = async () => ({}); diff --git a/web/workspace/src/routes/objectives/[objectiveId]/+page.svelte b/web/workspace/src/routes/objectives/[objectiveId]/+page.svelte index cf07c432..37f9891b 100644 --- a/web/workspace/src/routes/objectives/[objectiveId]/+page.svelte +++ b/web/workspace/src/routes/objectives/[objectiveId]/+page.svelte @@ -1,76 +1,6 @@ - - - - {data.objective?.title ?? data.objectiveId} · Objective - - -
-

Objectives

- {#if data.objectives} - - {:else if data.objectivesError} -

{data.objectivesError}

- {:else} -

Loading objectives…

- {/if} -
- -
-

Objective detail

- {#if data.objective} -
-
-

{data.objective.title}

-

{data.objective.id}

-
- {data.objective.state} -
-
-
-
Created
-
{data.objective.created_at ? formatDate(data.objective.created_at) : 'unknown'}
-
-
-
Updated
-
{data.objective.updated_at ? formatDate(data.objective.updated_at) : 'unknown'}
-
-
-
Record source
-
{data.objective.record_source}
-
-
-
Linked tickets
-
{data.objective.linked_tickets.length ? data.objective.linked_tickets.join(', ') : 'none'}
-
-
-
{data.objective.body}
- {#if data.objective.body_truncated} -

Objective body was truncated by the Backend response limit.

- {/if} - {:else if data.objectiveError} -

{data.objectiveError}

- {:else} -

Loading objective detail…

- {/if} -
+
+
+

Redirecting to scoped objective…

+

Objective detail routes are canonical under /w/<workspace-id>/objectives/<objective-id>.

+
+
diff --git a/web/workspace/src/routes/objectives/[objectiveId]/+page.ts b/web/workspace/src/routes/objectives/[objectiveId]/+page.ts index c3e034aa..7a066b58 100644 --- a/web/workspace/src/routes/objectives/[objectiveId]/+page.ts +++ b/web/workspace/src/routes/objectives/[objectiveId]/+page.ts @@ -1,26 +1,3 @@ -import { loadJson, workspaceApiPath } from "$lib/workspace-api/http"; -import type { ObjectiveDetail, ObjectiveListResponse } from "$lib/workspace-sidebar/types"; import type { PageLoad } from "./$types"; -export const load: PageLoad = async ({ fetch, params, parent }) => { - const layout = await parent(); - const workspaceId = layout.workspace?.workspace_id ?? ""; - const apiPath = (path: string) => workspaceApiPath(workspaceId, path); - const objectiveId = params.objectiveId; - const [objectives, objective] = await Promise.all([ - loadJson(fetch, apiPath("/objectives")), - loadJson( - fetch, - apiPath(`/objectives/${encodeURIComponent(objectiveId)}`), - ), - ]); - - return { - workspaceId, - objectiveId, - objectives: objectives.data, - objectivesError: objectives.error, - objective: objective.data, - objectiveError: objective.error, - }; -}; +export const load: PageLoad = async () => ({}); diff --git a/web/workspace/src/routes/repositories/[repositoryId]/+page.svelte b/web/workspace/src/routes/repositories/[repositoryId]/+page.svelte index d6493e49..c9ddef3c 100644 --- a/web/workspace/src/routes/repositories/[repositoryId]/+page.svelte +++ b/web/workspace/src/routes/repositories/[repositoryId]/+page.svelte @@ -1,105 +1,6 @@ - - - - {data.repository?.item.display_name ?? data.repositoryId} · Repository - - -
-

Repository

- {#if data.repository} -
-
-

{data.repository.item.display_name}

-

{data.repository.item.id}

-
- {data.repository.item.git?.status ?? 'not observed'} -
-
-
-
Kind
-
{data.repository.item.kind}
-
-
-
Provider
-
{data.repository.item.provider}
-
-
-
Record authority
-
{data.repository.item.record_authority}
-
-
-
Default selector
-
{data.repository.item.default_selector ?? 'none configured'}
-
-
-
Branch
-
{data.repository.item.git?.branch ?? 'unknown'}
-
-
-
HEAD
-
{data.repository.item.git?.head ?? 'unknown'}
-
-
-
Dirty
-
{data.repository.item.git?.dirty ? 'yes' : 'no'}
-
-
- {#if data.repository.item.diagnostics && data.repository.item.diagnostics.length > 0} -
    - {#each data.repository.item.diagnostics as diagnostic} -
  • {diagnostic.code}: {diagnostic.message}
  • - {/each} -
- {/if} - {:else if data.repositoryError} -

{data.repositoryError}

- {:else} -

Loading repository…

- {/if} -
- -
-

Recent commits

- {#if data.repositoryLog} - {#if data.repositoryLog.items.length === 0} -

No recent commits are available.

- {:else} -
- {#each data.repositoryLog.items as commit} -
- {commit.summary} - {commit.short_hash} · {commit.author_name} · {formatDate(commit.author_date)} -
- {/each} -
- {/if} - {#if data.repositoryLog.diagnostics.length > 0} -
    - {#each data.repositoryLog.diagnostics as diagnostic} -
  • {diagnostic.code}: {diagnostic.message}
  • - {/each} -
- {/if} - {:else if data.repositoryLogError} -

{data.repositoryLogError}

- {:else} -

Loading repository commits…

- {/if} -
- -
-

Repository Tickets

- {#if data.repositoryTickets} - - {:else if data.repositoryTicketsError} -

{data.repositoryTicketsError}

- {:else} -

Loading repository tickets…

- {/if} -
+
+
+

Redirecting to scoped repository…

+

Repository routes are canonical under /w/<workspace-id>/repositories/<repository-id>.

+
+
diff --git a/web/workspace/src/routes/repositories/[repositoryId]/+page.ts b/web/workspace/src/routes/repositories/[repositoryId]/+page.ts index 14b19f16..7a066b58 100644 --- a/web/workspace/src/routes/repositories/[repositoryId]/+page.ts +++ b/web/workspace/src/routes/repositories/[repositoryId]/+page.ts @@ -1,38 +1,3 @@ -import { loadJson, workspaceApiPath } from "$lib/workspace-api/http"; -import type { - RepositoryDetailResponse, - RepositoryLogResponse, - RepositoryTicketsResponse, -} from "$lib/workspace-sidebar/types"; import type { PageLoad } from "./$types"; -export const load: PageLoad = async ({ fetch, params, parent }) => { - const layout = await parent(); - const workspaceId = layout.workspace?.workspace_id ?? ""; - const apiPath = (path: string) => workspaceApiPath(workspaceId, path); - const repositoryId = params.repositoryId; - const [repository, log, tickets] = await Promise.all([ - loadJson( - fetch, - apiPath(`/repositories/${encodeURIComponent(repositoryId)}`), - ), - loadJson( - fetch, - apiPath(`/repositories/${encodeURIComponent(repositoryId)}/log`), - ), - loadJson( - fetch, - apiPath(`/repositories/${encodeURIComponent(repositoryId)}/tickets`), - ), - ]); - - return { - repositoryId, - repository: repository.data, - repositoryError: repository.error, - repositoryLog: log.data, - repositoryLogError: log.error, - repositoryTickets: tickets.data, - repositoryTicketsError: tickets.error, - }; -}; +export const load: PageLoad = async () => ({}); diff --git a/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte b/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte index 775be75e..5469576b 100644 --- a/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte +++ b/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte @@ -1,399 +1,6 @@ - - - - Worker Console · Yoi Workspace - - - -
-
-
-

{worker?.label ?? workerId}

-
-
-
- {worker?.state ?? 'unknown'} · {worker?.status ?? 'loading'} · stream {streamState} -
- -
-
- -
-
- {#if projection.status || projection.usage} -

- {#if projection.status}status: {projection.status}{/if} - {#if projection.status && projection.usage} · {/if} - {#if projection.usage}usage: {projection.usage}{/if} -

- {/if} - - {#if workerError} -

{workerError}

- {/if} - {#if transcriptError} -

{transcriptError}

- {/if} - - {#if lines.length === 0} -

No console output is available for this Worker yet.

- {:else} -
    - {#each lines as item} -
  1. - {#if lineClass(item) !== 'assistant' && lineClass(item) !== 'user'} -
    - {item.title} - {#if item.streaming}streaming{/if} -
    - {:else if item.streaming} -
    - streaming -
    - {/if} -
    {item.body || '—'}
    - {#if item.detail} -
    - detail -

    {item.detail}

    -
    - {/if} -
  2. - {/each} -
- {/if} -
- -
- - {#if workerDetailsOpen} - - {/if} - -
- -
- - {#if sendError}

{sendError}

{/if} -
-
-
+
+
+

Redirecting to scoped Worker Console…

+

Worker Console routes are canonical under /w/<workspace-id>/runtimes/<runtime-id>/workers/<worker-id>/console.

+
+
diff --git a/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts b/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts index 56967e1f..7a066b58 100644 --- a/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts +++ b/web/workspace/src/routes/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts @@ -1,10 +1,3 @@ import type { PageLoad } from "./$types"; -export const load: PageLoad = async ({ params, parent }) => { - const layout = await parent(); - return { - workspaceId: layout.workspace?.workspace_id ?? "", - runtimeId: params.runtimeId, - workerId: params.workerId, - }; -}; +export const load: PageLoad = async () => ({}); diff --git a/web/workspace/src/routes/settings/+page.svelte b/web/workspace/src/routes/settings/+page.svelte index 1737a2bc..18b67741 100644 --- a/web/workspace/src/routes/settings/+page.svelte +++ b/web/workspace/src/routes/settings/+page.svelte @@ -1,487 +1,6 @@ - - - - Settings · Yoi Workspace - - -
-
-
-

Workspace Browser

-

Settings / Admin

-

- Local administration surfaces for the Workspace backend. Runtime Connections v0 is editable through typed APIs; broader admin controls remain bounded placeholders. -

-
- local only -
- -
-
-

Authority boundary

-

No browser admin permission model

-

{SETTINGS_PERMISSION_NOTICE}

-
-
- Restart-required - Runtime config changes are persisted, then applied after backend restart. -
-
- -
- {#each SETTINGS_SECTIONS as section} - - {section.label} - {section.status === "editable" ? "Editable" : section.status === "read-only" ? "Read-only" : "Placeholder"} - - {/each} -
- -
-
-
-

editable

-

Runtime Connections

-
- typed API -
-

{SETTINGS_SECTIONS.find((section) => section.id === "runtime-connections")?.summary}

- - {#if runtimeLoading} -

Loading Runtime connections…

- {:else if runtimeError} -

Runtime connection settings unavailable: {runtimeError}

- {:else if runtimeSettings} -
- -
- - {#if showAddRuntimeForm} -
{ event.preventDefault(); void submitRemoteRuntime(); }}> -

Add remote Runtime

-

Endpoint is submitted to the Backend but not echoed back in settings responses.

- - - - -
- {/if} - - {#if mutationMessage} -

{mutationMessage}

- {/if} - {@render DiagnosticsList({ diagnostics: mutationDiagnostics })} - -
-

Runtimes

-
- - - - - - - - - - - - - - - - - - - {#if runtimeSettings.embedded.diagnostics.length > 0} - - - - {/if} - {#each runtimeSettings.remotes as remote (remote.runtime_id)} - - - - - - - - {#if remote.diagnostics.length > 0} - - - - {/if} - {#if tests[remote.runtime_id]} - {@const test = tests[remote.runtime_id]} - {@const available = capabilityOperations(test, "available")} - {@const unchecked = capabilityOperations(test, "unknown")} - - - - {/if} - {/each} - -
RuntimeSourceConnectionStatusActions
- {runtimeSettings.embedded.display_name} - {runtimeSettings.embedded.runtime_id} - - embedded - Workspace backend process - Local Backend runtime - {runtimeSettings.embedded.status} - {#if runtimeSettings.embedded.restart_required} - restart required - {/if} - Managed by backend
{@render DiagnosticsList({ diagnostics: runtimeSettings.embedded.diagnostics })}
- {remote.display_name} - {remote.runtime_id} - - remote - Configured Runtime endpoint - - Endpoint: {remote.endpoint_configured ? "configured" : "not configured"} - {#if remote.endpoint_configured}hidden{/if} - Token: {remote.token_ref_configured ? "configured" : "not configured"} - - {remote.status} - {#if remote.restart_required} - restart required - {/if} - -
- - -
-
{@render DiagnosticsList({ diagnostics: remote.diagnostics })}
-
- Test: {test.state} - {test.health_result} · {test.checked_at} -

{test.compatibility_basis}

- {#if available.length > 0} -

Verified areas: {available.join(', ')}

- {/if} - {#if unchecked.length > 0} -

Unchecked warning areas: {unchecked.join(', ')}

- {/if} - {@render DiagnosticsList({ diagnostics: test.diagnostics })} -
-
-
- {#if runtimeSettings.remotes.length === 0} -

No remote Runtime connections configured.

- {/if} -
- {/if} -
- -
- {#each SETTINGS_SECTIONS.filter((section) => section.id !== "runtime-connections") as section} -
-
-
-

{section.status}

-

{section.label}

-
- {#if section.status === "placeholder"} - not implemented - {:else} - read-only - {/if} -
-

{section.summary}

-
    - {#each section.bullets as bullet} -
  • {bullet}
  • - {/each} -
- - {#if section.id === "workspace-identity"} -
-
-
Workspace id
-
{workspace?.workspace_id ?? "loading"}
-
-
-
Display name
-
{workspace?.display_name ?? "loading"}
-
-
-
Record authority
-
.yoi tickets/objectives through the Backend projection
-
-
- {/if} -
- {/each} -
- -
-
-

Implementation patterns

-

How settings should appear

-
-
- {#each SETTINGS_PATTERNS as pattern} -
-

{pattern.title}

-

{pattern.body}

-
- {/each} -
-
- - {#if !workspace && !workspaceError} -

Loading workspace summary…

- {:else if workspaceError} -

Workspace summary unavailable: {workspaceError}

- {/if} -
- -{#snippet DiagnosticsList({ diagnostics }: { diagnostics: Diagnostic[] })} - {#if diagnostics.length > 0} -
    - {#each diagnostics as diagnostic} -
  • - {diagnosticLabel(diagnostic)} - {diagnostic.message} -
  • - {/each} -
- {/if} -{/snippet} +
+
+

Redirecting to scoped settings…

+

Settings are available at the canonical /w/<workspace-id>/settings route.

+
+