feat: add workspace breadcrumbs
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"dev": "deno run -A npm:vite@7.2.7 dev",
|
"dev": "deno run -A npm:vite@7.2.7 dev",
|
||||||
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787",
|
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --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",
|
"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/auth/model.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-completion.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",
|
"test": "deno test --allow-read=src --allow-env=VSCODE_TEXTMATE_DEBUG src/lib/workspace/auth/model.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-completion.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",
|
"build": "deno run -A npm:vite@7.2.7 build",
|
||||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { getHeaderController } from './context';
|
||||||
|
|
||||||
|
let { content }: { content: Snippet<[]> } = $props();
|
||||||
|
const controller = getHeaderController();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
controller.content = content;
|
||||||
|
return () => {
|
||||||
|
if (controller.content === content) controller.content = null;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { buildWorkspaceBreadcrumbs } from './breadcrumb-model';
|
||||||
|
|
||||||
|
let { workspaceId }: { workspaceId: string } = $props();
|
||||||
|
|
||||||
|
const workerName = $derived.by(() => {
|
||||||
|
const data = page.data as Record<string, unknown>;
|
||||||
|
const worker = data.worker as { display_name?: string | null; label?: string | null } | null | undefined;
|
||||||
|
return worker?.display_name ?? worker?.label ?? null;
|
||||||
|
});
|
||||||
|
const breadcrumbs = $derived(buildWorkspaceBreadcrumbs(page.url.pathname, workspaceId, { workerName }));
|
||||||
|
const workspaceRoot = $derived(`/w/${encodeURIComponent(workspaceId)}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="workspace-breadcrumbs" aria-label="Current workspace location">
|
||||||
|
<a class="workspace-breadcrumb-root" href={workspaceRoot} aria-label="Workspace home">/</a>
|
||||||
|
{#each breadcrumbs as breadcrumb, index (`${index}:${breadcrumb.label}`)}
|
||||||
|
{#if index > 0}<span class="workspace-breadcrumb-separator" aria-hidden="true">/</span>{/if}
|
||||||
|
{#if breadcrumb.href}
|
||||||
|
<a href={breadcrumb.href}>{breadcrumb.label}</a>
|
||||||
|
{:else}
|
||||||
|
<span class="workspace-breadcrumb-label" aria-current={index === breadcrumbs.length - 1 ? 'page' : undefined}>
|
||||||
|
{breadcrumb.label}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.workspace-breadcrumbs {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
color: var(--workspace-muted, #53606e);
|
||||||
|
font-family: var(--workspace-font-mono, monospace);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumbs a,
|
||||||
|
.workspace-breadcrumb-label {
|
||||||
|
max-width: min(32vw, 28rem);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumbs a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumbs a:hover {
|
||||||
|
color: var(--workspace-ink, #151b23);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumb-root {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumb-separator {
|
||||||
|
color: color-mix(in srgb, currentColor 45%, transparent);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-breadcrumbs span[aria-current='page'] {
|
||||||
|
color: var(--workspace-ink, #151b23);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { buildWorkspaceBreadcrumbs } from "./breadcrumb-model.ts";
|
||||||
|
|
||||||
|
declare const Deno: {
|
||||||
|
test(name: string, fn: () => Promise<void> | void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assertEquals(actual: unknown, expected: unknown): void {
|
||||||
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("Ticket detail breadcrumbs expose the Ticket list and current id", () => {
|
||||||
|
assertEquals(
|
||||||
|
buildWorkspaceBreadcrumbs("/w/workspace/tickets/TICKET-42", "workspace"),
|
||||||
|
[
|
||||||
|
{ label: "tickets", href: "/w/workspace/tickets" },
|
||||||
|
{ label: "TICKET-42" },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Worker console breadcrumbs use the logical Workers route and display name", () => {
|
||||||
|
assertEquals(
|
||||||
|
buildWorkspaceBreadcrumbs(
|
||||||
|
"/w/workspace/runtimes/runtime-a/workers/worker-7/console",
|
||||||
|
"workspace",
|
||||||
|
{ workerName: "Review Worker" },
|
||||||
|
),
|
||||||
|
[
|
||||||
|
{ label: "workers", href: "/w/workspace/workers" },
|
||||||
|
{ label: "Review Worker" },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Worker console breadcrumbs fall back to Worker id", () => {
|
||||||
|
assertEquals(
|
||||||
|
buildWorkspaceBreadcrumbs(
|
||||||
|
"/w/workspace/runtimes/runtime-a/workers/worker-7/console",
|
||||||
|
"workspace",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
{ label: "workers", href: "/w/workspace/workers" },
|
||||||
|
{ label: "worker-7" },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
export type WorkspaceBreadcrumb = {
|
||||||
|
label: string;
|
||||||
|
href?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceBreadcrumbContext = {
|
||||||
|
workerName?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildWorkspaceBreadcrumbs(
|
||||||
|
pathname: string,
|
||||||
|
workspaceId: string,
|
||||||
|
context: WorkspaceBreadcrumbContext = {},
|
||||||
|
): WorkspaceBreadcrumb[] {
|
||||||
|
const workspaceRoot = `/w/${encodeURIComponent(workspaceId)}`;
|
||||||
|
const prefix = `${workspaceRoot}/`;
|
||||||
|
if (pathname === workspaceRoot || pathname === `${workspaceRoot}/`) return [];
|
||||||
|
if (!pathname.startsWith(prefix)) return [];
|
||||||
|
|
||||||
|
const segments = pathname
|
||||||
|
.slice(prefix.length)
|
||||||
|
.split("/")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(decodeURIComponent);
|
||||||
|
|
||||||
|
if (
|
||||||
|
segments[0] === "runtimes" &&
|
||||||
|
segments[2] === "workers" &&
|
||||||
|
segments[3]
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{ label: "workers", href: `${workspaceRoot}/workers` },
|
||||||
|
{ label: context.workerName?.trim() || segments[3] },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
segments[0] === "settings" && segments[1] === "profiles" &&
|
||||||
|
segments[2] === "trees"
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{ label: "settings", href: `${workspaceRoot}/settings` },
|
||||||
|
{ label: "profiles", href: `${workspaceRoot}/settings/profiles` },
|
||||||
|
{ label: segments[3] || "trees" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
segments[0] === "settings" &&
|
||||||
|
segments[1] === "runtimes" &&
|
||||||
|
segments[2] &&
|
||||||
|
segments[3] === "workdirs"
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{ label: "settings", href: `${workspaceRoot}/settings` },
|
||||||
|
{ label: "runtimes", href: `${workspaceRoot}/settings/runtimes` },
|
||||||
|
{ label: segments[2] },
|
||||||
|
{ label: "workdirs" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments.map((segment, index) => {
|
||||||
|
const isCurrent = index === segments.length - 1;
|
||||||
|
const href = isCurrent || (segments[0] === "repositories" && index === 0)
|
||||||
|
? undefined
|
||||||
|
: `${workspaceRoot}/${
|
||||||
|
segments.slice(0, index + 1).map(encodeURIComponent).join("/")
|
||||||
|
}`;
|
||||||
|
return {
|
||||||
|
label: segment,
|
||||||
|
href,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
const HEADER_CONTEXT_KEY = Symbol("workspace-header");
|
||||||
|
|
||||||
|
export type HeaderContent = Snippet<[]> | null;
|
||||||
|
export type HeaderController = {
|
||||||
|
content: HeaderContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function provideHeaderController(controller: HeaderController): void {
|
||||||
|
setContext(HEADER_CONTEXT_KEY, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeaderController(): HeaderController {
|
||||||
|
return getContext<HeaderController>(HEADER_CONTEXT_KEY);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { setContext } from 'svelte';
|
import { setContext } from 'svelte';
|
||||||
import WorkspaceAlerts from '$lib/workspace/alerts/WorkspaceAlerts.svelte';
|
import WorkspaceAlerts from '$lib/workspace/alerts/WorkspaceAlerts.svelte';
|
||||||
|
import { provideHeaderController, type HeaderController } from '$lib/workspace/header/context';
|
||||||
import GlobalSidebar from '$lib/workspace/sidebar/GlobalSidebar.svelte';
|
import GlobalSidebar from '$lib/workspace/sidebar/GlobalSidebar.svelte';
|
||||||
import SidebarFrame from '$lib/workspace/sidebar/SidebarFrame.svelte';
|
import SidebarFrame from '$lib/workspace/sidebar/SidebarFrame.svelte';
|
||||||
import { SIDEBAR_CONTEXT, type SidebarSnippet } from '$lib/workspace/sidebar/context';
|
import { SIDEBAR_CONTEXT, type SidebarSnippet } from '$lib/workspace/sidebar/context';
|
||||||
@@ -10,7 +11,9 @@
|
|||||||
|
|
||||||
let { children }: LayoutProps = $props();
|
let { children }: LayoutProps = $props();
|
||||||
let sidebar = $state<SidebarSnippet | null>(null);
|
let sidebar = $state<SidebarSnippet | null>(null);
|
||||||
|
const headerController = $state<HeaderController>({ content: null });
|
||||||
|
|
||||||
|
provideHeaderController(headerController);
|
||||||
setContext(SIDEBAR_CONTEXT, {
|
setContext(SIDEBAR_CONTEXT, {
|
||||||
setSidebar(snippet: SidebarSnippet) {
|
setSidebar(snippet: SidebarSnippet) {
|
||||||
sidebar = snippet;
|
sidebar = snippet;
|
||||||
@@ -32,6 +35,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</SidebarFrame>
|
</SidebarFrame>
|
||||||
<header class="workspace-topbar">
|
<header class="workspace-topbar">
|
||||||
|
<div class="workspace-topbar-location">
|
||||||
|
{#if headerController.content}{@render headerController.content()}{/if}
|
||||||
|
</div>
|
||||||
<nav class="workspace-topbar-actions" aria-label="Global navigation">
|
<nav class="workspace-topbar-actions" aria-label="Global navigation">
|
||||||
<a class="topbar-icon-button" href="/account" aria-label="Open Account" title="Account">
|
<a class="topbar-icon-button" href="/account" aria-label="Open Account" title="Account">
|
||||||
<svg class="topbar-icon" aria-hidden="true" viewBox="0 0 24 24">
|
<svg class="topbar-icon" aria-hidden="true" viewBox="0 0 24 24">
|
||||||
@@ -64,7 +70,8 @@
|
|||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
|
gap: var(--space-4);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 3.25rem;
|
min-height: 3.25rem;
|
||||||
padding: 0 var(--space-5);
|
padding: 0 var(--space-5);
|
||||||
@@ -73,6 +80,11 @@
|
|||||||
backdrop-filter: blur(14px);
|
backdrop-filter: blur(14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-location {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.workspace-topbar-actions {
|
.workspace-topbar-actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
|
import HeaderOverride from '$lib/workspace/header/HeaderOverride.svelte';
|
||||||
|
import WorkspaceBreadcrumbs from '$lib/workspace/header/WorkspaceBreadcrumbs.svelte';
|
||||||
import SidebarOverride from '$lib/workspace/sidebar/SidebarOverride.svelte';
|
import SidebarOverride from '$lib/workspace/sidebar/SidebarOverride.svelte';
|
||||||
import WorkspaceSidebar from '$lib/workspace/sidebar/WorkspaceSidebar.svelte';
|
import WorkspaceSidebar from '$lib/workspace/sidebar/WorkspaceSidebar.svelte';
|
||||||
import '$lib/workspace/styles/workspace-pages.css';
|
import '$lib/workspace/styles/workspace-pages.css';
|
||||||
@@ -10,6 +12,10 @@
|
|||||||
let { data, children }: LayoutProps = $props();
|
let { data, children }: LayoutProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet workspaceHeader()}
|
||||||
|
<WorkspaceBreadcrumbs workspaceId={page.params.workspaceId ?? data.workspace?.workspace_id ?? ''} />
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#snippet workspaceSidebar()}
|
{#snippet workspaceSidebar()}
|
||||||
<WorkspaceSidebar
|
<WorkspaceSidebar
|
||||||
workspace={data.workspace ?? null}
|
workspace={data.workspace ?? null}
|
||||||
@@ -20,6 +26,7 @@
|
|||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
<HeaderOverride content={workspaceHeader} />
|
||||||
<SidebarOverride sidebar={workspaceSidebar} />
|
<SidebarOverride sidebar={workspaceSidebar} />
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
<div class="objective-title-row detail">
|
<div class="objective-title-row detail">
|
||||||
<div>
|
<div>
|
||||||
<h3>{data.objective.title}</h3>
|
<h3>{data.objective.title}</h3>
|
||||||
<p><code>{data.objective.id}</code></p>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="state-pill">{data.objective.state}</span>
|
<span class="state-pill">{data.objective.state}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
<div class="repository-detail-heading">
|
<div class="repository-detail-heading">
|
||||||
<div>
|
<div>
|
||||||
<h3>{data.repository.item.display_name}</h3>
|
<h3>{data.repository.item.display_name}</h3>
|
||||||
<p><code>{data.repository.item.id}</code></p>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="status-pill" class:warn={data.repository.item.git?.status !== 'clean'}>{data.repository.item.git?.status ?? 'not observed'}</span>
|
<span class="status-pill" class:warn={data.repository.item.git?.status !== 'clean'}>{data.repository.item.git?.status ?? 'not observed'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
-10
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from "svelte";
|
import { tick, untrack } from "svelte";
|
||||||
import ConsoleLineItem from "$lib/workspace/console/ConsoleLineItem.svelte";
|
import ConsoleLineItem from "$lib/workspace/console/ConsoleLineItem.svelte";
|
||||||
import ConsoleTimeline from "$lib/workspace/console/ConsoleTimeline.svelte";
|
import ConsoleTimeline from "$lib/workspace/console/ConsoleTimeline.svelte";
|
||||||
import { chatSubmit } from "$lib/workspace/console/chat-submit";
|
import { chatSubmit } from "$lib/workspace/console/chat-submit";
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
runtimeId: string;
|
runtimeId: string;
|
||||||
workerId: string;
|
workerId: string;
|
||||||
|
worker: Worker | null;
|
||||||
|
workerError: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,9 +86,11 @@
|
|||||||
client: number;
|
client: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let worker = $state<Worker | null>(null);
|
let worker = $state<Worker | null>(untrack(() => data.worker));
|
||||||
let liveWorkerState = $state<string | null>(null);
|
let liveWorkerState = $state<string | null>(
|
||||||
let workerError = $state<string | null>(null);
|
untrack(() => data.worker?.state ?? null),
|
||||||
|
);
|
||||||
|
let workerError = $state<string | null>(untrack(() => data.workerError));
|
||||||
let draft = $state("");
|
let draft = $state("");
|
||||||
let completionEntries = $state<ComposerCompletionEntry[]>([]);
|
let completionEntries = $state<ComposerCompletionEntry[]>([]);
|
||||||
let completionToken = $state<ComposerCompletionToken | null>(null);
|
let completionToken = $state<ComposerCompletionToken | null>(null);
|
||||||
@@ -193,7 +197,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadConsoleData(target: ConsoleTarget) {
|
async function loadConsoleData(target: ConsoleTarget) {
|
||||||
await loadWorker(target);
|
if (!worker) await loadWorker(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function advanceReloadToken(): number {
|
function advanceReloadToken(): number {
|
||||||
@@ -1101,10 +1105,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="console-shell worker-console-shell">
|
<div class="console-shell worker-console-shell">
|
||||||
<section class="console-header card">
|
<section class="console-header card" aria-label="Worker controls">
|
||||||
<div>
|
|
||||||
<h2>{worker?.label ?? workerId}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="console-header-actions">
|
<div class="console-header-actions">
|
||||||
<div
|
<div
|
||||||
class="console-status-pill"
|
class="console-status-pill"
|
||||||
@@ -1413,7 +1414,7 @@
|
|||||||
.console-header {
|
.console-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-6
@@ -1,11 +1,23 @@
|
|||||||
export function load(
|
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||||
{ params }: {
|
import type { Worker } from "$lib/workspace/sidebar/types";
|
||||||
params: { workspaceId: string; runtimeId: string; workerId: string };
|
import type { PageLoad } from "./$types";
|
||||||
},
|
|
||||||
) {
|
export const load: PageLoad = async ({ fetch, params }) => {
|
||||||
|
const worker = await loadJson<Worker>(
|
||||||
|
fetch,
|
||||||
|
workspaceApiPath(
|
||||||
|
params.workspaceId,
|
||||||
|
`/runtimes/${encodeURIComponent(params.runtimeId)}/workers/${
|
||||||
|
encodeURIComponent(params.workerId)
|
||||||
|
}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workspaceId: params.workspaceId,
|
workspaceId: params.workspaceId,
|
||||||
runtimeId: params.runtimeId,
|
runtimeId: params.runtimeId,
|
||||||
workerId: params.workerId,
|
workerId: params.workerId,
|
||||||
|
worker: worker.data,
|
||||||
|
workerError: worker.error,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|||||||
+2
-4
@@ -7,7 +7,7 @@
|
|||||||
fetchProfileTreeFile,
|
fetchProfileTreeFile,
|
||||||
writeProfileTreeFile,
|
writeProfileTreeFile,
|
||||||
} from '$lib/workspace/settings/profile-api';
|
} from '$lib/workspace/settings/profile-api';
|
||||||
import { profileSettingsHref, virtualProfilePathForCreate } from '$lib/workspace/settings/profile-routes';
|
import { virtualProfilePathForCreate } from '$lib/workspace/settings/profile-routes';
|
||||||
import type { Diagnostic } from '$lib/workspace/settings/model';
|
import type { Diagnostic } from '$lib/workspace/settings/model';
|
||||||
import type {
|
import type {
|
||||||
WorkspaceProfileSourceTreeFileResponse,
|
WorkspaceProfileSourceTreeFileResponse,
|
||||||
@@ -122,10 +122,8 @@
|
|||||||
<section class="card settings-section" aria-labelledby="profile-tree-title">
|
<section class="card settings-section" aria-labelledby="profile-tree-title">
|
||||||
<header class="settings-section-header">
|
<header class="settings-section-header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Profile source tree</p>
|
<h2 id="profile-tree-title">Profile source tree</h2>
|
||||||
<h2 id="profile-tree-title">{sourceTreeId}</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<a href={profileSettingsHref(workspaceId)}>Back to profiles</a>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
|
|||||||
@@ -164,15 +164,10 @@
|
|||||||
<svelte:head><title>{ticket.title} · Yoi</title></svelte:head>
|
<svelte:head><title>{ticket.title} · Yoi</title></svelte:head>
|
||||||
|
|
||||||
<div class="workspace-page ticket-detail-page">
|
<div class="workspace-page ticket-detail-page">
|
||||||
<a class="workspace-back-link" href={`/w/${encodeURIComponent(data.workspaceId)}/tickets`}>
|
|
||||||
← Ticket board
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<header class="ticket-detail-header">
|
<header class="ticket-detail-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="ticket-detail-kicker">
|
<div class="ticket-detail-kicker">
|
||||||
<span class="workspace-status-pill" data-status={ticket.state}>{ticket.state}</span>
|
<span class="workspace-status-pill" data-status={ticket.state}>{ticket.state}</span>
|
||||||
<code>{ticket.id}</code>
|
|
||||||
</div>
|
</div>
|
||||||
<h1>{ticket.title}</h1>
|
<h1>{ticket.title}</h1>
|
||||||
<p>Updated {prettyDate(ticket.updated_at)}</p>
|
<p>Updated {prettyDate(ticket.updated_at)}</p>
|
||||||
|
|||||||
@@ -272,7 +272,6 @@
|
|||||||
<h1 id="new-worker-heading">New Worker</h1>
|
<h1 id="new-worker-heading">New Worker</h1>
|
||||||
<p>Create a Worker on a selected Runtime. Workdir-less conversation Workers are only available on embedded Runtime.</p>
|
<p>Create a Worker on a selected Runtime. Workdir-less conversation Workers are only available on embedded Runtime.</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="secondary-link" href={`/w/${workspaceId}`}>Back to workspace</a>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if ticketContext}
|
{#if ticketContext}
|
||||||
|
|||||||
Reference in New Issue
Block a user