Skip to content

feat: item tags, tag view, and an editable detail view - #45

Merged
jwarykowski merged 6 commits into
masterfrom
feat/item-tags
Jul 29, 2026
Merged

feat: item tags, tag view, and an editable detail view#45
jwarykowski merged 6 commits into
masterfrom
feat/item-tags

Conversation

@jwarykowski

@jwarykowski jwarykowski commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Items gain a free-form tag set, the board gains a tag view and a second flush-right label, and the detail view becomes a place you can actually edit from. Includes a repo-wide simplification pass and the 0.19.0 bump.

Rebased onto master now that #44 has merged.

tags

  • Free-form lowercase tags alongside the single category: many per item, no ordering effect.
  • Round-trip as one tags: api, docs meta line; #tag adds, tags:a,b replaces, a bare key clears — the same grammar add/sub/edit already use.
  • Present in list --json, matched by --filter and the board's /, described in shepherd schema.
  • T sets them from the list or the detail view.
  • Fixed a latent aliasing bug: todo.Clone shared the Tags backing array with undo snapshots.

board

  • v cycles a tag view: grouped by an item's first tag (untagged last), the rest shown on the row. Repeating an item under each of its tags would break the 1:1 row↔item index that the cursor, mutations and undo all rely on.
  • Rows carry a second flush-right label — whichever grouping axis the headers don't already name: priority in the category and tag views, category in the priority view.
  • Adding with a inherits the selected item's category, so a new item joins the group you were looking at. Precedence: inline @category, then the selected row, then a category filter, then uncategorised.

detail view

  • Now an editor: u/h/m/l/g/T/t/s/L/tab delegate to the list handlers (which already guard parent-only fields and re-place the cursor) and return here, not to the list.
  • Its footer is the list footer's labelled key grid, narrowed to one item: fields · dates · item · go.
  • Field editors render over the detail view instead of the board — View dispatched on mode, so every editor mode fell through to listView and the item you were editing left the screen.

simplification pass (-196 lines, no behaviour change)

  • store.ScanKV/ScanKVFile/ParseList replace four hand-rolled key = value scanners (config.toml, boards.toml, the embedded manifest).
  • cli.locked wraps the six mutating verbs' shared load→mutate→save lock dance; extractFlag replaces three near-identical flag strippers.
  • tui.prompt opens the shared text input, tui.spread lays out flush-right labels, keyGrid backs both footers, one liveOnlyKeys guard replaces six in the board picker.
  • Charts use ansi.Truncate, lipgloss width padding and sort.Slice instead of hand-rolled clip/pad/selection sort.
  • Drops the unused store.TodoPath and todo.SetSubStatus.

docs

README, AGENTS.md and the bundled skill cover tags, the tag view, the two right-hand labels and the detail editor. Also corrected the note-placeholder key (n, not e), the --board usage line, and the stale "one flush-right value" help text.

Items carry a free-form lowercase tag set alongside their single category.
Tags round-trip as one `tags: api, docs` meta line, parse from `#tag` (adds)
and `tags:a,b` (replaces, bare key clears) in add/sub/edit, appear in
`list --json`, and are matched by `--filter` and the board's `/` filter.

Board changes:
- `v` gains a tag view, grouping by an item's first tag (untagged last) and
  showing the rest on the row.
- Rows carry a second flush-right label: whichever grouping axis the headers
  don't already name — priority in the category and tag views, category in the
  priority view.
- `T` sets tags from the list or the detail view.
- The detail view is now an editor: u/h/m/l/g/T/t/s/L/tab delegate to the list
  handlers and return to the detail view.

Docs and help synced with the above; also fixes the note placeholder and detail
footer key hints (`n`, not `e`), the `--board` usage line, and the stale
"one flush-right value" help text.

Includes a repo-wide simplification pass (-196 lines, no behaviour change):
- store.ScanKV/ScanKVFile/ParseList replace four hand-rolled `key = value`
  scanners (config.toml, boards.toml, the embedded manifest).
- cli.locked wraps the six mutating verbs' shared load→mutate→save lock dance;
  extractFlag replaces three near-identical flag strippers.
- tui.prompt opens the shared text input; tui.spread lays out flush-right
  labels; one liveOnlyKeys guard replaces six in the board picker.
- charts use ansi.Truncate, lipgloss width padding and sort.Slice instead of
  hand-rolled clip/pad/selection sort.
- Drops the unused store.TodoPath and todo.SetSubStatus.
The detail view's three crammed hint lines become four labelled columns —
fields / dates / item / go — matching the board footer.

Pulls the layout out of helpGrid as keyGrid(cols, dim) plus a keyCol type, so
both footers share the width spreading, key-column padding and faint-when-inert
rendering; helpGrid and the new detailGrid are just their column lists and dim
rules. Inert keys dim as they do in the list: all but o/esc/q in the read-only
global view, and g/T on a subtask. Note mode keeps its single-line hint.
Adding with `a` while the cursor sits on a categorised row gave the new item no
category, dropping it into the uncategorised tail — you then had to press g and
retype the category you were looking at.

addCategory sits beside filterCategory and falls back to it, so precedence is:
an inline @category, then the selected row's category (a subtask row contributes
its parent's), then a category filter's, then uncategorised. No selected row —
an empty board, or a filter hiding every row — falls straight through to the
filter rule, which the existing non-category-filter case relies on.
Pressing u/g/T/t/s/L from the detail view opened the right editor but drew it on
the board: View dispatches on mode, and modeEdit/modeCategory/modeTags/modeDue/
modeDefer/modeLink all fell through to listView, so the item you were editing
left the screen. m.fieldEdit — already set when an editor opens from the detail
view — now selects detailView too, and the detail footer swaps its key grid for
the prompt. The prompt line is one inputPrompt() shared with listFooter, so an
editor reads the same wherever it was opened from.

Also clears fieldEdit off the immediate mutations (h/m/l, tab). They delegate to
updateList, which leaves the mode alone — it is already modeDetail — so the old
`nm.mode == modeList` check never fired and the flag stayed set. Harmless while
nothing read it; now it would pin a stale prompt in the footer. The check is
`!nm.input.Focused()`: whether an editor actually opened, not a mode guess.
The footer grid shows both as live in the read-only aggregate, but updateGlobal
had no case for either: A only worked one way (README says it toggles in and out,
and esc was the sole way back), and F, a pure display preference, did nothing.

Everything else in that grid already matches the handler — mutations are absent
by structure, not per-case guards — so this makes the footer honest rather than
dimming two keys that ought to work.
@jwarykowski
jwarykowski changed the base branch from fix/default-board-roundtrip to master July 29, 2026 12:01
@jwarykowski
jwarykowski merged commit dd3939c into master Jul 29, 2026
1 check passed
@jwarykowski
jwarykowski deleted the feat/item-tags branch July 29, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant