commit b79bd2eb1e237aab05ef4aff60eb921ab665973a Author: bp99 Date: Wed Jul 15 17:42:18 2026 +0200 docs: add design sketch diff --git a/docs/design_sketch.adoc b/docs/design_sketch.adoc new file mode 100644 index 0000000..767e155 --- /dev/null +++ b/docs/design_sketch.adoc @@ -0,0 +1,494 @@ += 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.