chore: merge hare/develop into develop
This commit is contained in:
@@ -115,14 +115,12 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async ()
|
||||
"top workspace page should not own the Worker list",
|
||||
);
|
||||
assert(
|
||||
workersPage.includes("workerConsoleHref(worker, data.workspaceId)") &&
|
||||
workersPage.includes('<table class="workers-table">') &&
|
||||
workersPage.includes(
|
||||
"workerDisplayName = worker.display_name || worker.label",
|
||||
) &&
|
||||
workersPage.includes("worker <code>{worker.worker_id}</code>") &&
|
||||
workersPage.includes("workerHref") &&
|
||||
workersPage.includes("workers-table") &&
|
||||
workersPage.includes("workerDisplayName") &&
|
||||
workersPage.includes("worker.resource_key") &&
|
||||
workersPage.includes("Delete ${workerDisplayName}"),
|
||||
"dedicated Workers page should expose a table, console link target, and icon actions per Worker",
|
||||
"dedicated Workers page should expose a table, canonical Worker link target, and icon actions per Worker",
|
||||
);
|
||||
assert(
|
||||
workersNav.includes("href={`/w/${workspaceId}/workers`}") &&
|
||||
@@ -218,14 +216,16 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as
|
||||
"Tickets and Objectives should each be a single sidebar link",
|
||||
);
|
||||
assert(
|
||||
ticketsLoad.includes("?limit=1000") &&
|
||||
ticketsLoad.includes("Object.entries(LANE_STATES)") &&
|
||||
ticketsLoad.includes('limit: "30"') &&
|
||||
ticketsLoad.includes('states: states.join(",")') &&
|
||||
ticketsLoad.includes("/tickets?${search}") &&
|
||||
!ticketsLoad.includes("/tickets/query") &&
|
||||
ticketsPage.includes('class="ticket-kanban"') &&
|
||||
ticketsPage.includes('class="ticket-lane-cards"') &&
|
||||
ticketsPage.includes("lane.tickets.slice(0, lane.visibleCount)") &&
|
||||
ticketsPage.includes("handleLaneScroll") &&
|
||||
ticketsPage.includes("revealNextTickets"),
|
||||
"Tickets list should fetch lightweight summaries once and incrementally reveal each Kanban lane",
|
||||
ticketsPage.includes("laneState") &&
|
||||
ticketsPage.includes("loadMore(lane.id)") &&
|
||||
ticketsPage.includes("handleLaneScroll(event, lane.id)"),
|
||||
"Tickets list should fetch lightweight paginated summaries for each Kanban lane",
|
||||
);
|
||||
assert(
|
||||
ticketPanelModel.includes('label: "Ready + Planning"') &&
|
||||
@@ -250,16 +250,17 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as
|
||||
assert(
|
||||
ticketDetailLoad.includes("/repositories") &&
|
||||
ticketDetailPage.includes('mutate("state", "/state"') &&
|
||||
ticketDetailPage.includes('mutate("queue", "/queue"') &&
|
||||
ticketDetailPage.includes("async function queueTicket") &&
|
||||
ticketDetailPage.includes("`${ticketPath}/queue`") &&
|
||||
!ticketDetailPage.includes("/merge-request/merge") &&
|
||||
ticketDetailPage.includes("mergeRequest.selector_from") &&
|
||||
ticketDetailPage.includes("currentReview?.kind") &&
|
||||
ticketDetailPage.includes("mergeEvent?.kind") &&
|
||||
!ticketDetailPage.includes('mutate("review", "/review"') &&
|
||||
ticketDetailPage.includes("mergeRequest.review_status") &&
|
||||
ticketDetailPage.includes("mergeRequestPagePath") &&
|
||||
ticketDetailPage.includes('mutate("close", "/close"') &&
|
||||
ticketDetailPage.includes("ticketWorkerLaunchHref") &&
|
||||
ticketDetailPage.includes("mutateAssignment") &&
|
||||
ticketDetailPage.includes("can_start_manual_coder") &&
|
||||
ticketDetailPage.includes("ticket.relations.outgoing"),
|
||||
"Ticket detail should expose typed lifecycle actions, relations, target selection, and role Worker launch",
|
||||
"Ticket detail should expose typed lifecycle actions, relations, target selection, assignments, and Merge Request navigation",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -319,17 +320,19 @@ Deno.test("workspace Memory surfaces use read-only scoped memory APIs", async ()
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("root layout does not keep legacy unscoped route compatibility", async () => {
|
||||
Deno.test("root layout keeps Workspace selection explicit", async () => {
|
||||
const layoutLoad = await Deno.readTextFile(
|
||||
new URL("./../../../routes/+layout.ts", import.meta.url),
|
||||
);
|
||||
|
||||
assert(
|
||||
!layoutLoad.includes("scopedCompatibilityRoute") &&
|
||||
!layoutLoad.includes('pathname === "/runtimes"') &&
|
||||
!layoutLoad.includes("return workspaceRoute(workspaceId, pathname)") &&
|
||||
layoutLoad.includes("workspaceRoute(workspace.data.workspace_id)"),
|
||||
"root layout should bootstrap the workspace entry only, not preserve legacy unscoped routes",
|
||||
layoutLoad.includes("export const load") &&
|
||||
layoutLoad.includes("() => ({})") &&
|
||||
!layoutLoad.includes("scopedCompatibilityRoute") &&
|
||||
!layoutLoad.includes("/api/workspace") &&
|
||||
!layoutLoad.includes("workspaceRoute") &&
|
||||
!layoutLoad.includes("redirect("),
|
||||
"root layout should not infer, bootstrap, or redirect through a singleton Workspace",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -811,9 +814,11 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
|
||||
"SidebarOverride should register and clean up the child-provided sidebar snippet",
|
||||
);
|
||||
assert(
|
||||
rootLayoutLoad.includes('"/account"') &&
|
||||
rootLayoutLoad.includes('"/login/device"'),
|
||||
"Root layout should not redirect account and device-login public routes to a workspace",
|
||||
rootLayoutLoad.includes("export const load") &&
|
||||
rootLayoutLoad.includes("() => ({})") &&
|
||||
!rootLayoutLoad.includes("workspaceRoute") &&
|
||||
!rootLayoutLoad.includes("redirect("),
|
||||
"Root layout should leave account and device-login routes public by avoiding Workspace redirects entirely",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export type RuntimeConnectionSummary = {
|
||||
built_in: boolean;
|
||||
config_managed: boolean;
|
||||
active: boolean;
|
||||
can_spawn_worker: boolean;
|
||||
worker_creation_available: boolean;
|
||||
restart_required: boolean;
|
||||
status: string;
|
||||
diagnostics: Diagnostic[];
|
||||
|
||||
@@ -29,30 +29,15 @@ export type Diagnostic = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type RuntimeCapabilities = {
|
||||
can_list_hosts: boolean;
|
||||
can_list_workers: boolean;
|
||||
can_get_worker: boolean;
|
||||
can_spawn_worker: boolean;
|
||||
can_stop_worker: boolean;
|
||||
has_workspace_fs: boolean;
|
||||
has_shell: boolean;
|
||||
has_git: boolean;
|
||||
supports_worktrees: boolean;
|
||||
supports_backend_internal_tools: boolean;
|
||||
workspace_scope: string;
|
||||
os: string;
|
||||
arch: string;
|
||||
max_workers: number;
|
||||
};
|
||||
|
||||
export type Runtime = {
|
||||
runtime_id: string;
|
||||
label: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
host_ids: string[];
|
||||
capabilities: RuntimeCapabilities;
|
||||
worker_creation_available: boolean;
|
||||
os: string;
|
||||
arch: string;
|
||||
diagnostics: Diagnostic[];
|
||||
};
|
||||
|
||||
@@ -64,7 +49,8 @@ export type Host = {
|
||||
status: string;
|
||||
observed_at: string;
|
||||
last_seen_at: string | null;
|
||||
capabilities: RuntimeCapabilities;
|
||||
os: string;
|
||||
arch: string;
|
||||
diagnostics: Diagnostic[];
|
||||
};
|
||||
|
||||
@@ -100,7 +86,7 @@ export type WorkerLaunchRuntimeOption = {
|
||||
runtime_id: string;
|
||||
display_name: string;
|
||||
built_in: boolean;
|
||||
can_spawn_worker: boolean;
|
||||
worker_creation_available: boolean;
|
||||
working_directory_required: boolean;
|
||||
status: string;
|
||||
diagnostics: Diagnostic[];
|
||||
|
||||
@@ -23,7 +23,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||
runtime_id: "remote",
|
||||
display_name: "Remote",
|
||||
status: "active",
|
||||
can_spawn_worker: true,
|
||||
worker_creation_available: true,
|
||||
built_in: false,
|
||||
working_directory_required: true,
|
||||
diagnostics: [],
|
||||
@@ -32,7 +32,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||
runtime_id: "embedded",
|
||||
display_name: "Embedded",
|
||||
status: "active",
|
||||
can_spawn_worker: true,
|
||||
worker_creation_available: true,
|
||||
built_in: true,
|
||||
working_directory_required: false,
|
||||
diagnostics: [],
|
||||
|
||||
@@ -30,9 +30,9 @@ export function defaultWorkerLaunchForm(
|
||||
): WorkerLaunchFormState {
|
||||
const preferredRuntime =
|
||||
options?.runtimes.find((runtime) =>
|
||||
runtime.can_spawn_worker && runtime.status === "active"
|
||||
runtime.worker_creation_available && runtime.status === "active"
|
||||
) ??
|
||||
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ??
|
||||
options?.runtimes.find((runtime) => runtime.worker_creation_available) ??
|
||||
options?.runtimes[0];
|
||||
const preferredProfile = options?.profiles.find((candidate) =>
|
||||
candidate.id === options.default_profile
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<dt>Platform</dt>
|
||||
<dd>{host.capabilities.os} / {host.capabilities.arch}</dd>
|
||||
<dd>{host.os} / {host.arch}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
built_in: true,
|
||||
config_managed: false,
|
||||
active: false,
|
||||
can_spawn_worker: false,
|
||||
worker_creation_available: false,
|
||||
restart_required: false,
|
||||
status: 'unknown',
|
||||
diagnostics: []
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
function runtimePlatform(runtime: Runtime): string {
|
||||
return `${runtime.capabilities.os} / ${runtime.capabilities.arch}`;
|
||||
return `${runtime.os} / ${runtime.arch}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<th>Kind</th>
|
||||
<th>Status</th>
|
||||
<th>Platform</th>
|
||||
<th>Capacity</th>
|
||||
<th>Workdirs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -51,7 +50,6 @@
|
||||
<td>{runtime.kind}</td>
|
||||
<td>{runtime.status}</td>
|
||||
<td>{runtimePlatform(runtime)}</td>
|
||||
<td>{runtime.capabilities.max_workers} workers</td>
|
||||
<td>
|
||||
<a class="inline-link" href={`/w/${data.workspaceId}/settings/runtimes/${encodeURIComponent(runtime.runtime_id)}/workdirs`}>
|
||||
Open workdirs
|
||||
|
||||
@@ -313,7 +313,7 @@
|
||||
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">
|
||||
{#if options?.runtimes.length}
|
||||
{#each options.runtimes as runtime}
|
||||
<option value={runtime.runtime_id} disabled={!runtime.can_spawn_worker}>
|
||||
<option value={runtime.runtime_id} disabled={!runtime.worker_creation_available}>
|
||||
{runtime.display_name}
|
||||
</option>
|
||||
{/each}
|
||||
|
||||
@@ -5,6 +5,9 @@ export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
|
||||
server: {
|
||||
host: "localhost",
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
allowedHosts: ["develop.hareworks.net"],
|
||||
watch: {
|
||||
ignored: [
|
||||
|
||||
Reference in New Issue
Block a user