From ab5129d7dcdd46306e7dbe06b1befd760ae81ca5 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 6 Apr 2026 20:27:44 -0400 Subject: [PATCH] refactor(rtasks): remove workspace list, single board per space Load kanban board directly on page load instead of showing a workspace picker first. ClickUp connect button moved into board nav. Co-Authored-By: Claude Opus 4.6 --- modules/rtasks/components/folk-tasks-board.ts | 184 ++---------------- 1 file changed, 16 insertions(+), 168 deletions(-) diff --git a/modules/rtasks/components/folk-tasks-board.ts b/modules/rtasks/components/folk-tasks-board.ts index a45688a..3012555 100644 --- a/modules/rtasks/components/folk-tasks-board.ts +++ b/modules/rtasks/components/folk-tasks-board.ts @@ -1,23 +1,18 @@ /** - * โ€” kanban board for workspace task management. + * โ€” kanban board for per-space task management. * - * Views: workspace list โ†’ board with draggable columns. + * Single board per space, loaded directly on page load. * Supports task creation, status changes, and priority labels. */ import { boardSchema, type BoardDoc } from "../schemas"; -import { makeDraggableAll } from "../../../shared/draggable"; -import type { DocumentId } from "../../../shared/local-first/document"; import { TourEngine } from "../../../shared/tour-engine"; -import { ViewHistory } from "../../../shared/view-history.js"; import { startPresenceHeartbeat } from '../../../shared/collab-presence'; class FolkTasksBoard extends HTMLElement { private shadow: ShadowRoot; private space = ""; - private view: "list" | "board" = "list"; private workspaceSlug = ""; - private workspaces: any[] = []; private tasks: any[] = []; private statuses: string[] = ["TODO", "IN_PROGRESS", "REVIEW", "DONE"]; private loading = false; @@ -32,7 +27,6 @@ class FolkTasksBoard extends HTMLElement { private dragSourceStatus: string | null = null; private priorities = ["LOW", "MEDIUM", "HIGH", "URGENT"]; private _offlineUnsubs: (() => void)[] = []; - private _history = new ViewHistory<"list" | "board">("list"); private _backlogTaskId: string | null = null; private _stopPresence: (() => void) | null = null; // Detail panel @@ -45,8 +39,6 @@ class FolkTasksBoard extends HTMLElement { private _showColumnEditor = false; // Board labels (from server) private _boardLabels: string[] = []; - // Inline workspace creation - private _showCreateWs = false; // Inline delete confirmation private _confirmDeleteId: string | null = null; // ClickUp integration state @@ -65,7 +57,6 @@ class FolkTasksBoard extends HTMLElement { private _cuImportResult: { boardId: string; taskCount: number } | null = null; private _tour!: TourEngine; private static readonly TOUR_STEPS = [ - { target: '.workspace-card', title: "Workspaces", message: "Select a workspace to open its kanban board.", advanceOnClick: true }, { target: '#create-task', title: "New Task", message: "Create a new task with title, priority, and description.", advanceOnClick: false }, { target: '.board', title: "Kanban Board", message: "Drag tasks between columns โ€” TODO, In Progress, Review, Done.", advanceOnClick: false }, { target: '.badge.clickable', title: "Priority", message: "Click the priority badge on a task to cycle through levels.", advanceOnClick: false }, @@ -84,13 +75,14 @@ class FolkTasksBoard extends HTMLElement { connectedCallback() { this.space = this.getAttribute("space") || "demo"; + this.workspaceSlug = this.space; // Check for ?backlog= deep-link from email checklist const params = new URLSearchParams(window.location.search); this._backlogTaskId = params.get("backlog"); if (this.space === "demo") { this.loadDemoData(); } else { this.subscribeOffline(); - this.loadWorkspaces(); + this.loadTasks(); this.loadClickUpStatus(); this.render(); } @@ -137,19 +129,12 @@ class FolkTasksBoard extends HTMLElement { try { const docs = await runtime.subscribeModule('tasks', 'boards', boardSchema); - // Build workspace list from cached boards - if (docs.size > 0 && this.workspaces.length === 0) { - const boards: any[] = []; - for (const [docId, doc] of docs) { - const d = doc as BoardDoc; - if (!d?.board) continue; - boards.push({ slug: d.board.slug, name: d.board.name, icon: null, task_count: Object.keys(d.tasks || {}).length }); + // Subscribe to changes for the current board + if (docs.size > 0) { + for (const [docId] of docs) { this._offlineUnsubs.push(runtime.onChange(docId, () => this.refreshFromDocs())); } - if (boards.length > 0) { - this.workspaces = boards; - this.render(); - } + this.refreshFromDocs(); } } catch { /* runtime unavailable */ } } @@ -172,8 +157,6 @@ class FolkTasksBoard extends HTMLElement { private loadDemoData() { this.isDemo = true; - this.workspaces = [{ slug: "rspace-dev", name: "rSpace Development", icon: "\u{1F680}", task_count: 11, member_count: 2 }]; - this.view = "board"; this.workspaceSlug = "rspace-dev"; this.tasks = [ { id: "d1", title: "Add dark mode toggle to settings page", status: "TODO", priority: "MEDIUM", labels: ["feature"], assignee: "Alice", sort_order: 0 }, @@ -204,19 +187,6 @@ class FolkTasksBoard extends HTMLElement { return h; } - private async loadWorkspaces() { - try { - const base = this.getApiBase(); - let res = await fetch(`${base}/api/spaces`, { headers: this.authHeaders() }); - // If 401 with stale token, retry without auth - if (res.status === 401 && localStorage.getItem("encryptid-token")) { - res = await fetch(`${base}/api/spaces`); - } - if (res.ok) this.workspaces = await res.json(); - } catch { this.workspaces = []; } - this.render(); - } - private async loadTasks() { if (!this.workspaceSlug) return; try { @@ -327,7 +297,7 @@ class FolkTasksBoard extends HTMLElement { this.error = 'Import failed'; this._cuStep = 'list'; } - this.loadWorkspaces(); + this.loadTasks(); this.render(); } @@ -443,33 +413,6 @@ class FolkTasksBoard extends HTMLElement { return ''; } - private async createWorkspace(name: string) { - if (!name?.trim()) return; - if (this.isDemo) { - const slug = name.trim().toLowerCase().replace(/\s+/g, "-"); - this.workspaces.push({ slug, name: name.trim(), icon: "\u{1F4CB}", task_count: 0, member_count: 1 }); - this._showCreateWs = false; - this.render(); - return; - } - try { - const base = this.getApiBase(); - const res = await fetch(`${base}/api/spaces`, { - method: "POST", - headers: this.authHeaders({ "Content-Type": "application/json" }), - body: JSON.stringify({ name: name.trim() }), - }); - if (res.ok) { - this._showCreateWs = false; - this.loadWorkspaces(); - } else { - const data = await res.json().catch(() => ({})); - this.error = data.error || `Failed to create workspace (${res.status})`; - this.render(); - } - } catch { this.error = "Failed to create workspace"; this.render(); } - } - private async submitCreateTask(title: string, priority: string, description: string, dueDate?: string) { if (!title.trim()) return; if (this.isDemo) { @@ -686,20 +629,6 @@ class FolkTasksBoard extends HTMLElement { return { sortOrder: ordinal, rebalanceUpdates }; } - private openBoard(slug: string) { - this.workspaceSlug = slug; - this.view = "board"; - this.loadTasks(); - } - - private goBack() { - const prev = this._history.back(); - if (!prev) return; - this.view = prev.view; - if (prev.view === "list") this.loadWorkspaces(); - else this.render(); - } - private render() { this.shadow.innerHTML = `