Skip to content

Latest commit

 

History

History
840 lines (647 loc) · 39.4 KB

File metadata and controls

840 lines (647 loc) · 39.4 KB

NAPIREND - UI Documentation

Overview

NAPIREND is a single-page application (SPA) with a vertical board-style layout for managing tasks across groups (sprints). Built with React and Bootstrap.


URL Routes

Route Description
/ Main board view - displays all groups and tasks

Single route. All interactions happen inline (no modals for task creation/editing).


Screen Layouts

Main Board View (Vertical Layout)

┌────────────────────────────────────────────────────────────────────┐
│  NAVBAR                                                            │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  NAPIREND                 [Manage Projects...] [+ Add Group] │  │
│  └──────────────────────────────────────────────────────────────┘  │
├────────────────────────────────────────────────────────────────────┤
│  FILTER BAR (sticky)                                               │
│  ┌────────────┐ ┌────────┐ ┌─────────┐                             │
│  │ 🔍 Search  │ │Status ▼│ │Project ▼│          [Clear] 12 tasks   │
│  └────────────┘ └────────┘ └─────────┘                             │
├────────────────────────────────────────────────────────────────────┤
│  BOARD (vertical scroll, filtered tasks)                           │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  ▼ Backlog (2 tasks)                                   [🗑️]  │  │
│  ├──────────────────────────────────────────────────────────────┤  │
│  │  [Pending] Task one                                          │  │
│  │  [Pending] Task two                                          │  │
│  │  + Add task...                                               │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  ▼ Sprint 1 (2 tasks)                                  [🗑️]  │  │
│  ├──────────────────────────────────────────────────────────────┤  │
│  │  [In Progress] Task three                                    │  │
│  │  [Pending] Task four                                         │  │
│  │  + Add task...                                               │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  ▼ Done (2 tasks)                                      [🗑️]  │  │
│  ├──────────────────────────────────────────────────────────────┤  │
│  │  [Completed] Task five                                       │  │
│  │  [Completed] Task six                                        │  │
│  │  + Add task...                                               │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘
  • Groups are full-width sections stacked vertically
  • Each group can be collapsed (▼/▶ toggle)
  • Tasks are single-line rows within each group
  • Vertical scrolling for the entire page
  • Filter bar is sticky below navbar

Filter Bar

┌────────────────────────────────────────────────────────────────────────┐
│  ┌────────────────┐ ┌──────────┐ ┌──────────┐                          │
│  │ 🔍 Search...   │ │ Status ▼ │ │ Project▼ │   Clear            12/45 │
│  └────────────────┘ └──────────┘ └──────────┘                          │
└────────────────────────────────────────────────────────────────────────┘

Components:

  • Search input: Text search, matches task titles (case-insensitive)
  • Status dropdown: Multi-select (Pending, In Progress, Completed)
  • Project dropdown: Multi-select (Lorem, Ipsum, Dolor, Unset)
  • MCP only checkbox: Toggle to show only MCP-created tasks
  • Clear button: Resets all filters (visible only when filters active)
  • Task count: "Showing X of Y tasks" when filtered, "Y tasks" otherwise

Dropdown Behavior:

  • Multi-select with checkboxes
  • Stays open for multiple selections
  • Click outside to close
  • Button shows count: "Status (2)" when 2 items selected

Filter Logic:

  • Filters combine with AND (task must match ALL active filters)
  • Empty selection = show all (no filtering for that category)
  • Groups with no matching tasks are hidden when filters are active

Sticky Behavior:

  • Part of sticky header container (see below)

Bulk Action Bar

Always visible below Filter Bar. Part of sticky header container.

┌────────────────────────────────────────────────────────────────────────┐
│  [Clear] │ 3 selected │ [Move to ▼] │ [Set Status ▼] │ [Set Project ▼] │ [Delete...] │
└────────────────────────────────────────────────────────────────────────┘
Element Description
Clear Clears selection (disabled when 0 selected)
"[N] selected" Shows selection count
Move to ▼ Dropdown to move tasks to a group
Set Status ▼ Dropdown to change task status
Set Project ▼ Dropdown to change task project
Delete... Opens confirmation dialog

