= lopa: v1 Design Sketch Bertalan Z. Péter :toc: left :toclevels: 2 :sectnums: :sectanchors: :icons: font :source-highlighter: rouge [NOTE] ==== This is a design sketch, not a specification. It exists to pin down v1 scope, data model, file format, and CLI surface before implementation begins. It is expected to evolve during and after v1 use. ==== == 💡 Purpose and Motivation `lopa` is a single-user CLI tool that aims to do *two* things well: task management and day planning. It was built to scratch a specific itch: existing tools (eg todo.txt, TaskWarrior, SuperProductivity) each solve part of this problem but none solve all of it well for the author. .Key motivations, in order . Efficient **CLI**-first interaction (TaskWarrior does this well) . *Day scheduling:* assigning tasks to concrete time slots on a given day (SuperProductivity does this reasonably well; TaskWarrior does not) . **Plaintext**-first storage, so tasks can be viewed and edited on any device with a text editor, without a dedicated client (neither motivating tool does this) . *Simple* by default: no urgency calculation, no forced project hierarchy . *Nothing is deleted:* tasks are permanent records; if you want to get a task out of your view, close it with the appropriate reason [NOTE] ==== It should be emphasized that besides pure task management, _day scheduling_ is also a key feature of `lopa`. Doing this type of scheduling well was a strong factor when I decided to develop _yet another_ task manager. In my view, none of the existing perform adequately in this regard (SuperProductivity comes closest). ==== == 🚧 Scope .In scope for v1 * Fundamentals ** Create and edit tasks ** Close tasks (as done or with a different reason: ‘wontfix’, invalid, duplicate) ** Reopen closed tasks * Metadata ** Tag tasks (tags subsume the notion of ‘project’, at least for now) ** Add freeform notes to tasks * Scheduling ** Create and edit day plans: ordered lists of tasks planned for a specific date with optional start times and durations ** Reorder tasks manually within day plans and the backlog * Views ** Today’s plan (default) ** Other day’s plans ** Backlog ** Filtered task lists (including closed task archive) .Out of scope for v1 * Bulk maintenance operations (including task deletion; to deal with the ever-growing task list) * Priorities * Subtasks * Blocking relationships between tasks * Recurring tasks * Undo (git on the task directory serves this role) * Sync protocol (external file-sync tools handle this) * Long-running daemon * REPL, TUI, alternative frontends * Urgency calculation * Other date fields on tasks (e.g. due dates). Distinct from day plan scheduling: day plans express intent to work on a specific date; a due date would express a deadline * Notifications and reminders * Natural-language date parsing * Time tracking (start/stop timers) * Calendar integration * Import from other tools * Plugins .Explicit non-goals * *Not* a multiuser system * *Not* a networked system * *Not* a GUI-focused application. The primary interface is the CLI. Additional frontends (potentially including a first-party GUI) may consume the same core library in future versions. * *Not* a collaboration tool == 🧠 Design Principles . *Plaintext first.* The task database must be readable and editable with any text editor on any device. The tool is a convenience layer, not a gatekeeper. Anybody must be able to develop their own alternative tooling over the same task database. . *Tasks are permanent records.* `lopa` does not delete tasks. A task created has either an `open` state or some type of _closed_ state (`done`, `wontfix`, `invalid`, `duplicate` in v1). . *Tasks and plans are separate artefacts.* A task describes work that exists; a day plan describes intent for a specific date. . *One thing per file.* Editing one task or one plan must not require rewriting the whole database. This also makes file-level sync tools (Syncthing, git, rsync) work correctly out of the box. . *Boring, standard formats.* Prefer widely-parsed formats over custom syntax. Others should be able to write their own processors without implementing our parser. . *Small, composable commands.* Each subcommand does one thing. Complex operations compose from simple ones. . *Core logic as a library.* The CLI is one frontend among possible others. Task and plan operations live in a library package; the CLI is a thin shell over it. The library is designed to serve the CLI’s actual needs – abstractions are not added in anticipation of hypothetical future consumers. . *Defer complexity.* Features earn their place through real use, not through anticipation. . *Design for the daemon we might build, but do not build it.* Keep business logic separable from CLI plumbing so a future daemon or alternate frontend does not require a rewrite. == 💽 Data Model We have two artefact types: *tasks* and *day plans.* Tasks describe work to be done and day plans describe your intent to do certain tasks on a specific date. Day plans reference tasks by their IDs. === The task A task has the following fields in v1: [cols="2,1,1,4",options="header"] |=== | Field | Type | Required | Notes | `id` | string | yes | Short generated identifier, 5-character base32. Short enough to type, long enough to avoid collisions at expected scale. | `title` | string | yes | One-line summary of the task. | `status` | enum | yes | One of `open`, `done`, `wontfix`, `invalid`, or `duplicate`. Defaults to `open` on creation and `done` when closed without any specific reason. | `created` | datetime (ISO 8601) | yes | Auto-set on creation. | `tags` | list of strings | no | Free-form labels. Also used to model what other tools call ‘projects.’ | `order` | integer | no | Manual ordering within the backlog. Higher values sort higher. | `notes` | string (multiline) | no | Freeform text attached to the task. Stored as a multiline TOML string. |=== === The day plan A day plan has the following fields in v1: [cols="2,1,1,4",options="header"] |=== | Field | Type | Required | Notes | `date` | date (ISO 8601) | yes | The date this plan covers. Matches the filename; stored in-file for self-containment. | `entries` | array of tables | no | Ordered list of planned entries. Empty array is valid (a plan can exist without content). Ordering is by array position in the file; reordering means editing the array. |=== Each entry within `entries` has: [cols="2,1,1,4",options="header"] |=== | Field | Type | Required | Notes | `task` | string | yes | ID of a task file. | `start` | time (HH:MM) | no | Start time within the plan’s date. Absence means ‘on the list, no specific slot.’ | `duration` | integer (minutes) | no | Expected duration. Required when `start` is set; ignored when `start` is absent. |=== == 🤯 Design Notes === Tasks vs Day Plans In `lopa`, tasks are timeless artefacts. Day plans form a mechanism to schedule a task to a specific time. .Consequences * Initially, a special ‘today’ feature was considered that would be used at the start of a day to add a set of tasks that are planned for today. However, since more long-term scheduling was also desired, this would mean managing two schedules in parallel. It could be done but would be convoluted. Instead, the simple, sane, and logical choice was to not treat ‘today’ differently than other dates. Tasks for ‘today’ are those that are scheduled for today’s date in a day plan. * Historical plans are preserved; for example, `plans/2026-03-15.toml` is a record of what the user intended to do on that day and remains readable indefinitely. === Task Lifecycle It is a rather unorthodox choice, but in `lopa`, tasks live forever and are never meant to be deleted. If deletion were possible, we would have to think about what to do when a task is deleted that was referenced in a day plan but that becomes a non-issue. The state change of a task never implies any change to the day plans. For example, even if a task is marked `done`, it remains in the day plans it was added to. In practice, the default `lopa view` and `lopa ls` output hides closed tasks; passing `--all` shows them. [NOTE] ==== Unlike tasks, day plans *can* be deleted. These plans represent intent and can be reasonably discarded. Of course, deleting a plan does not affect the tasks that were referenced in that plan in any way. ==== === IDs * Generated at task creation. * 5-character base32 (alphabet: `abcdefghijkmnpqrstuvwxyz23456789`, omitting visually ambiguous characters such as `l` and `o`). * Case-insensitive on input, canonicalized to lowercase on write. * If collision, retry. [NOTE] ==== Because tasks are never deleted, the ID space accumulates over time. For power users, this _might_ become an issue after prolonged use. The future plan to handle this problem is a supported mechanism for wiping the ‘archive,’ possibly after an export. ==== == 🗄️ File Format === One file per artefact Each task lives in its own file named `.toml`. Each day plan lives in its own file named `.toml`. .Rationale * Editing one task or plan rewrites only that file. Sync tools handle per-file merges cleanly. Concurrent edits on different devices only conflict when the same file is edited on both. * `git diff` on the data directory shows exactly what changed. * `grep`, `find`, etc work across the directory without any custom tooling. * On a phone with a text editor, opening one task means opening one file, not scrolling through thousands of lines. * This pattern is well-precedented (Maildir for email, Jekyll/Hugo for static sites, Obsidian for notes). === File structure Each task file is a valid TOML document. .Example task file (eg `4a2fk.toml`) [source,toml] ---- id = "4a2fk" title = "Write project spec" status = "open" created = 2026-07-02T09:15:00Z tags = ["work", "writing"] order = 100 notes = """ Freeform notes go here. Multiple paragraphs are fine. Markdown, code blocks, whatever the user wants. The tool treats this string as opaque. """ ---- Each day plan file is also a valid TOML document. Entries are represented as an array of tables. .Example day plan file (eg `2026-07-13.toml`) [source,toml] ---- date = 2026-07-13 [[entries]] task = "4a2fk" start = "11:00" duration = 90 [[entries]] task = "7b3cm" start = "13:00" duration = 10 [[entries]] task = "9k4mp" # no start time – planned for the day but no fixed slot ---- The order of entries in the file is the display order. Reordering means moving the `[[entries]]` blocks up or down in the file. .Rationale for TOML over alternatives * *JSON*: 👎 hostile to hand-editing (no comments, quoted keys, trailing-comma paranoia). * *YAML*: 👎 whitespace-significant editing on a phone is error-prone. * *TOML*: 👍 unambiguous, comments allowed, native date/time types, wide parser support in every serious language. * *Custom format*: would require documenting and maintaining a parser, and would raise the barrier for third-party tooling. == 📁 Directory Layout .Default layout under XDG base directories ---- $XDG_DATA_HOME/lopa/ (default: ~/.local/share/lopa/) tasks/ 4a2fk.toml 7b3cm.toml ... plans/ 2026-07-13.toml 2026-07-14.toml ... archive/ 9k4mp.toml ... $XDG_CONFIG_HOME/lopa/ (default: ~/.config/lopa/) config.toml ---- .Design decisions * Three flat directories: `tasks/` for open tasks, `plans/` for day plans, `archive/` for closed tasks. * Plan files are named by the date they cover (`YYYY-MM-DD.toml`). * Tasks whose `status` is not `open` (i.e. `done`, `wontfix`, `invalid`, or `duplicate`) move to `archive/` via rename. Rationale: keeps the active `tasks/` directory focused on live work, cleaner git diffs for daily activity. * Reopening a closed task moves it back to `tasks/`. * Plan files are never archived – they accumulate as a permanent journal of intent. * No index file. The directory listings are the index. A cache can be added later without changing the on-disk format. == ⌨️ CLI Surface === Command shape Subcommand style. Every mutating command takes a task ID. .Command surface for v1 [cols="2,3",options="header"] |=== | Command | Effect | `lopa` | Default view: today’s scheduled tasks. | `lopa add "title" [flags]` | Create a task. Flags: `--tag`, `--plan `, `--note`. | `lopa ls [filters]` | List tasks. Filters: `--today`, `--tag `, `--status `, `--planned`, `--unplanned`, `--planned-on `, `--all` (include closed). Composable. | `lopa show ` | Show full detail of one task, including notes and referencing day plans. | `lopa edit ` | Open the raw task file in `$EDITOR`. Re-parsed and validated on save. | `lopa close [reason]` | Close a task, optionally with a specific reason (`done` is the default). | `lopa reopen ` | Reopen a closed task. | `lopa view [when]` | Show the day plan for `` (an ISO 8601 date) or today by default. Closed tasks are hidden unless `--all` is passed. | `lopa plan [when]` | Add a task to a day plan (today by default). | `lopa unplan [when]` | Remove a task from a day plan (today by default). An `--all` flag requests removal from all day plans. | `lopa edit-plan [when]` | Open the raw plan in `$EDITOR` for today or the specified date. | `lopa mv [--on \|--backlog]` | Reorder a task using anchored positioning. `` is one of: `top`, `bottom`, `before `, `after `. By default, operates on today’s plan. `--on ` targets a specific day’s plan. `--backlog` adjusts the task’s `order` field to fit the specified position. | `lopa tag +foo -bar` | Add or remove tags. |=== === Date arguments In v1, all date arguments to CLI commands must be in ISO 8601 (`YYYY-MM-DD`) format. Human-readable date grammars (`today`, `tomorrow`, `next mon`) are deferred to a later version. Commands that accept a date default to today’s date when the argument is omitted. === Output * Default output is human-readable, formatted for terminal width. * `--json` flag on `ls`, `show`, and `view` produces machine-readable output. This is the first seed of the future protocol/API, but is a byproduct of the CLI, not a separate design effort. === Design notes on the command surface * *Verbs, not `set field=value`.* `lopa close`, `lopa plan`, `lopa tag` express intent more clearly than something like `lopa set 4a2fk status=done`. * *`lopa edit` as escape hatch.* Anything the flags do not cover can be done by editing the file directly. This lowers the bar for the tool itself: it does not need to support every possible mutation as a flag. * *`lopa add` accepts common fields at creation.* Avoids the add-then-edit friction for the common case. == 🛠️ Implementation Notes These are guidance for implementation, not final decisions. * *Language*: Go, provisionally. Rationale: fast to write, good stdlib for filesystem and encoding work, single static binary, easy cross-compilation for future device targets, mature TOML libraries. * *Dependencies*: minimize. Curated libraries allowed where they replace significant plumbing (TOML parsing, CLI arg parsing). Zero deps is not a goal. * *Architecture*: the core lives in a library package (task and plan CRUD, filtering, scheduling logic). The CLI is a thin frontend over this library. This keeps the door open to a first-party GUI, a daemon, a REPL, a TUI, or any other frontend without rewriting core logic. Design the library API to serve the CLI’s needs; do not speculatively add abstractions for consumers that do not yet exist. * *No daemon in v1.* Each invocation opens the data directory, does its work, and exits. == ❓ Open Questions Questions to resolve before or during implementation: * Whether closing a task should also remove its references from *future* plan files. The current proposal is to leave them. * How `--json` output represents null/unset optional fields. * Whether to include a `--dry-run` flag on mutating commands. * How `lopa mv --backlog` computes `order` values when inserting between existing tasks (renumber vs. fractional gaps vs. large-integer gap policy). Implementation detail, but affects long-term stability of manual ordering. == ✅ What v1 "Done" Looks Like .v1 is done when the author can . Add tasks with tags from the CLI. . Create day plans and add tasks to them with or without specific time slots. . Put the same task on multiple day plans. . See the day’s plan and other days’ plans with a single command. . Reorder tasks manually within a plan and within the backlog. . Edit any field of any task, and any plan file, directly in `$EDITOR`. . Close tasks with a reason. . Sync the whole data directory across machines with Syncthing and have it _Just Work™._ . Use the tool daily for at least two weeks without reaching for another tried-and-tested tool. The last item is the real acceptance test.