cat-task-manager
A TUI for managing small, repetitive daily tasks. Keyboard-operated. Written in Rust.
Background
Everyone has their quirks, and so do lifestyle task management apps.
In an age where vibe coding is possible, there’s no longer a need to adapt one’s quirks to a task management app; it’s faster to vibe-code a task management app that fits one’s own habits.
That’s what I thought, so I vibe-coded this. It is not intended for use by others. I will frequently make breaking changes without prior notice. That said, I hope sharing this might provide some hints or inspiration to someone.
Note: Much of the following text was generated by AI and may cause eyes to glaze over; I plan to manually revise it later.
Philosophy
General-purpose TODO apps are convenient. They can do anything: projects, due dates, priorities, tags, search, task dependencies… However, they can also have a high adoption cost or be overkill.
In that case, building your own is a good option. This app abandons generality and focuses on: “Do daily tasks. Do small tasks. Do them in a fixed order. Do them without hesitation.”
Daily tasks don’t stick if their management becomes overly elaborate. What’s needed is immediate visibility of only the single task to be worked on now, recording of start times, moving to the next task upon completion, and minimal user interaction.
Therefore, cat-task-manager does not have features that encourage adding too many tasks. Tasks are not hierarchical. They don’t have parallel dependencies. They don’t have per-task notes or attributes. To maintain the daily flow, it strongly emphasizes only progressing through tasks sequentially.
Tasks and their in-progress status for the current day are solely defined by tasks/*.md. Tasks are not written in config.
It does not maintain a separate status directory.
Prioritizing frequent task maintenance as an ETC principle, the user’s interaction is limited to only the task description Markdown files.
- [ ] Morning Routine
- [ ] Check Emails
- [ ] Code Review
In this format, there’s no need to duplicate tables or field names when adding tasks. The top-to-bottom order directly dictates the execution order; if you want to change the order, just move the lines in a text editor.
Implementation Concept
Task definitions and their current status are consolidated in tasks/*.md.
Regular non-empty lines serve as task definitions, and the status is read as JSON at the end of each task line.
The end-of-line JSON is an area managed by the application.
- [x] Morning Routine {"date":"2026-05-19","state":"done","started_at":"2026-05-19T09:00:00+09:00","completed_at":"2026-05-19T09:15:00+09:00","pauses":[{"paused_at":"2026-05-19T09:05:00+09:00","resumed_at":"2026-05-19T09:10:00+09:00"}]}
- [ ] Check Emails {"date":"2026-05-19","state":"in_progress","started_at":"2026-05-19T09:12:00+09:00"}
- [ ] Code Review {"date":"2026-05-19","state":"not_started"}
This is a compromise to avoid duplicating status into separate files.
Even if you correct a typo in a task name, the status is preserved as long as the end-of-line JSON on that line remains.
If lines are added, deleted, or reordered, the status moves with the same line.
If an in-progress task is put on hold or deferred, paused_at is added to the pauses array in the end-of-line JSON.
When resumed, resumed_at is recorded in the same pause entry. Only the last currently paused entry will not have resumed_at.
If a not-yet-started task is deferred, no pause is added as timing has not begun.
For tasks marked with - [x] but lacking end-of-line JSON, the system normalizes them by treating the detection time as both the start and completion time in the end-of-line JSON.
If the end-of-line JSON format is corrupted, it will be treated as an error upon startup or reload.
Statistics for past data are read from tasks/*.md found in git history.
For the task list in the statistics screen, the guideline is to remove outliers using the IQR method from past completion records for each task, and then display the average of the most frequent band in a 5-minute histogram.
If multiple bands have the same highest frequency, the median is displayed.
Persistent task information is stored solely in tasks/*.md.
The screen is designed to focus on the next task while allowing the user to view the entire list only when necessary.
Upon startup, it defaults to a single-line view. Pressing v toggles between single-line, incomplete, and full views.
The incomplete view excludes completed items, while the full view includes them.
For completed items, the actual work time (start to finish minus all pauses) is displayed. Operations are only available for not-started, in-progress, on-hold, and deferred tasks.
The number of states is kept minimal: not started, in progress, completed, on hold, deferred, and timed out. On hold blocks the next task, while deferred allows progression to the next task without blocking. When a task is on hold in the single-line view of a specific tab, a status explanation is displayed to guide moving to other tabs. When the date changes, unfinished tasks are treated as ‘timed out’ for that day’s record. This is a compromise not to complicate deadlines or scheduling, but simply to easily record that a task ‘wasn’t completed on that day.’
Operations are primarily keybinding-driven. Start and complete use the same key to advance, and hold (to other tabs) and resume also toggle with the same key.
Deferring tasks is also toggled on/off with key operations.
If an in-progress task is automatically put on hold when free time is manually started, it is also recorded as a pause.
tasks/*.md can also be opened with a key operation, and reloaded upon closing.
Entrusting task editing to a familiar external editor is faster and makes it easier to inspect content in case of errors, rather than building forms within the app.
Configuration Structure
config.toml holds settings for editor candidates, keybindings, startup git snapshot, automatic free time start, and external event notifications. It does not contain tasks.
editors = ["fresh", "zed", "nvim", "code"]
[startup_git]
auto_commit_and_push = false
[auto_free_time]
enabled = false
idle_seconds = 60
active_hours = "09:00-17:00"
[external_event]
# interface_file = "C:/path/to/external-task-event.toml"
[keybindings]
j = "next"
down = "next"
k = "previous"
up = "previous"
enter = "advance"
space = "advance"
p = "hold"
d = "defer"
q = "quit"
e = "edit"
l = "next_tab"
right = "next_tab"
h = "previous_tab"
left = "previous_tab"
v = "toggle_view"
s = "stats"
"?" = "help"
Only when startup_git.auto_commit_and_push = true, %LOCALAPPDATA%\cat-task-manager is git committed and pushed once a day upon startup.
Free time, whether manually or automatically started, accumulates only within active_hours and stops at the end of the time slot.
Only when auto_free_time.enabled = true, if there are no in-progress tasks within active_hours for idle_seconds, free time automatically starts.
The end time is not included in the time slot. Cross-day periods like 22:00-02:00 can also be specified.
If external_event.interface_file is specified, key-driven task start/completion events are notified to a TOML file.
Notifications are atomically replaced after tasks/*.md is successfully saved, keeping only the latest one.
This TOML is a transient event for external applications and is not the SSoT (Single Source of Truth) for task status.
Storage location is consolidated under Windows AppData Local.
%LOCALAPPDATA%\cat-task-manager\config.toml
%LOCALAPPDATA%\cat-task-manager\tasks\tasks.md
tasks/*.md is the SSoT that both the user and the app read and write. config.toml defines the operating environment settings.
Design Choices
This app is designed not as a ‘place to manage tasks,’ but as a ‘place to advance through a fixed daily routine.’
For flexible TODO management, search, tagging, and deadline management, other tools should be used. cat-task-manager starts the same way every day, progresses from top to bottom, and records unfinished items as the day’s result. Maintaining this simplicity is a top implementation priority.