Merge branch 'work/companion' into hare/develop

This commit is contained in:
2026-09-03 03:06:10 +09:00
6 changed files with 51 additions and 35 deletions
@@ -43,17 +43,21 @@ Deno.test("root layout leaves Workspace selection explicit", async () => {
new URL("./../../../routes/+layout.ts", import.meta.url),
);
assert(
!layout.includes("/api/workspace") &&
!layout.includes('"/api/workspace"') &&
!layout.includes("redirect(") &&
layout.includes("Workspace selection is explicit"),
"root layout must not infer or redirect to a singleton Workspace",
layout.includes("listWorkspaces(fetch)") &&
layout.includes("accessibleWorkspaces"),
"root layout may list accessible Workspaces but must not infer or redirect to a singleton Workspace",
);
});
Deno.test("Workspace route changes dispose old multiplexed subscription state", async () => {
const [layout, multiplexer] = await Promise.all([
Deno.readTextFile(
new URL("./../../../routes/w/[workspaceId]/+layout.svelte", import.meta.url),
new URL(
"./../../../routes/w/[workspaceId]/+layout.svelte",
import.meta.url,
),
),
Deno.readTextFile(new URL("./../multiplexer.ts", import.meta.url)),
]);
@@ -358,9 +358,10 @@ Deno.test("root layout keeps Workspace selection explicit", async () => {
assert(
layoutLoad.includes("export const load") &&
layoutLoad.includes("() => ({})") &&
layoutLoad.includes("listWorkspaces(fetch)") &&
layoutLoad.includes("accessibleWorkspaces") &&
!layoutLoad.includes("scopedCompatibilityRoute") &&
!layoutLoad.includes("/api/workspace") &&
!layoutLoad.includes('"/api/workspace"') &&
!layoutLoad.includes("workspaceRoute") &&
!layoutLoad.includes("redirect("),
"root layout should not infer, bootstrap, or redirect through a singleton Workspace",
@@ -815,9 +816,6 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
const globalNavSections = await Deno.readTextFile(
new URL("../sidebar/GlobalNavSections.svelte", import.meta.url),
);
const workspaceCatalogPage = await Deno.readTextFile(
new URL("./../../../routes/+page.svelte", import.meta.url),
);
const sidebarFrame = await Deno.readTextFile(
new URL("../sidebar/SidebarFrame.svelte", import.meta.url),
);
@@ -891,13 +889,12 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
globalNavSections.includes("workspaces") &&
globalNavSections.includes("workspace.display_name") &&
globalNavSections.includes("workspaceHref(workspace.workspace_id)") &&
workspaceCatalogPage.includes("SidebarOverride") &&
workspaceCatalogPage.includes("sidebar={homeSidebar}") &&
workspaceCatalogPage.includes("GlobalNavSections") &&
workspaceCatalogPage.includes("{workspaces}") &&
rootLayout.includes("workspaces={data.accessibleWorkspaces}") &&
rootLayout.includes("workspaceError={data.workspaceCatalogError}") &&
rootLayoutLoad.includes("listWorkspaces(fetch)") &&
!globalNavSections.includes("Tickets") &&
!globalNavSections.includes("Repositories"),
"Root page sidebar should replace the Workspaces button with a categorized accessible Workspace list below the remaining global navigation",
"Top-level sidebar should replace the Workspaces button with a categorized accessible Workspace list below the remaining global navigation",
);
assert(
workspaceLayout.includes("{#snippet workspaceSidebar()}") &&
@@ -935,7 +932,8 @@ Deno.test("Account UI owns browser passkey session state without workspace autho
);
assert(
rootLayoutLoad.includes("export const load") &&
rootLayoutLoad.includes("() => ({})") &&
rootLayoutLoad.includes("listWorkspaces(fetch)") &&
rootLayoutLoad.includes("accessibleWorkspaces") &&
!rootLayoutLoad.includes("workspaceRoute") &&
!rootLayoutLoad.includes("redirect("),
"Root layout should leave account and device-login routes public by avoiding Workspace redirects entirely",
@@ -1,4 +1,5 @@
<script lang="ts">
import type { WorkspaceCatalogRecord } from '../api/workspace-catalog';
import type { SidebarSnippet } from './context';
import GlobalNavSections from './GlobalNavSections.svelte';
import './sidebar.css';
@@ -6,9 +7,16 @@
type Props = {
currentPath: string;
content?: SidebarSnippet | null;
workspaces?: WorkspaceCatalogRecord[];
workspaceError?: string | null;
};
const { currentPath, content = null }: Props = $props();
const {
currentPath,
content = null,
workspaces = [],
workspaceError = null,
}: Props = $props();
</script>
{#if content}
@@ -16,7 +24,7 @@
{:else}
<div class="global-sidebar" aria-label="Global navigation">
<div class="global-sidebar-section">
<GlobalNavSections {currentPath} />
<GlobalNavSections {currentPath} {workspaces} {workspaceError} />
</div>
</div>
{/if}
+7 -2
View File
@@ -10,7 +10,7 @@
import '../app.css';
import type { LayoutProps } from './$types';
let { children }: LayoutProps = $props();
let { children, data }: LayoutProps = $props();
let sidebar = $state<SidebarSnippet | null>(null);
const sidebarOverrides = createOverrideStack<SidebarSnippet>((activeSidebar) => {
sidebar = activeSidebar;
@@ -27,7 +27,12 @@
<div class="app-shell">
<SidebarFrame>
<GlobalSidebar currentPath={page.url.pathname} content={sidebar} />
<GlobalSidebar
currentPath={page.url.pathname}
content={sidebar}
workspaces={data.accessibleWorkspaces}
workspaceError={data.workspaceCatalogError}
/>
</SidebarFrame>
<header class="app-shell__topbar">
<div class="app-shell__topbar-location">
+17 -4
View File
@@ -1,5 +1,18 @@
import type { LayoutLoad } from './$types';
import { listWorkspaces } from "$lib/workspace/api/workspace-catalog";
import type { LayoutLoad } from "./$types";
// Workspace selection is explicit at `/`; the root layout must never infer a
// singleton Workspace or redirect based on an unscoped compatibility endpoint.
export const load: LayoutLoad = () => ({});
export const load: LayoutLoad = async ({ fetch }) => {
try {
return {
accessibleWorkspaces: await listWorkspaces(fetch),
workspaceCatalogError: null,
};
} catch (error) {
return {
accessibleWorkspaces: [],
workspaceCatalogError: error instanceof Error
? error.message
: "Unable to load Workspaces",
};
}
};
-12
View File
@@ -8,8 +8,6 @@
type CreateWorkspaceRequest,
type WorkspaceCatalogItem,
} from "$lib/workspace/api/workspace-catalog";
import GlobalNavSections from "$lib/workspace/sidebar/GlobalNavSections.svelte";
import SidebarOverride from "$lib/workspace/sidebar/SidebarOverride.svelte";
import "$lib/workspace/styles/workspace-catalog.css";
let { data } = $props();
@@ -89,16 +87,6 @@
}
</script>
{#snippet homeSidebar()}
<GlobalNavSections
currentPath="/"
{workspaces}
workspaceError={catalogError}
/>
{/snippet}
<SidebarOverride sidebar={homeSidebar} />
<svelte:head>
<title>Workspaces · Yoi</title>
</svelte:head>