feat(agents): role-specific tabs + bttask Tauri backend

- TaskBoardTab: kanban board (5 columns, CRUD, comments, 5s poll) for Manager
- ArchitectureTab: PlantUML viewer/editor (4 templates, plantuml.com) for Architect
- TestingTab: Selenium screenshots + test file discovery for Tester
- bttask.rs: Rust backend (list, create, update_status, delete, comments)
- bttask-bridge.ts: TypeScript IPC adapter
- ProjectBox: conditional role tabs (isAgent && agentRole), PERSISTED-LAZY
This commit is contained in:
DexterFromLab 2026-03-11 15:25:41 +01:00
parent 0c28f204c7
commit 2ca7756a74
9 changed files with 1812 additions and 2 deletions

View file

@ -11,7 +11,10 @@
import FilesTab from './FilesTab.svelte';
import SshTab from './SshTab.svelte';
import MemoriesTab from './MemoriesTab.svelte';
import { getTerminalTabs } from '../../stores/workspace.svelte';
import TaskBoardTab from './TaskBoardTab.svelte';
import ArchitectureTab from './ArchitectureTab.svelte';
import TestingTab from './TestingTab.svelte';
import { getTerminalTabs, getActiveGroup } from '../../stores/workspace.svelte';
import { getProjectHealth, setStallThreshold } from '../../stores/health.svelte';
import { fsWatchProject, fsUnwatchProject, onFsWriteDetected, fsWatcherStatus } from '../../adapters/fs-watcher-bridge';
import { recordExternalWrite } from '../../stores/conflicts.svelte';
@ -31,9 +34,13 @@
let mainSessionId = $state<string | null>(null);
let terminalExpanded = $state(false);
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories';
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'tasks' | 'architecture' | 'selenium' | 'tests';
let activeTab = $state<ProjectTab>('model');
let activeGroup = $derived(getActiveGroup());
let agentRole = $derived(project.agentRole);
let isAgent = $derived(project.isAgent ?? false);
// PERSISTED-LAZY: track which tabs have been activated at least once
let everActivated = $state<Record<string, boolean>>({});
@ -149,6 +156,16 @@
class:active={activeTab === 'memories'}
onclick={() => switchTab('memories')}
>Memory</button>
{#if isAgent && agentRole === 'manager'}
<button class="ptab ptab-role" class:active={activeTab === 'tasks'} onclick={() => switchTab('tasks')}>Tasks</button>
{/if}
{#if isAgent && agentRole === 'architect'}
<button class="ptab ptab-role" class:active={activeTab === 'architecture'} onclick={() => switchTab('architecture')}>Arch</button>
{/if}
{#if isAgent && agentRole === 'tester'}
<button class="ptab ptab-role" class:active={activeTab === 'selenium'} onclick={() => switchTab('selenium')}>Selenium</button>
<button class="ptab ptab-role" class:active={activeTab === 'tests'} onclick={() => switchTab('tests')}>Tests</button>
{/if}
</div>
<div class="project-content-area">
@ -182,6 +199,26 @@
<MemoriesTab />
</div>
{/if}
{#if everActivated['tasks'] && activeGroup}
<div class="content-pane" style:display={activeTab === 'tasks' ? 'flex' : 'none'}>
<TaskBoardTab groupId={activeGroup.id} projectId={project.id} />
</div>
{/if}
{#if everActivated['architecture']}
<div class="content-pane" style:display={activeTab === 'architecture' ? 'flex' : 'none'}>
<ArchitectureTab cwd={project.cwd} />
</div>
{/if}
{#if everActivated['selenium']}
<div class="content-pane" style:display={activeTab === 'selenium' ? 'flex' : 'none'}>
<TestingTab cwd={project.cwd} mode="selenium" />
</div>
{/if}
{#if everActivated['tests']}
<div class="content-pane" style:display={activeTab === 'tests' ? 'flex' : 'none'}>
<TestingTab cwd={project.cwd} mode="tests" />
</div>
{/if}
</div>
<div class="terminal-section" style:display={activeTab === 'model' ? 'flex' : 'none'}>
@ -267,6 +304,14 @@
margin-bottom: -1px;
}
.ptab-role {
color: var(--ctp-mauve);
}
.ptab-role:hover {
color: var(--ctp-text);
}
.project-content-area {
overflow: hidden;
position: relative;