Jamie Lawrence

taskboard

A Kanban board over a folder of Markdown task files. The files are the only store, so git, an editor and a coding agent can all edit them while the board is open.

Private repo

Built with: Rust · axum · vanilla JS · Markdown

Screenshot of taskboard

melee had grown a task list of 1,400 lines and 256 tasks, and everything in it went by an id like “SEC-43”. Those ids told me nothing. I couldn’t see the state of the project, let alone decide what the agents should work on next.

The fix was not a project management tool, it was a board. Each task became one Markdown file in a tasks/ folder with a few key: value header lines: a short title, a topic, a status, a priority, and optionally a question waiting on me. taskboard reads that folder and shows it as columns, one per topic, sorted by priority. Once the tasks had titles and were grouped by topic I could finally see the project.

It was only ten minutes of work, but that’s kind of the point of these silly apps.

How it works

Run taskboard inside any project and it opens a board at 127.0.0.1:4700. There is no database. The files are the only store, and when you drag a card to reorder it only that one file is rewritten, keeping every header line in the order it was written, including keys the board doesn’t know about.

/// Sets `key`, keeping its place in the header, or appends it. `None` removes it.
pub fn set(&mut self, key: &str, value: Option<&str>) {
    let value = value.map(|v| v.replace(['\n', '\r'], " ").trim().to_string());
    match (self.fields.iter().position(|(k, _)| k == key), value) {
        (Some(i), Some(v)) if !v.is_empty() => self.fields[i].1 = v,
        (Some(i), _) => {
            self.fields.remove(i);
        }
        (None, Some(v)) if !v.is_empty() => self.fields.push((key.to_string(), v)),
        (None, _) => {}
    }
}

Tasks waiting on a decision from me are highlighted amber with the question on the card. I answer on the board and the answer is appended to the file under a dated heading, so the reasoning stays with the task.

The agents show up too. A card reads “agent on branch” while a coding agent is working on its branch, which the board discovers by asking git worktree list. Selecting a group of cards and choosing “Make batch” writes a brief for starting an agent on all of them.

Every write carries the version of the file the board last read, so an edit made on disk in the meantime is refused rather than overwritten. The server only listens on localhost and only answers requests addressed to itself.

Rust with axum and one hand-written HTML page, no framework, shipped as a single binary. About 1,500 lines all in, tests included.