Styling:

  • Background: bg-secondary (opaque gray)
  • Buttons: btn-outline-light (Delete uses btn-outline-warning)
  • Text: text-white

Behavior:

  • All buttons disabled when selectedCount === 0
  • Selection persists after bulk move/status/project actions (user must click Clear)
  • Selection is automatically cleared after bulk delete
  • Only shows failure toasts (no success toasts)

Sticky Header Container

All top bars are wrapped in a single sticky container:

┌─────────────────────────────────────────┐
│  Navbar                                 │
│  FilterBar                              │
│  BulkActionBar                          │
└─────────────────────────────────────────┘
  ↑ This entire block sticks to top
  • Uses sticky-top on wrapper div
  • z-index: 1020
  • All three bars stick together as one unit when scrolling

Boxed Layout

All main content areas use a consistent boxed layout pattern to limit content width on large screens while remaining fluid on smaller screens:

// Pattern used in Navbar, FilterBar, BulkActionBar, and Board
<div className="container-fluid" style={{ maxWidth: 1400 }}>
  {/* content */}
</div>

Key Details:

  • container-fluid provides full-width responsiveness with padding
  • maxWidth: 1400 (pixels) constrains content on wide displays
  • Content is horizontally centered when viewport exceeds 1400px
  • Applied to: Navbar, FilterBar, BulkActionBar, Board

This ensures a comfortable reading width on ultra-wide monitors while maintaining Bootstrap's responsive behavior.


Group Section

┌─────────────────────────────────────────────────────────────────────────────┐
│  GROUP HEADER                                                               │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │  ▼ Group Name (3 tasks) [Select All]  01:15/02:30 (50%)          [🗑️]  │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────────────────┤
│  TASK LIST (droppable)                                             │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  [Pending] Task one                                          │  │
│  ├──────────────────────────────────────────────────────────────┤  │
│  │  [In Progress] Task two                                      │  │
│  ├──────────────────────────────────────────────────────────────┤  │
│  │  [Completed] Task three                               [note] │  │
│  └──────────────────────────────────────────────────────────────┘  │
├────────────────────────────────────────────────────────────────────┤
│  + Add task...                                                     │
└────────────────────────────────────────────────────────────────────┘

Group Header:

  • Collapse/expand toggle (▼/▶)
  • Group name (click to edit inline)
  • Task count
  • Select All button
  • Time display: HH:MM/HH:MM (XX%) (spent/estimated with percentage)
    • Spent: sum of all task elapsed work time
    • Estimated: sum of all task estimatedHours, converted to HH:MM
    • Percentage: only shown when estimated > 0, can exceed 100%
  • Delete button (🗑️) - shows confirmation dialog
  • Drag handle for reordering groups

Task Row

┌─────────────────────────────────────────────────────────────────────────────────┐
│  ┌───┐  ┌─────────┐  ┌──────┐  ┌───────┐ ┌───┐                                  │
│  │ - │  │ Pending │  │      │  │ 00:00 │ │ ⏱ │  Task title here          [note] │
│  └───┘  │ (gray)  │  └──────┘  └───────┘ └───┘                                  │
│         └─────────┘                                                             │
└─────────────────────────────────────────────────────────────────────────────────┘

Single-line task row showing:

  • Project column (click to change, "-" if unset)
  • Status column (full-height colored box with status text, click to change)
  • Estimation input (decimal hours, e.g., "2.5")
  • Timer display (HH:MM format, monospace font)
  • Timer toggle button (⏱ grey to start, ⏱ red when running)
  • Task title (click to edit inline, strikethrough if completed)
  • Notes badge ([note]) - shown only if task has a description
  • MCP badge ([mcp]) - yellow badge shown for MCP-created tasks

MCP Visual Distinction:

  • MCP-created tasks have a yellow background highlight (bg-warning bg-opacity-25)
  • A yellow "mcp" badge appears at the end of the row

Right-click to open Task Details Drawer for notes editing.

Project Column:

Project colors are managed dynamically via the /api/projects CRUD endpoints and the ManageProjectsModal. Example project colors:

Project Background Text
- (unset) #f8f9fa #6c757d
Lorem #0d6efd #ffffff
Ipsum #0dcaf0 #000000
Dolor #dc3545 #ffffff

