feat(kanban): Interactive Kanban board with real-time task tracking (#83)
* feat(kanban): add Kanban board with task-manager skill, modal UI, and desk clutter Implement a full Kanban board system for tracking agent tasks: - Add task-manager skill with shared JSON task store for persistence - Render board as a floating modal over the live 3D office (not immersive) - Auto-create tasks from actionable user messages with heuristic filtering - Sync task status through OpenClaw agent lifecycle events - Collapse task details panel by default, expand on card click - Add dynamic desk clutter (papers, folders, etc.) reflecting active task count - Exclude done tasks from desk clutter count - Extract KANBAN_CLUTTER_OFFSET for easy positioning adjustment - Add install flow with progress bar for the task-manager skill - Include unit and e2e test coverage Made-with: Cursor * feat(kanban): production-harden task board with AI-free classification, resilient persistence, and modal UX - Harden shared task store with atomic writes, payload size limits, and server-side enum validation - Add client resilience: request timeouts (AbortController), exponential backoff retries, poll deduplication - Implement optimistic UI with rollback on all card mutations (update, move, archive) - Add modal accessibility: focus trap, Escape to close, aria-modal, keyboard card navigation - Trust OpenClaw agent lifecycle phase=start as task classification signal instead of regex heuristics - Keep regex heuristic only as lightweight filter for direct chat events (conversational noise) - Expand verb recognition with typo tolerance and broader action vocabulary - Create tasks from agent runs even when no chat event is received (external channel support) - Merge dual header bars into single bar; reposition close button outside modal corner - Exclude done tasks from desk clutter count; make clutter position configurable via KANBAN_CLUTTER_OFFSET - Update default furniture layout to match user configuration - Ensure kanban_board furniture persists in local storage across sessions - Add comprehensive test coverage for store, API route, and controller logic Made-with: Cursor --------- Co-authored-by: iamlukethedev <lucas.guilherme@smartwayslfl.com>
This commit is contained in:
@@ -60,6 +60,13 @@ const DEFAULT_JUKEBOX: FurnitureSeed = {
|
||||
facing: 90,
|
||||
};
|
||||
|
||||
const DEFAULT_KANBAN_BOARD: FurnitureSeed = {
|
||||
type: "kanban_board",
|
||||
x: 460,
|
||||
y: -60,
|
||||
facing: 180,
|
||||
};
|
||||
|
||||
const PREVIOUS_SERVER_ROOM_ITEMS_BOTTOM_RIGHT: FurnitureSeed[] = [
|
||||
{ type: "wall", x: 820, y: 540, w: 280, h: WALL_THICKNESS },
|
||||
{ type: "wall", x: 820, y: 540, w: WALL_THICKNESS, h: 70 },
|
||||
@@ -419,9 +426,8 @@ const DEFAULT_FURNITURE: FurnitureSeed[] = [
|
||||
{ type: "chair", x: 120, y: 480, facing: 180 },
|
||||
{ type: "chair", x: 50, y: 150, facing: 105 },
|
||||
{ type: "chair", x: 60, y: 80, facing: 60 },
|
||||
{ type: "executive_desk", x: 420, y: 60, w: 130, h: 65 },
|
||||
{ type: "chair", x: 540, y: 60, facing: 0 },
|
||||
{ type: "bookshelf", x: 500, y: 30, w: 80, h: 120 },
|
||||
{ type: "chair", x: 550, y: 50, facing: 0 },
|
||||
{ type: "bookshelf", x: 600, y: 30, w: 80, h: 120 },
|
||||
{ type: "couch", x: 270, y: 90, w: 40, h: 80, vertical: true, facing: 180 },
|
||||
{ type: "fridge", x: 1050, y: 20, w: 40, h: 80 },
|
||||
{ type: "stove", x: 920, y: 20 },
|
||||
@@ -433,7 +439,11 @@ const DEFAULT_FURNITURE: FurnitureSeed[] = [
|
||||
{ type: "coffee_machine", x: 880, y: 30, elevation: 0.56 },
|
||||
{ type: "wall_cabinet", x: 960, y: 10, w: 80, h: 20, elevation: 0.9 },
|
||||
{ type: "wall_cabinet", x: 880, y: 10, w: 80, h: 20, elevation: 0.9 },
|
||||
...DEFAULT_DINING_ITEMS,
|
||||
{ type: "round_table", x: 890, y: 100, r: 50 },
|
||||
{ type: "chair", x: 930, y: 100, facing: 0 },
|
||||
{ type: "chair", x: 930, y: 180, facing: 180 },
|
||||
{ type: "chair", x: 880, y: 130, facing: 90 },
|
||||
{ type: "chair", x: 970, y: 130, facing: 270 },
|
||||
{ type: "vending", x: 790, y: 10 },
|
||||
{ type: "trash", x: 210, y: 20 },
|
||||
{ type: "desk_cubicle", x: 100, y: 300, id: "desk_0" },
|
||||
@@ -486,11 +496,12 @@ const DEFAULT_FURNITURE: FurnitureSeed[] = [
|
||||
{ type: "couch", x: 1000, y: 380, w: 100, h: 40, facing: 90 },
|
||||
{ type: "couch", x: 390, y: 630, w: 100, h: 40 },
|
||||
{ type: "table_rect", x: 980, y: 380, w: 60, h: 30, facing: 270 },
|
||||
DEFAULT_PINGPONG_TABLE,
|
||||
{ type: "pingpong", x: 950, y: 600, w: 100, h: 60 },
|
||||
{ type: "beanbag", x: 1000, y: 330, color: "#e65100", facing: 90 },
|
||||
{ type: "beanbag", x: 1000, y: 410, color: "#1565c0", facing: 90 },
|
||||
DEFAULT_ATM_MACHINE,
|
||||
DEFAULT_PHONE_BOOTH,
|
||||
DEFAULT_KANBAN_BOARD,
|
||||
{ type: "whiteboard", x: 40, y: 200, w: 10, h: 60 },
|
||||
{ type: "clock", x: 550, y: 5 },
|
||||
{ type: "lamp", x: 430, y: 100 },
|
||||
@@ -595,6 +606,11 @@ export const ensureOfficeJukebox = (items: FurnitureItem[]): FurnitureItem[] =>
|
||||
return [...items, { ...DEFAULT_JUKEBOX, _uid: nextUid() }];
|
||||
};
|
||||
|
||||
export const ensureOfficeKanbanBoard = (items: FurnitureItem[]): FurnitureItem[] => {
|
||||
if (items.some((item) => item.type === "kanban_board")) return items;
|
||||
return [...items, { ...DEFAULT_KANBAN_BOARD, _uid: nextUid() }];
|
||||
};
|
||||
|
||||
export const ensureOfficePhoneBooth = (
|
||||
items: FurnitureItem[],
|
||||
): FurnitureItem[] => {
|
||||
|
||||
Reference in New Issue
Block a user