8.8 KiB
KBTD - Kanban TODO
A minimal Kanban board web app that uses a Markdown file as its data store.
Overview
KBTD presents a Kanban board interface for managing tasks stored in a standard Markdown TODO file. The Markdown file is the canonical data store—the app provides a visual interface for viewing and manipulating it.
Core Concepts
| Term | Description |
|---|---|
| Project | A working directory containing a TODO.md file (identified by absolute file path) |
| Section | An H1 (# Name) in the TODO file — rendered as a selectable Kanban board |
| Column | An H2 (## Name) under a section — a column within that board |
| Card | A - [ ] or - [x] item — draggable between columns |
Markdown Format
# Project Alpha
## Backlog
- [ ] Research competitors
- [ ] Write RFC
## In Progress
- [x] Design mockups
- [ ] Build prototype
## Done
- [x] Initial brainstorm
# Project Beta
## TODO
- [ ] Something else
Parsing Rules
- H1 headers (
#) define sections (boards) - H2 headers (
##) define columns within the preceding section - [ ]is an incomplete task- [x]is a complete task- Task text is everything after the
]until end of line - Content before the first H1 is ignored
- Content between H1 and first H2 is ignored
- Nested content under tasks (indented lines) is preserved but not displayed
Architecture
Components
- Shell script (
kbtd) - Cross-platform launcher - Single HTML file (
index.html) - The entire web application
Data Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ TODO.md │◄───►│ Web App │◄───►│ localStorage│
│ (file) │ │ (browser) │ │ (UI state) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ File System │ IndexedDB
│ Access API │ (FileHandle)
│ │
└────────────────────┘
State Management
- File state: The TODO.md file is the source of truth for task data
- UI state: localStorage stores current section selection, keyed by file path
- File handle: IndexedDB stores the FileSystemFileHandle for persistence across sessions
localStorage Schema
Key: kbtd:${filePath}
{
"currentSection": "Project Alpha"
}
IndexedDB Schema
Database: kbtd
Object Store: fileHandles
Key: file path from URL query string
Value: FileSystemFileHandle
User Interface
States
- No file associated: Shows file picker button and instructions
- Permission needed: Shows button to re-grant permission (after page reload)
- File not found: Shows error message if TODO.md doesn't exist
- Section picker: Shows list of sections (H1s) to choose from
- Kanban board: Shows columns and cards for selected section
Kanban Board Features
- Columns displayed horizontally
- Cards displayed vertically within columns
- Drag and drop cards between columns
- Click card to toggle complete/incomplete
- Visual distinction for completed tasks (strikethrough or muted)
Navigation
- Back button to return to section picker
- Current section name displayed in header
File Synchronization
Polling
- Poll file every 250ms using
file.lastModified - On change detected: reload file, rebuild state, re-render UI
- If currently selected section no longer exists: return to section picker
Write Strategy
- On any card move or status toggle: serialize full state to Markdown and write
- Use
FileSystemFileHandle.createWritable()for atomic writes
Conflict Resolution
None. Last write wins. External changes blow away app state.
Shell Script (kbtd)
Usage
kbtd [OPTIONS] <path-to-markdown-file>
Options:
--browser Open in existing browser instead of app mode
--help Show help message
Behavior
- Resolve the markdown file path to absolute path
- Locate a Chromium-based browser (Chrome, Chromium, Edge)
- Determine mode:
- Default:
--appmode with isolated--user-data-dirin current directory - With
--browser: open in existing browser instance with--new-window
- Default:
- Launch browser with URL:
{base_url}?file={absolute_path}
Browser Detection
Check in order:
$BROWSERenvironment variable (if Chromium-based)google-chrome/chromium/chromium-browser/microsoft-edgein PATH- macOS:
/Applications/Google Chrome.app/...,/Applications/Chromium.app/...,/Applications/Microsoft Edge.app/... - WSL:
/mnt/c/Program Files/Google/Chrome/Application/chrome.exe, etc.
If no Chromium browser found: exit with error message explaining requirement.
App Mode Launch
# Create isolated user data directory
USER_DATA_DIR="./.kbtd-chrome-data"
# Launch in app mode
google-chrome \
--user-data-dir="$USER_DATA_DIR" \
--app="http://localhost:8000/?file=/absolute/path/to/TODO.md"
Browser Mode Launch
google-chrome --new-window "http://localhost:8000/?file=/absolute/path/to/TODO.md"
URL Configuration
- Development:
http://localhost:8000 - Production: Configurable via
$KBTD_URLenvironment variable or hardcoded default
Web App Behavior
Startup Flow
1. Parse ?file= from URL query string
2. If no file param:
→ Show landing page with instructions and script download link
3. Check IndexedDB for stored FileHandle for this file path
4. If handle exists:
→ Check permission with queryPermission()
→ If "granted": proceed to load file
→ If "prompt": show "Grant Access" button
→ If "denied": show file picker
5. If no handle:
→ Show file picker button
6. On file picker success:
→ Store handle in IndexedDB
→ Load and parse file
→ Show section picker or board
File Picker
const [fileHandle] = await window.showOpenFilePicker({
id: 'kbtd-todo-file',
mode: 'readwrite',
types: [{
description: 'Markdown files',
accept: { 'text/markdown': ['.md'] }
}]
});
Permission Re-grant
After page reload, stored handles return "prompt" for permission state. User must click a button to trigger:
const permission = await fileHandle.requestPermission({ mode: 'readwrite' });
This requires a user gesture (button click).
Browser Compatibility
Required: Chromium-based browser (Chrome 86+, Edge 86+, Opera 72+)
Not supported: Firefox, Safari (File System Access API not implemented)
The shell script should detect and warn if no compatible browser is found.
File Structure
kbtd/
├── index.html # Complete web application (single file)
├── kbtd # Shell script launcher
└── SPEC.md # This specification
Landing Page
When loaded without a ?file= parameter, the app displays:
- App name and brief description
- Instructions for use
- Download link/instructions for the shell script
- Note about Chromium browser requirement
Architecture Principles
Source of Truth
The Markdown file is the only source of truth. The Kanban UI is purely a renderer and editor for the file. There is no separate application state that needs to be "synced" with the file.
Functional & Idempotent Design
- Pure parsing:
parseMarkdown(text) → {sections, columns, cards}— same input always produces same output - Pure serialization:
serializeToMarkdown(data) → text— same input always produces same output - Idempotent renders:
render(data)can be called any number of times with the same data and produce the same DOM - No hidden state: UI state (current section) is minimal and stored separately from task data
- Stateless transformations: operations like
moveCard(data, cardIndex, fromColumn, toColumn) → newDatareturn new data structures rather than mutating
Data Flow
File Change → Read File → Parse → Render
User Action → Transform Data → Serialize → Write File → Read File → Parse → Render
Note: After every write, the app re-reads and re-parses the file. This ensures the file remains the source of truth and the UI always reflects exactly what's on disk.
Non-Goals (v1)
- No task metadata (due dates, tags, assignees)
- No task IDs or UUIDs
- No multi-file support
- No task details/descriptions (subtasks, notes)
- No offline support beyond what the browser provides
- No collaborative editing
- No mobile support
- No server component (pure client-side)
Future Considerations
- Task detail files in
.issues/subdirectory - UUID-based task tracking for stable identity
- Folder access instead of single file
- Multiple TODO files in project