8.6 KiB
Review package Task 3
Commits
95dce81 feat: add project channel workbench shell
Stat
apps/web/src/app/App.svelte | 6 +-- .../workbench/ProjectChannelSidebar.svelte | 52 ++++++++++++++++++++++ apps/web/src/features/workbench/ProjectRail.svelte | 26 +++++++++++ .../src/features/workbench/ProjectWorkbench.svelte | 41 +++++++++++++++++ .../features/workbench/ProjectWorkbench.test.ts | 26 +++++++++++ .../src/features/workbench/WorkspaceTopbar.svelte | 16 +++++++ 6 files changed, 163 insertions(+), 4 deletions(-)
Diff
diff --git a/apps/web/src/app/App.svelte b/apps/web/src/app/App.svelte index a5d8dce..e7170ef 100644 --- a/apps/web/src/app/App.svelte +++ b/apps/web/src/app/App.svelte @@ -1,21 +1,19 @@
<script lang="ts"> + import ProjectWorkbench from '../features/workbench/ProjectWorkbench.svelte'; import ServerLogin from '../features/auth/ServerLogin.svelte'; let apiBase = localStorage.getItem('apiBase') ?? 'http://localhost:8080'; let currentUser: { account: string } | null = null; function handleLogin(detail: { apiBase: string; account: string }) { apiBase = detail.apiBase; localStorage.setItem('apiBase', apiBase); currentUser = { account: detail.account }; } </script>{#if currentUser}
-
SenlinAI Workbench
-
Signed in as {currentUser.account}
- <ProjectWorkbench {currentUser} /> {:else} <ServerLogin {apiBase} onLogin={handleLogin} /> {/if} diff --git a/apps/web/src/features/workbench/ProjectChannelSidebar.svelte b/apps/web/src/features/workbench/ProjectChannelSidebar.svelte new file mode 100644 index 0000000..40cb1a0 --- /dev/null +++ b/apps/web/src/features/workbench/ProjectChannelSidebar.svelte @@ -0,0 +1,52 @@ +<script lang="ts">
- import type { AISessionItem, WorkbenchChannel, WorkbenchProject } from './types';
- export let project: WorkbenchProject;
- export let channels: WorkbenchChannel[];
- export let selectedChannelID: string;
- export let tags: string[];
- export let recentSessions: AISessionItem[];
- export let onSelectChannel: (channelID: string) => void; +</script>
+
diff --git a/apps/web/src/features/workbench/ProjectRail.svelte b/apps/web/src/features/workbench/ProjectRail.svelte new file mode 100644 index 0000000..d1be341 --- /dev/null +++ b/apps/web/src/features/workbench/ProjectRail.svelte @@ -0,0 +1,26 @@ +<script lang="ts">- import type { WorkbenchProject } from './types';
- export let projects: WorkbenchProject[];
- export let selectedProjectID: number;
- export let onSelectProject: (projectID: number) => void; +</script>
+
- SA
- {#each projects as project}
- <button
-
class:active={project.id === selectedProjectID} -
aria-pressed={project.id === selectedProjectID} -
aria-label={project.name} -
title={project.name} -
on:click={() => onSelectProject(project.id)} -
-
<span>{project.initials}</span> -
{#if project.unreadCount} -
<small>{project.unreadCount}</small> -
{/if} - {/each}
- + +
+
-
- <ProjectRail projects={workbenchProjects} {selectedProjectID} onSelectProject={selectProject} />
- <ProjectChannelSidebar
-
project={workspace.project} -
channels={workspace.channels} -
{selectedChannelID} -
tags={workspace.tags} -
recentSessions={workspace.recentSessions} -
onSelectChannel={selectChannel} - />
-
-
<h1>{workspace.channels.find((channel) => channel.id === selectedChannelID)?.title}</h1> -
<p>Select an item to inspect discussion, properties, and actions.</p>
+
diff --git a/apps/web/src/features/workbench/ProjectWorkbench.test.ts b/apps/web/src/features/workbench/ProjectWorkbench.test.ts new file mode 100644 index 0000000..cf251b3 --- /dev/null +++ b/apps/web/src/features/workbench/ProjectWorkbench.test.ts @@ -0,0 +1,26 @@ +import '@testing-library/jest-dom/vitest'; +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it } from 'vitest'; +import ProjectWorkbench from './ProjectWorkbench.svelte'; + +describe('ProjectWorkbench', () => {- it('renders project rail, channel sidebar, topbar, and inspector', () => {
- render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } });
- expect(screen.getByLabelText('Project list')).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Project A1' })).toHaveAttribute('aria-pressed', 'true');
- expect(screen.getByLabelText('Project channels')).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Message Flow 36' })).toBeInTheDocument();
- expect(screen.getByLabelText('Global search')).toBeInTheDocument();
- expect(screen.getByLabelText('Object inspector')).toBeInTheDocument();
- });
- it('refreshes channels when switching projects', async () => {
- render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } });
- await fireEvent.click(screen.getByRole('button', { name: 'Project A2' }));
- expect(screen.getByRole('button', { name: 'Project A2' })).toHaveAttribute('aria-pressed', 'true');
- expect(screen.getByText('Ops Dashboard')).toBeInTheDocument();
- }); +}); diff --git a/apps/web/src/features/workbench/WorkspaceTopbar.svelte b/apps/web/src/features/workbench/WorkspaceTopbar.svelte new file mode 100644 index 0000000..c828fcf --- /dev/null +++ b/apps/web/src/features/workbench/WorkspaceTopbar.svelte @@ -0,0 +1,16 @@ +<script lang="ts">
- export let account: string; +</script>
+
-
SenlinAI
- Search
-
- ←
- →
- <button aria-label={
Account ${account}}>{account.slice(0, 2).toUpperCase()}
+