Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
814f492
auto-claude: subtask-1-1 - Expand TaskStatus from 4 to 5 values and a…
siracusa5 Mar 29, 2026
0915d3c
auto-claude: subtask-1-2 - Update MCP task tools: Zod schemas, inputS…
siracusa5 Mar 29, 2026
dc8ac80
auto-claude: subtask-1-4 - Create data migration script for status va…
siracusa5 Mar 29, 2026
5cc99bb
auto-claude: subtask-2-1 - Install clsx, tailwind-merge, lucide-react…
siracusa5 Mar 29, 2026
9818768
auto-claude: subtask-2-2 - Replace board globals.css with warm-tinted…
siracusa5 Mar 29, 2026
a816afc
auto-claude: subtask-2-3 - Port theme system from desktop (light/dark…
siracusa5 Mar 29, 2026
05c96f5
auto-claude: subtask-2-4 - Update board app types and columns for 5-c…
siracusa5 Mar 29, 2026
6c646ab
auto-claude: subtask-3-1 - Redesign TaskCard.tsx — migrate to Tailwin…
siracusa5 Mar 29, 2026
bb5a279
auto-claude: subtask-3-2 - Redesign DroppableColumn.tsx and SortableT…
siracusa5 Mar 29, 2026
4a2b79c
auto-claude: subtask-3-3 - Redesign Sidebar.tsx — Aperant-style secti…
siracusa5 Mar 29, 2026
b3199ab
auto-claude: subtask-3-4 - Update board page for 5-column rendering a…
siracusa5 Mar 29, 2026
1d7986a
auto-claude: subtask-3-5 - Redesign TaskDetailPanel and TaskForm with…
siracusa5 Mar 29, 2026
6e4fd40
auto-claude: subtask-3-6 - Update remaining board components: Swimlan…
siracusa5 Mar 29, 2026
363ea12
auto-claude: subtask-4-1 - Update desktop board types, column config,…
siracusa5 Mar 29, 2026
0319b3d
auto-claude: subtask-4-2 - Mirror TaskCard.tsx and DroppableColumn.ts…
siracusa5 Mar 29, 2026
57586c9
auto-claude: subtask-4-3 - Mirror TaskDetailPanel.tsx and TaskForm.ts…
siracusa5 Mar 29, 2026
badb81a
auto-claude: subtask-4-4 - Mirror remaining desktop components: Swiml…
siracusa5 Mar 29, 2026
71440ec
auto-claude: subtask-5-1 - Verify TypeScript compilation and builds a…
siracusa5 Mar 29, 2026
2cc941d
auto-claude: subtask-5-2 - End-to-end functional verification: 5-colu…
siracusa5 Mar 29, 2026
eb3db2e
fix: update pnpm-lock.yaml after adding clsx, tailwind-merge, lucide-…
siracusa5 Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
=== AUTO-BUILD PROGRESS ===

Project: Redesign Kanban Board to Match Aperant
Workspace: .auto-claude/specs/003-redesign-kanban-board-to-match-auto-claude
Started: 2026-03-28

Workflow Type: feature
Rationale: Comprehensive visual and structural redesign adding new capabilities
(5-column workflow, enriched cards, theme system) across backend, web board,
and desktop app. Feature workflow follows service dependency order.

Session 1 (Planner):
- Created implementation_plan.json
- Phases: 5
- Total subtasks: 20
- Created init.sh
- Updated context.json with investigation findings

Phase Summary:
- Phase 1 — Backend Data Model: 4 subtasks, depends on [] (no dependencies)
• Expand TaskStatus type (types.ts)
• Update MCP tools Zod schemas (task.ts)
• Update yaml-store default status (yaml-store.ts, routes.ts)
• Create migration script (migrate-statuses.ts)

- Phase 2 — Board App Foundations: 4 subtasks, depends on [Phase 1]
• Install deps + create cn() utility
• Replace globals.css with warm-tinted light/dark theme
• Port theme system from desktop (theme.ts + layout.tsx)
• Update api.ts types + columns.ts for 5 columns

- Phase 3 — Board Component Redesign: 6 subtasks, depends on [Phase 2]
• Redesign TaskCard.tsx (status badge, priority, timestamps)
• Redesign DroppableColumn.tsx + SortableTaskCard.tsx (5-column layout)
• Redesign Sidebar.tsx (Aperant-style navigation + theme toggle)
• Update [project]/page.tsx (5-column rendering, DnD updates)
• Redesign TaskDetailPanel.tsx + TaskForm.tsx (5 statuses, priority)
• Update SwimlaneView, ViewToggle, CommentThread, Tooltip

- Phase 4 — Desktop Board Parity: 4 subtasks, depends on [Phase 3]
• Update desktop types + columns + CSS status vars
• Mirror TaskCard + DroppableColumn redesign
• Mirror TaskDetailPanel + TaskForm redesign
• Mirror SwimlaneView, ViewToggle, CommentThread, BoardKanbanPage

- Phase 5 — Integration & Verification: 2 subtasks, depends on [Phase 3, Phase 4]
• TypeScript compilation check across all services
• End-to-end functional verification

Services Involved:
- board-server (packages/board-server): TaskStatus type expansion, Zod schemas, MCP tools, migration script
- board (apps/board): Primary visual redesign — theme, components, Tailwind migration
- desktop (apps/desktop): Mirror all board component changes for desktop parity

Key Technical Decisions:
- CSS variable names: Keep board's existing names (--text-primary, --text-muted) but map to desktop warm values
- DnD-kit: Do NOT upgrade — board v8, desktop v10 (different APIs, out of scope)
- Theme: Port desktop theme.ts to board; add inline <script> in layout.tsx to prevent flash
- Status mapping: backlog→planning, review→ai-review, new: human-review
- request_review MCP tool: Change target from 'review' to 'ai-review'
- createTask default: Change from 'backlog' to 'planning'
- DroppableColumn Add Task: Change condition from 'backlog' to 'planning'

Parallelism Analysis:
- Max parallel phases: 1
- Recommended workers: 1
- Parallel groups: None (strict sequential dependencies)
- Reason: Each phase depends on the previous; board design must finalize before desktop mirroring

=== STARTUP COMMAND ===

To continue building this spec, run:

source auto-claude/.venv/bin/activate && python auto-claude/run.py --spec 003 --parallel 1

=== END SESSION 1 ===

=== SESSION: subtask-5-2 (E2E Verification) ===

Automated Verification Results:
✅ board-server: TypeScript compilation passes
✅ board: TypeScript compilation passes
✅ desktop: TypeScript compilation passes
✅ board-server: Build (tsc) passes
✅ 5-column config verified in all 3 services
✅ Theme toggle integrated in Sidebar
✅ DroppableColumn Add Task uses 'planning' status
✅ Desktop unit tests: 550 pass (2 pre-existing failures unrelated to this work)
⚠️ Next.js build: Requires non-sandboxed environment (port binding)
⚠️ Migration script: Requires non-sandboxed environment (Unix socket)

Manual Browser Verification Needed:
- DnD across 5 columns
- WebSocket real-time sync between tabs
- Theme toggle persistence on reload
- Swimlane view with 5 columns

=== END subtask-5-2 ===

Large diffs are not rendered by default.

78 changes: 22 additions & 56 deletions apps/board/app/[project]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import { TaskCard } from '../components/TaskCard';
import { TaskDetailPanel } from '../components/TaskDetailPanel';
import { TaskForm } from '../components/TaskForm';
import { api } from '../lib/api';
import { cn } from '../lib/utils';
import type { Task, Epic, TaskStatus } from '../lib/api';
import { COLUMNS } from '../lib/columns';

const LS_VIEW_KEY = 'harness:board:view';

function flattenTasks(epics: Epic[]): Task[] {
Expand All @@ -32,9 +34,7 @@ function flattenTasks(epics: Epic[]): Task[] {
function resolveTargetStatus(overId: string, allTasks: Task[]): TaskStatus | null {
if (overId.startsWith('col-')) return overId.replace('col-', '') as TaskStatus;
if (overId.startsWith('swim-')) {
// swim-{epicId}-{status}
const parts = overId.split('-');
// status may contain hyphens (e.g. "in-progress" → parts[2]+parts[3])
return parts.slice(2).join('-') as TaskStatus;
}
if (overId.startsWith('task-')) {
Expand All @@ -51,7 +51,7 @@ export default function BoardPage({ params }: { params: Promise<{ project: strin
const [activeTask, setActiveTask] = useState<Task | null>(null);
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
const [formOpen, setFormOpen] = useState(false);
const [formDefaultStatus, setFormDefaultStatus] = useState<TaskStatus>('backlog');
const [formDefaultStatus, setFormDefaultStatus] = useState<TaskStatus>('planning');
const [formDefaultEpicId, setFormDefaultEpicId] = useState<number | undefined>();
const [viewMode, setViewMode] = useState<ViewMode>('columns');

Expand Down Expand Up @@ -122,83 +122,59 @@ export default function BoardPage({ params }: { params: Promise<{ project: strin

if (loading) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<span style={{ color: 'var(--text-muted)', fontSize: 14 }}>Loading board…</span>
<div className="flex h-full items-center justify-center">
<span className="text-sm text-[var(--text-muted)]">Loading board…</span>
</div>
);
}

if (error || !project) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', flexDirection: 'column', gap: 12 }}>
<span style={{ fontSize: 32 }}>⚠️</span>
<span style={{ color: 'var(--text-secondary)', fontSize: 14 }}>
<div className="flex h-full flex-col items-center justify-center gap-3">
<span className="text-[32px]">⚠️</span>
<span className="text-sm text-[var(--text-secondary)]">
{error ?? `Project "${projectSlug}" not found`}
</span>
<span style={{ color: 'var(--text-muted)', fontSize: 12 }}>Is the board server running on :4800?</span>
<span className="text-xs text-[var(--text-muted)]">Is the board server running on :4800?</span>
</div>
);
}

return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
<div className="flex h-full flex-col overflow-hidden">
{/* Header */}
<div
style={{
padding: '12px 24px',
borderBottom: '1px solid var(--border-subtle)',
display: 'flex',
alignItems: 'center',
gap: 12,
flexShrink: 0,
}}
>
<div className="flex shrink-0 items-center gap-3 border-b border-[var(--border-subtle)] px-6 py-3">
{project.color && (
<span style={{ width: 10, height: 10, borderRadius: '50%', background: project.color, flexShrink: 0 }} />
<span
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ background: project.color }}
/>
)}
<h1 style={{ margin: 0, fontSize: 16, fontWeight: 700, color: 'var(--text-primary)' }}>
<h1 className="m-0 text-base font-bold text-[var(--text-primary)]">
{project.name}
</h1>
{project.description && (
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{project.description}</span>
<span className="text-xs text-[var(--text-muted)]">{project.description}</span>
)}
{project.repo_url && (
<a
href={project.repo_url}
target="_blank"
rel="noopener noreferrer"
title="View on GitHub"
style={{
color: 'var(--text-muted)',
display: 'flex',
alignItems: 'center',
padding: 4,
borderRadius: 4,
transition: 'color 0.1s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'; }}
className="flex items-center rounded p-1 text-[var(--text-muted)] transition-colors duration-100 hover:text-[var(--text-primary)]"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
</svg>
</a>
)}
<span
style={{
fontSize: 11,
color: 'var(--text-muted)',
background: 'var(--bg-elevated)',
borderRadius: 6,
padding: '2px 8px',
border: '1px solid var(--border-subtle)',
}}
>
<span className="rounded-md border border-[var(--border-subtle)] bg-[var(--bg-elevated)] px-2 py-0.5 text-[11px] text-[var(--text-muted)]">
{allTasks.length} task{allTasks.length !== 1 ? 's' : ''}
</span>

{/* View toggle */}
<div style={{ marginLeft: 'auto' }}>
<div className="ml-auto">
<ViewToggle mode={viewMode} onChange={handleViewChange} />
</div>
</div>
Expand All @@ -211,17 +187,7 @@ export default function BoardPage({ params }: { params: Promise<{ project: strin
onDragEnd={handleDragEnd}
>
{viewMode === 'columns' ? (
<div
style={{
flex: 1,
display: 'flex',
gap: 16,
padding: 20,
overflowX: 'auto',
overflowY: 'hidden',
alignItems: 'flex-start',
}}
>
<div className="flex flex-1 items-start gap-4 overflow-x-auto overflow-y-hidden p-5">
{COLUMNS.map(col => (
<DroppableColumn
key={col}
Expand All @@ -244,7 +210,7 @@ export default function BoardPage({ params }: { params: Promise<{ project: strin

<DragOverlay dropAnimation={{ duration: 150, easing: 'ease' }}>
{activeTask ? (
<div style={{ opacity: 0.85, transform: 'rotate(1.5deg)' }}>
<div className="rotate-[1.5deg] opacity-85">
<TaskCard task={activeTask} />
</div>
) : null}
Expand Down
68 changes: 17 additions & 51 deletions apps/board/app/components/CommentThread.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import { cn } from '../lib/utils';
import type { Comment } from '../lib/api';

interface Props {
Expand Down Expand Up @@ -34,89 +35,54 @@ export function CommentThread({ comments, onAdd }: Props) {
}

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div className="flex flex-col gap-3">
{/* Comment list */}
{comments.length === 0 ? (
<div style={{ color: 'var(--text-muted)', fontSize: 12, fontStyle: 'italic' }}>
<div className="text-[12px] italic text-[var(--text-muted)]">
No comments yet.
</div>
) : (
comments.map((c, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div key={i} className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span
style={{
fontSize: 11,
fontWeight: 600,
color: c.author === 'claude' ? 'var(--accent)' : 'var(--status-in-progress)',
textTransform: 'uppercase',
letterSpacing: '0.04em',
}}
className={cn(
'text-[11px] font-semibold uppercase tracking-[0.04em]',
c.author === 'claude' ? 'text-[var(--accent)]' : 'text-[var(--status-in-progress)]',
)}
>
{c.author === 'claude' ? '✦ Claude' : '● You'}
</span>
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
<span className="text-[11px] text-[var(--text-muted)]">
{formatTime(c.timestamp)}
</span>
</div>
<div
style={{
fontSize: 13,
color: 'var(--text-primary)',
lineHeight: 1.5,
background: 'var(--bg-base)',
borderRadius: 6,
padding: '8px 10px',
border: '1px solid var(--border-subtle)',
whiteSpace: 'pre-wrap',
}}
>
<div className="rounded-[6px] border border-[var(--border-subtle)] bg-[var(--bg-base)] px-2.5 py-2 text-[13px] text-[var(--text-primary)] leading-[1.5] whitespace-pre-wrap">
{c.body}
</div>
</div>
))
)}

{/* Add comment form */}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 4 }}>
<form onSubmit={handleSubmit} className="flex flex-col gap-2 mt-1">
<textarea
value={body}
onChange={e => setBody(e.target.value)}
placeholder="Add a comment…"
rows={3}
style={{
background: 'var(--bg-base)',
border: '1px solid var(--border)',
borderRadius: 6,
color: 'var(--text-primary)',
fontSize: 13,
padding: '8px 10px',
resize: 'vertical',
fontFamily: 'inherit',
outline: 'none',
}}
onFocus={e => { (e.target as HTMLElement).style.borderColor = 'var(--accent)'; }}
onBlur={e => { (e.target as HTMLElement).style.borderColor = 'var(--border)'; }}
className="rounded-[6px] border border-[var(--border)] bg-[var(--bg-base)] px-2.5 py-2 text-[13px] text-[var(--text-primary)] font-[inherit] resize-y outline-none transition-colors duration-100 focus:border-[var(--accent)]"
onKeyDown={e => {
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) handleSubmit(e);
}}
/>
<button
type="submit"
disabled={submitting || !body.trim()}
style={{
alignSelf: 'flex-end',
padding: '6px 16px',
background: 'var(--accent)',
border: 'none',
borderRadius: 6,
color: '#fff',
fontSize: 13,
fontWeight: 500,
cursor: submitting || !body.trim() ? 'not-allowed' : 'pointer',
opacity: submitting || !body.trim() ? 0.5 : 1,
transition: 'opacity 0.1s',
}}
className={cn(
'self-end rounded-[6px] border-none bg-[var(--accent)] px-4 py-1.5 text-[13px] font-medium text-white cursor-pointer transition-opacity duration-100',
submitting || !body.trim() ? 'opacity-50 cursor-not-allowed' : 'opacity-100 hover:opacity-90',
)}
>
{submitting ? 'Posting…' : 'Comment'}
</button>
Expand Down
Loading
Loading