Click the project column to open dropdown with all project options. Select "-" to unset the project.

Note: The "-" indicator appears for both unset projects (null) and tasks with invalid projects (projects that were removed from the options list are automatically displayed as "-").

Status Column:

Status Color Bootstrap Class
Pending Gray bg-secondary
In Progress Orange bg-warning
Completed Green bg-success, title strikethrough

Click the status column to open dropdown with all status options. The status column spans the full height of the row and displays the status text.

Estimation Column:

Element Description
Input field 50px wide, text input
Format Decimal hours (e.g., 0.5, 2.5, 12.5)

Estimation behavior:

  • Enter value and blur/press Enter to save
  • Press Escape to revert changes
  • Empty input clears estimation (null)
  • Invalid input (non-numeric, negative) reverts to previous value
  • Values stored as decimal hours (e.g., 2.5 = 2 hours 30 minutes)
  • Displayed in group header as spent/estimated with percentage

Timer Column:

Element Display
Timer display HH:MM format (e.g., "00:00", "01:30", "100:00+")
Timer button (stopped) ⏱ (grey, btn-outline-secondary)
Timer button (running) ⏱ (red, btn-danger)

Timer behavior:

  • Click ⏱ to start timer (button turns red ⏱)
  • Click ⏱ to stop timer (button turns grey ⏱)
  • Display updates every 10 seconds when running
  • Time format: HH:MM (hours:minutes)
  • No maximum display limit (e.g., "105:30" for 105 hours)
  • Timer state persists across page refresh (stored on server)
  • Multiple tasks can have running timers simultaneously

Inline Add Task

┌───────────────────────────────────────────┐
│  + Add task...                            │
└───────────────────────────────────────────┘

When focused:
┌───────────────────────────────────────────┐
│  [Type task title here...]                │
└───────────────────────────────────────────┘
  • Placeholder shows "+ Add task..."
  • Click to focus and type
  • Enter to create (default: pending status), input stays focused for adding more
  • Escape to cancel
  • Blur with empty: reset to placeholder

Inline Title Edit

Before:
┌───────────────────────────────────────────┐
│  [Pending] My task                        │
└───────────────────────────────────────────┘

During edit:
┌───────────────────────────────────────────┐
│  [Pending] [My task__________]            │
└───────────────────────────────────────────┘
  • Click title to edit
  • Enter saves, Escape cancels
  • Auto-select all text

Add Group Input (Navbar)

When "Add Group" is clicked, inline input appears in navbar:

┌────────────────────────────────────────────────────────────────────┐
│  NAPIREND                           ┌──────────────────────┐       │
│                                     │ New group name...    │       │
│                                     └──────────────────────┘       │
└────────────────────────────────────────────────────────────────────┘
  • Input auto-focuses
  • Enter creates group, input stays focused for adding more
  • Escape or blur (when empty) dismisses

Inline Group Name Editor

Click group name to edit:

Before:
┌───────────────────────────────────────────┐
│  ▼ Sprint 1 (2 tasks)               [🗑️]  │
└───────────────────────────────────────────┘

During edit:
┌───────────────────────────────────────────┐
│  ▼ [Sprint 1_________] (2 tasks)    [🗑️]  │
└───────────────────────────────────────────┘
  • Enter or click outside saves
  • Escape cancels
  • Auto-selects all text

Confirmation Dialog

┌───────────────────────────────────────────┐
│  Delete Task                          [×] │
├───────────────────────────────────────────┤
│                                           │
│  Are you sure you want to delete          │
│  "Task title here"?                       │
│                                           │
│  This action cannot be undone.            │
│                                           │
├───────────────────────────────────────────┤
│                     [Cancel]    [Delete]  │
└───────────────────────────────────────────┘
  • Message supports newlines (\n) via CSS white-space: pre-line
  • Escape key or click outside to cancel

Task Notes Drawer

Right-side drawer for editing task notes. Opens on right-click of a task row.

View Mode:

┌─────────────────────────────────────┐
│ Notes                          [×]  │
├─────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ Rendered markdown content...    │ │
│ │ - List items                    │ │
│ │ - **Bold** and *italic*         │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘

