NAPIREND is a single-page application (SPA) with a vertical board-style layout for managing tasks across groups (sprints). Built with React and Bootstrap.
| Route | Description |
|---|---|
/ |
Main board view - displays all groups and tasks |
Single route. All interactions happen inline (no modals for task creation/editing).
┌────────────────────────────────────────────────────────────────────┐
│ 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
┌────────────────────────────────────────────────────────────────────────┐
│ ┌────────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 🔍 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)
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 usesbtn-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)
All top bars are wrapped in a single sticky container:
┌─────────────────────────────────────────┐
│ Navbar │
│ FilterBar │
│ BulkActionBar │
└─────────────────────────────────────────┘
↑ This entire block sticks to top
- Uses
sticky-topon wrapper div - z-index: 1020
- All three bars stick together as one unit when scrolling
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-fluidprovides full-width responsiveness with paddingmaxWidth: 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 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
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ┌───┐ ┌─────────┐ ┌──────┐ ┌───────┐ ┌───┐ │
│ │ - │ │ 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
┌───────────────────────────────────────────┐
│ + 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
Before:
┌───────────────────────────────────────────┐
│ [Pending] My task │
└───────────────────────────────────────────┘
During edit:
┌───────────────────────────────────────────┐
│ [Pending] [My task__________] │
└───────────────────────────────────────────┘
- Click title to edit
- Enter saves, Escape cancels
- Auto-select all text
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
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
┌───────────────────────────────────────────┐
│ Delete Task [×] │
├───────────────────────────────────────────┤
│ │
│ Are you sure you want to delete │
│ "Task title here"? │
│ │
│ This action cannot be undone. │
│ │
├───────────────────────────────────────────┤
│ [Cancel] [Delete] │
└───────────────────────────────────────────┘
- Message supports newlines (
\n) via CSSwhite-space: pre-line - Escape key or click outside to cancel
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)
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 Group (0 tasks) [🗑️] │
├────────────────────────────────────────────────────────────────────┤
│ + Add task... │
└────────────────────────────────────────────────────────────────────┘
When no groups exist:
No groups yet. Click "Add Group" to create your first group.
┌───────────────────────────────────────────┐
│ │
│ Loading... │
│ [Spinner] │
│ │
└───────────────────────────────────────────┘
Shown when initial data load fails:
┌───────────────────────────────────────────┐
│ │
│ Failed to load data │
│ │
│ [Retry] │
│ │
└───────────────────────────────────────────┘
- Retry button reloads data from API
- Error toast also shown with details
┌───────────────────────────────────────────┐
│ ⚠ Error: Could not save task [×] │
└───────────────────────────────────────────┘
- Top-right corner, fixed position (
z-index: 1100to 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)
| 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 |
| 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) |
| 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.
| 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 |
Implemented using @atlaskit/pragmatic-drag-and-drop.
| 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
| Item | Drag Handle | Drop Target |
|---|---|---|
| Tasks | Entire task row | Any group's task list |
| Groups | Group header | Board (reorder among groups) |
- 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-edgefor precise positioning - Use
@atlaskit/pragmatic-drag-and-drop-react-drop-indicatorfor visual drop lines
- Tasks can be reordered within a group or moved to another group
- Groups can be reordered (drag up/down)
- Cannot drop on self
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.
| 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).
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 |
Principle: Use default Bootstrap components and classes. Avoid custom CSS.
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.
| 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 |
// 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">// 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>- 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)
For dropdowns and modals, detect clicks outside the element:
- Use
mousedownevent (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)
When modal is open:
- Set
document.body.style.overflow = 'hidden' - Restore to original value on close/unmount
- Prevents background scrolling while modal is visible
Three ways to close modals:
- Escape key (via
keydownevent listener) - Click backdrop (via
onClickon backdrop element) - Click outside modal content (check
e.target === e.currentTarget)
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 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 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 assignmentname: Display name (unique, case-insensitive)color: Hex background color (e.g.,#0d6efd)textColor: Hex text color (e.g.,#ffffff)