Edit Mode (click to enter):

┌─────────────────────────────────────┐
│ Notes                          [×]  │
├─────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ Raw markdown text...            │ │
│ │                                 │ │
│ └─────────────────────────────────┘ │
│ 125/2000 chars                      │
└─────────────────────────────────────┘

Content:

  • Header with "Notes" title and close button
  • View mode: Rendered markdown (click to edit)
  • Edit mode: Textarea with character count (blur to save)

Behavior:

  • Click rendered markdown to enter edit mode
  • Blur textarea to save and return to view mode
  • Auto-enters edit mode if notes are empty
  • Supports markdown: bold, italic, lists, links, code, headings, etc.
  • Click backdrop to close
  • Escape key to close
  • Body scroll lock when open
  • Z-index: 1040 (above sticky header, below toasts)
  • Uses Bootstrap Offcanvas CSS classes (React-controlled)

Manage Projects Modal

Modal for managing projects. Accessed via "Manage Projects..." button in Navbar.

Display Mode:

┌───────────────────────────────────────────────────────────┐
│  Manage Projects                                      [×] │
├───────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐  │
│  │ [Lorem] #0dcaf0 / #000000           [Edit] [Delete] │  │
│  ├─────────────────────────────────────────────────────┤  │
│  │ [Ipsum] #6f42c1 / #ffffff           [Edit] [Delete] │  │
│  ├─────────────────────────────────────────────────────┤  │
│  │ + Add Project                                       │  │
│  └─────────────────────────────────────────────────────┘  │
├───────────────────────────────────────────────────────────┤
│                                              [Close]      │
└───────────────────────────────────────────────────────────┘

Edit Mode:

┌─────────────────────────────────────────────────────────────┐
│ [Name input] BG:[🎨 #rrggbb] Text:[🎨 #rrggbb] [Save][Cancel]│
└─────────────────────────────────────────────────────────────┘

Color Picker:

  • Native <input type="color"> for visual selection
  • Text input for hex value entry
  • Bidirectional sync - changing either updates both

Behavior:

  • Projects sorted alphabetically
  • Delete blocked if tasks reference project (error shown)
  • Escape cancels current edit, or closes modal if not editing
  • Enter saves current edit
  • Only one row can be in edit mode at a time

Empty State (Group with no tasks)

┌────────────────────────────────────────────────────────────────────┐
│  ▼ Empty Group (0 tasks)                                    [🗑️]  │
├────────────────────────────────────────────────────────────────────┤
│  + Add task...                                                     │
└────────────────────────────────────────────────────────────────────┘

Empty State (No groups)

When no groups exist:

No groups yet. Click "Add Group" to create your first group.

Loading State

┌───────────────────────────────────────────┐
│                                           │
│               Loading...                  │
│               [Spinner]                   │
│                                           │
└───────────────────────────────────────────┘

Error State

Shown when initial data load fails:

┌───────────────────────────────────────────┐
│                                           │
│         Failed to load data               │
│                                           │
│              [Retry]                      │
│                                           │
└───────────────────────────────────────────┘
  • Retry button reloads data from API
  • Error toast also shown with details

Toast Notifications

┌───────────────────────────────────────────┐
│  ⚠ Error: Could not save task         [×] │
└───────────────────────────────────────────┘
  • Top-right corner, fixed position (z-index: 1100 to appear above modals)
  • Auto-dismisses after 5000ms (5 seconds)
  • Click × to dismiss manually
  • Timer does NOT pause on hover (simple implementation)
  • Toast header shows variant name: "Error" (danger), "Warning" (warning), "Success" (success)

User Interactions

Groups

Action Trigger Result
Create Click "Add Group" in navbar Inline input appears, group created on submit
Rename Click group name Inline edit enabled
Delete (empty) Click 🗑️ button Confirmation shown, deletes on confirm
Delete (non-empty) Click 🗑️ button Error toast: "Cannot delete group with tasks. Move or delete tasks first."
Collapse/Expand Click ▼/▶ icon Toggle task list visibility
Reorder Drag group by header Group moves to new position

Tasks

Action Trigger Result
Create Type in "+ Add task..." input Task created with default status
Edit Title Click task title Inline edit enabled
Change Project Click project column Select from -, Lorem, Ipsum, Dolor
Change Status Click status column Select from Pending, In Progress, Completed
Set Estimation Type in estimation field, blur/Enter Estimation saved as decimal hours
Clear Estimation Clear estimation field, blur/Enter Estimation cleared to null
Start Timer Click ⏱ button Timer starts, button turns red (⏱)
Stop Timer Click ⏱ button Timer stops, elapsed time saved, button turns grey (⏱)
Edit Notes Right-click task row Notes drawer opens, edits saved on blur
Bulk Delete Select tasks → Delete in Bulk Action Bar Confirmation shown, deletes on confirm
Reorder Drag within group Order updates instantly (no animation)
Move Drag to another group Group and order update instantly (no animation)

Filtering

Action Trigger Result
Search Type in search input Tasks filtered by title (case-insensitive)
Filter by status Click Status dropdown, check items Show only tasks with selected statuses
Filter by project Click Project dropdown, check items Show only tasks with selected projects
Filter MCP only Check "MCP only" checkbox Show only tasks created via MCP
Clear filters Click "Clear" button Reset all filters, show all tasks

Note: Groups with no matching tasks are automatically hidden when filters are active.


Keyboard Shortcuts

Shortcut Context Action
Enter Group name input Save
Escape Group name input Cancel
Enter Add group input Create group
Escape Add group input Cancel
Enter Add task input Create task
Escape Add task input Cancel
Enter Task title edit Save
Escape Task title edit Cancel
Enter Estimation input Save and blur
Escape Estimation input Revert and blur

Drag and Drop

Implemented using @atlaskit/pragmatic-drag-and-drop.

Visual Feedback

State Visual
Idle Normal appearance
Dragging Slight opacity reduction (50%)
Over drop zone Drop indicator line shows insertion point
Over task list Task list container shows subtle blue highlight (bg-primary bg-opacity-10)
Drop Instant position update (no animation)

Drag-and-drop is instant with no animations for better concurrent editing safety.

The drop indicator is provided by @atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box package (use DropIndicator component with edge prop).

TaskList Drop Target:

  • Minimum height (44px) ensures it remains a valid drop target even when empty (matches task row height)
  • Shows subtle blue highlight when dragging a task over it

What's Draggable

Item Drag Handle Drop Target
Tasks Entire task row Any group's task list
Groups Group header Board (reorder among groups)

Implementation Details

  • Use draggable() from @atlaskit/pragmatic-drag-and-drop/element/adapter
  • Use dropTargetForElements() for drop zones
  • Use monitorForElements() at Board level to handle all drop events
  • Use @atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge for precise positioning
  • Use @atlaskit/pragmatic-drag-and-drop-react-drop-indicator for visual drop lines

Constraints

  • Tasks can be reordered within a group or moved to another group
  • Groups can be reordered (drag up/down)
  • Cannot drop on self

Decision Logic

The frontend calls the same PUT /api/tasks/reorder endpoint for both reorder and move operations. The backend determines which operation to perform based on whether targetGroupId matches the task's current group. See COMPONENTS.md → "Drag-and-Drop Decision Logic" for details.


Responsive Behavior

Breakpoint Layout
All sizes Vertical stacked sections, full width

Groups are always full-width and vertically stacked. All breakpoints support drag-and-drop (touch on mobile).


Color Scheme

Bootstrap defaults:

Element Class
Navbar bg-dark
Group section bg-light
Task row bg-white (or bg-warning bg-opacity-25 for MCP tasks)
Primary buttons btn-primary
Danger buttons btn-danger
Status: Pending bg-secondary
Status: In Progress bg-warning
Status: Completed bg-success
Project colors Inline styles (hex from server)
Timer: Stopped btn-outline-secondary
Timer: Running btn-danger

Bootstrap 5 Implementation

Principle: Use default Bootstrap components and classes. Avoid custom CSS.

Important: Bootstrap JavaScript NOT Used

This project uses Bootstrap CSS classes but NOT Bootstrap's JavaScript. All interactive components are React-controlled:

Component Instead of Bootstrap JS... Use React State
Modal data-bs-toggle="modal" show prop controlled by state
Offcanvas data-bs-toggle="offcanvas" isOpen prop controlled by state
Dropdown data-bs-toggle="dropdown" showMenu state + click-outside handler
Toast Bootstrap's Toast JS React state + setTimeout
Collapse data-bs-toggle="collapse" isCollapsed state

Rationale: React-controlled components integrate better with React's state model and avoid conflicts between Bootstrap JS and React's virtual DOM.

Recommended Components

UI Element Bootstrap Solution
Navbar <nav class="navbar navbar-dark bg-dark sticky-top">
Group sections <div class="card mb-3"> with card-header, card-body
Task rows <div class="d-flex align-items-center p-2 border-bottom">
Buttons btn btn-primary, btn btn-danger, btn btn-outline-secondary
Delete icon btn btn-sm btn-outline-danger or btn-close
Inputs form-control and form-control-sm
Status dropdown Custom React dropdown (see below)
Confirmation dialog React-controlled modal with Bootstrap CSS
Task details drawer React-controlled offcanvas with Bootstrap CSS
Toasts React-controlled with Bootstrap CSS
Loading spinner <div class="spinner-border" role="status">
Collapse toggle React-controlled with Bootstrap CSS

Layout Utilities

// Flexbox layouts
<div className="d-flex justify-content-between align-items-center">

// Spacing
<div className="p-3 mb-2 mt-3">

// Full width
<div className="w-100">

// Gaps between flex children
<div className="d-flex gap-2">

Form Controls

// Text input
<input type="text" className="form-control" />

// Small input
<input type="text" className="form-control form-control-sm" />

// Button group
<div className="btn-group">
  <button className="btn btn-outline-secondary">Option 1</button>
  <button className="btn btn-outline-secondary">Option 2</button>
</div>

What NOT to Do

  • Don't write custom CSS for spacing (use m-*, p-*, gap-*)
  • Don't write custom CSS for colors (use bg-*, text-*)
  • Don't import Bootstrap's JavaScript bundle
  • Don't use data-bs-* attributes for interactivity
  • Don't use inline styles for layout (use utility classes)

React-Controlled Patterns

Click-Outside Handler

For dropdowns and modals, detect clicks outside the element:

  • Use mousedown event (not click) for better UX
  • Check !element.contains(event.target as Node)
  • Add listener on mount/open, remove on unmount/close
  • Clean up in both unmount AND when closing (prevent memory leaks)

Body Scroll Lock

When modal is open:

  • Set document.body.style.overflow = 'hidden'
  • Restore to original value on close/unmount
  • Prevents background scrolling while modal is visible

Modal Dismiss Patterns

Three ways to close modals:

  1. Escape key (via keydown event listener)
  2. Click backdrop (via onClick on backdrop element)
  3. Click outside modal content (check e.target === e.currentTarget)

Blur-to-Save vs Blur-to-Cancel

InlineEditInput uses blur-to-save pattern:

  • Blur with non-empty changed value → save and close
  • Blur with unchanged value → cancel (close without API call)
  • Blur with empty value → cancel (close without saving)
  • Escape key → cancel and revert to original value

Result: Input always closes on blur. Empty values are never saved to prevent accidental data loss.

This differs from some UIs that cancel on blur.


Status Dropdown Colors

Status colors with proper text contrast:

Status Background Text Color Reason
Pending bg-secondary white (default) Good contrast
In Progress bg-warning text-dark Yellow needs dark text
Completed bg-success white (default) Good contrast

The warning status explicitly uses text-dark for readability.


Project Dropdown Colors

Project colors are managed dynamically via the /api/projects CRUD endpoints and the ManageProjectsModal. Colors are applied via inline styles. The unset project styling is hardcoded in TaskRow.tsx:

const UNSET_PROJECT = { color: '#f8f9fa', textColor: '#6c757d' };

Projects are created and configured through the UI (Navbar → "Manage Projects..." button) or via the REST API. Each project has:

  • id: UUID for task assignment
  • name: Display name (unique, case-insensitive)
  • color: Hex background color (e.g., #0d6efd)
  • textColor: Hex text color (e.g., #ffffff)