f57baee6bc
IMP-5: Replace strings.Contains(arg, ":") heuristic with an allowlist of recognized attribute keys (ValidAttributeKeys). Colons in task descriptions (URLs, "Meeting: topic") are no longer misinterpreted as modifiers. Canonical key sets live in engine/keys.go and are shared across parseAddArgs, ParseFilter, and ParseModifier. ParseModifier now errors on unknown keys. IMP-4: delete command now loads the working set and resolves display IDs via GetTaskByDisplayID, matching the pattern used by done/modify. IMP-6: All action commands (done, delete, modify, start, stop) now return an error on no-match (stderr, exit 1). Previously done/delete printed to stdout and exited 0; start/stop had no check at all. Also adds requirements and design docs for the CLI UX improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
471 lines
15 KiB
Markdown
471 lines
15 KiB
Markdown
# Opal CLI: User Experience Improvements
|
|
|
|
**Status:** Draft — awaiting feedback
|
|
**Date:** 2026-02-18
|
|
|
|
---
|
|
|
|
## Problem Statement
|
|
|
|
Opal's CLI is functional and expressive, but several gaps in feedback, safety,
|
|
and discoverability make daily use rougher than it needs to be. These
|
|
improvements target the person who uses `opal` 10+ times a day — reducing
|
|
friction, preventing mistakes, and surfacing information at the right moment.
|
|
|
|
---
|
|
|
|
## Proposed Improvements
|
|
|
|
### IMP-1: Undo / Uncomplete
|
|
|
|
**Priority:** MUST
|
|
**Noted in:** `opal-web/BUGS.md` (missing uncomplete feat)
|
|
|
|
#### Problem
|
|
|
|
Accidentally completing or deleting the wrong task has no quick recovery path.
|
|
The only workaround is `opal edit <uuid>` and manually setting status back to
|
|
`pending`. This is slow, error-prone, and requires knowing the UUID.
|
|
|
|
#### User Stories
|
|
|
|
**US-1.1** As a user, I want to uncomplete a task so that I can recover from
|
|
accidental completions.
|
|
|
|
- **Given** task 3 was just completed
|
|
- **When** I run `opal 3 uncomplete` (or `opal undo`)
|
|
- **Then** task 3 returns to `pending` status, its `end` timestamp is cleared,
|
|
and it reappears in my default report
|
|
|
|
**US-1.2** As a user, I want a generic `undo` that reverts my last action so
|
|
that I don't need to know the exact reverse command.
|
|
|
|
- **Given** I just ran `opal 5 delete`
|
|
- **When** I run `opal undo`
|
|
- **Then** task 5 is restored to its previous status
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-1.1: `opal undo` MUST revert the last mutating CLI action (done,
|
|
delete, modify, add, start, stop).
|
|
2. FR-1.2: `opal <id> uncomplete` MUST set a completed task back to pending
|
|
and clear the `end` timestamp.
|
|
3. FR-1.3: Undo history SHOULD persist across CLI invocations (stored in a
|
|
local undo log file or DB table).
|
|
4. FR-1.4: Undo SHOULD support at least the last 10 operations.
|
|
5. FR-1.5: `opal undo` MUST display what was reverted
|
|
(e.g., `Undone: task 3 "Buy milk" restored to pending`).
|
|
|
|
#### Design Decisions
|
|
|
|
- **Scope:** Local only — undo does NOT propagate across sync boundaries.
|
|
- **Undo `add`:** Deletes the created task entirely (hard delete, not soft).
|
|
- **Multi-level:** `opal undo` can be called repeatedly to walk back through
|
|
the last 10 operations (stack-based, LIFO).
|
|
|
|
---
|
|
|
|
### IMP-2: Better Feedback After `add`
|
|
|
|
**Priority:** MUST
|
|
|
|
#### Problem
|
|
|
|
`opal add Buy groceries due:tomorrow +errand` prints:
|
|
|
|
```
|
|
Created task 8f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
|
|
```
|
|
|
|
The UUID is meaningless for subsequent commands. The user can't confirm their
|
|
modifiers were parsed correctly without running a separate `list` or `info`.
|
|
|
|
#### User Stories
|
|
|
|
**US-2.1** As a user, I want to see the display ID and parsed attributes after
|
|
adding a task so that I can confirm it was created correctly and reference it
|
|
immediately.
|
|
|
|
- **Given** I run `opal add Buy groceries due:tomorrow +errand`
|
|
- **When** the task is created
|
|
- **Then** I see output like:
|
|
```
|
|
Created task 3 — "Buy groceries"
|
|
Due: tomorrow (2026-02-19)
|
|
Tags: errand
|
|
Priority: default
|
|
```
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-2.1: `add` MUST display the new task's display ID (not just UUID).
|
|
2. FR-2.2: `add` MUST echo back all parsed modifiers so the user can verify.
|
|
3. FR-2.3: For recurring tasks, `add` MUST show recurrence interval and the
|
|
first instance's due date.
|
|
4. FR-2.4: The display ID shown MUST be valid for immediate use
|
|
(e.g., `opal 3 done`).
|
|
|
|
---
|
|
|
|
### IMP-3: Show Matched Tasks in Confirmations
|
|
|
|
**Priority:** MUST
|
|
|
|
#### Problem
|
|
|
|
Destructive commands (`done`, `delete`, `modify`) prompt with only a count:
|
|
|
|
```
|
|
About to complete 3 tasks. Proceed? (y/N):
|
|
```
|
|
|
|
The user can't verify *which* 3 tasks will be affected without cancelling and
|
|
running a separate `list` command with the same filter.
|
|
|
|
#### User Stories
|
|
|
|
**US-3.1** As a user, I want to see the list of affected tasks before
|
|
confirming a bulk action so that I can verify I'm not making a mistake.
|
|
|
|
- **Given** I run `opal +errand done`
|
|
- **When** 3 tasks match
|
|
- **Then** I see:
|
|
```
|
|
About to complete 3 tasks:
|
|
1 Buy groceries due:tomorrow +errand
|
|
4 Return library books due:fri +errand
|
|
7 Pick up dry cleaning +errand
|
|
Proceed? (y/N):
|
|
```
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-3.1: `done`, `delete`, and `modify` MUST list matched tasks (ID,
|
|
description, key attributes) before the confirmation prompt.
|
|
2. FR-3.2: If more than 10 tasks match, show the first 10 and note
|
|
"...and N more".
|
|
3. FR-3.3: Single-task operations (e.g., `opal 3 done`) SHOULD still show the
|
|
task description for verification but skip the y/N prompt.
|
|
|
|
---
|
|
|
|
### IMP-4: Fix `delete` Not Resolving Display IDs
|
|
|
|
**Priority:** MUST (bug fix)
|
|
|
|
#### Problem
|
|
|
|
`delete` calls `engine.GetTasks(filter)` directly without loading the working
|
|
set to resolve display IDs. This means `opal 3 delete` may not resolve ID 3
|
|
correctly, unlike `done` and `modify` which both load the working set.
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-4.1: `delete` MUST load the working set and resolve display IDs the same
|
|
way `done` and `modify` do.
|
|
2. FR-4.2: All action commands (done, delete, modify, start, stop, info, edit)
|
|
MUST use the same ID resolution path.
|
|
|
|
---
|
|
|
|
### IMP-5: Handle Colons in Descriptions
|
|
|
|
**Priority:** MUST
|
|
|
|
#### Problem
|
|
|
|
`parseAddArgs` treats any argument containing `:` as a modifier. This silently
|
|
drops or misparses descriptions containing colons:
|
|
|
|
```
|
|
opal add "Meeting: discuss Q3 goals" # "Meeting:" parsed as modifier
|
|
opal add Fix bug in http://example.com # "http:" parsed as modifier
|
|
```
|
|
|
|
There's no escaping mechanism or error message — the description is silently
|
|
truncated.
|
|
|
|
#### User Stories
|
|
|
|
**US-5.1** As a user, I want to include colons in task descriptions so that I
|
|
can write natural language without worrying about parser conflicts.
|
|
|
|
- **Given** I run `opal add "Meeting: discuss Q3 goals"`
|
|
- **When** the task is created
|
|
- **Then** the description is `Meeting: discuss Q3 goals` with no modifiers
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-5.1: Quoted strings MUST be treated as description text, not parsed for
|
|
modifiers.
|
|
2. FR-5.2: Only tokens matching a known modifier pattern (`key:value` where
|
|
`key` is a recognized attribute like `due`, `priority`, `project`, `recur`,
|
|
`status`, `wait`, `scheduled`, `until`) SHOULD be treated as modifiers.
|
|
3. FR-5.3: Unknown `key:value` patterns SHOULD be treated as description text,
|
|
not silently dropped.
|
|
4. FR-5.4: If a token is ambiguous, prefer treating it as description text.
|
|
|
|
#### Design Decisions
|
|
|
|
- **Allowlist approach:** Only recognized attribute keys (`due`, `priority`,
|
|
`project`, `recur`, `status`, `wait`, `scheduled`, `until`) are treated as
|
|
modifiers. All other `key:value` tokens are treated as description text.
|
|
|
|
---
|
|
|
|
### IMP-6: Consistent Error on No-Match
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
Action commands behave inconsistently when no tasks match a filter:
|
|
|
|
| Command | No-match behavior | Exit code |
|
|
|----------|-------------------------------------|-----------|
|
|
| `done` | Prints "No tasks matched." | 0 |
|
|
| `delete` | Prints "No tasks matched." | 0 |
|
|
| `modify` | Returns error "no tasks matched" | 1 |
|
|
| `start` | (unknown — needs verification) | ? |
|
|
| `stop` | (unknown — needs verification) | ? |
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-6.1: All action commands MUST return exit code 1 when no tasks match an
|
|
explicit filter.
|
|
2. FR-6.2: All action commands MUST print to stderr (not stdout) when no tasks
|
|
match, to support scripting.
|
|
3. FR-6.3: The message SHOULD be consistent:
|
|
`Error: no tasks matched filter "<filter>"`.
|
|
|
|
---
|
|
|
|
### IMP-7: Recurring Task Feedback
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
Completing a recurring task instance gives no indication about recurrence:
|
|
|
|
```
|
|
$ opal 3 done
|
|
Completed 1 task(s).
|
|
```
|
|
|
|
The user doesn't know if a next instance was spawned, when it's due, or whether
|
|
the recurrence is still active.
|
|
|
|
#### User Stories
|
|
|
|
**US-7.1** As a user, I want to see recurrence information when completing a
|
|
recurring task so that I know the schedule is continuing.
|
|
|
|
- **Given** task 3 is a recurring weekly task
|
|
- **When** I run `opal 3 done`
|
|
- **Then** I see:
|
|
```
|
|
Completed task 3 — "Weekly review"
|
|
Next instance created — due: 2026-02-25 (in 7 days)
|
|
```
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-7.1: Completing a recurring task instance MUST display whether a new
|
|
instance was created and its due date.
|
|
2. FR-7.2: If no new instance was created (e.g., recurrence was cleared), the
|
|
output MUST say so.
|
|
3. FR-7.3: `info` on a recurring instance SHOULD show the recurrence pattern
|
|
and parent template UUID.
|
|
|
|
---
|
|
|
|
### IMP-8: Shell Completions
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
No tab completion exists for commands, report names, project names, or tag
|
|
names. For a CLI with 14 commands, 13 report names, and user-defined projects
|
|
and tags, discoverability is poor.
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-8.1: `opal completion bash|zsh|fish` MUST generate shell completion
|
|
scripts (cobra has built-in support for this).
|
|
2. FR-8.2: Completions SHOULD cover: commands, report names, `+tag` names,
|
|
`project:` values, `priority:` values, and `status:` values.
|
|
3. FR-8.3: Dynamic completions for tags and projects SHOULD query the database.
|
|
4. FR-8.4: Setup instructions SHOULD be printed after `opal completion <shell>`.
|
|
|
|
---
|
|
|
|
### IMP-9: Relative Dates in CLI Reports
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
CLI report tables likely show absolute dates (`2026-02-20`). When scanning a
|
|
task list, relative dates ("in 2d", "yesterday", "3w ago") are faster to parse
|
|
at a glance. The web UI already uses relative dates.
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-9.1: Due dates in reports MUST be shown as relative when within 14 days
|
|
(e.g., "tomorrow", "in 3d", "2d ago").
|
|
2. FR-9.2: Dates beyond 14 days SHOULD fall back to short absolute format
|
|
(e.g., "Feb 28", "Mar 15").
|
|
3. FR-9.3: `info` SHOULD show both absolute and relative
|
|
(e.g., `Due: 2026-02-20 (in 2 days)`).
|
|
4. FR-9.4: Relative display COULD be togglable via config
|
|
(`date_display: relative|absolute`).
|
|
|
|
---
|
|
|
|
### IMP-10: Dry-Run / Preview for Action Commands
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
Before running `opal +errand done`, the user often runs `opal +errand list`
|
|
first to preview. This is a two-step workflow that could be one step.
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-10.1: `done`, `delete`, `modify`, `start`, and `stop` SHOULD support a
|
|
`--dry-run` flag that lists matched tasks without acting.
|
|
2. FR-10.2: Dry-run output MUST match the same format as the confirmation
|
|
listing (IMP-3), followed by "Dry run — no changes made."
|
|
3. FR-10.3: Dry-run MUST exit with code 0 if tasks matched, 1 if none matched.
|
|
|
|
---
|
|
|
|
### IMP-11: Task Annotations
|
|
|
|
**Priority:** SHOULD
|
|
|
|
#### Problem
|
|
|
|
There's no way to attach notes to a task after creation. For long-running tasks
|
|
like "Debug auth issue" or "Research hosting options", users want to record
|
|
progress without cluttering the description.
|
|
|
|
#### User Stories
|
|
|
|
**US-11.1** As a user, I want to annotate tasks with timestamped notes so that
|
|
I can track progress and findings over time.
|
|
|
|
- **Given** task 3 exists
|
|
- **When** I run `opal 3 annotate "Traced to token expiry in middleware"`
|
|
- **Then** the annotation is saved with a timestamp
|
|
- **And** `opal 3 info` shows the annotation under the task details
|
|
|
|
**US-11.2** As a user, I want to link a task to a jade-depo note so that I can
|
|
associate detailed research or write-ups with a task.
|
|
|
|
- **Given** task 3 exists and a jade-depo note "debug-auth-issue.md" exists
|
|
- **When** I run `opal 3 annotate --note debug-auth-issue` (or similar)
|
|
- **Then** the task stores a reference to the jade-depo note
|
|
- **And** `opal 3 info` shows the linked note path
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-11.1: `opal <id> annotate "<text>"` SHOULD add a timestamped note.
|
|
2. FR-11.2: Annotations MUST be visible in `info` and `edit`.
|
|
3. FR-11.3: Annotations MUST sync via the existing change log / sync system.
|
|
4. FR-11.4: `opal <id> denotate` COULD remove the most recent annotation.
|
|
5. FR-11.5: Annotations SHOULD support linking to jade-depo notes (exact
|
|
mechanism TBD — flag, URI scheme, or convention like `note:slug`).
|
|
|
|
#### Design Decisions
|
|
|
|
- **Storage:** JSON text column (`annotations`) on the `tasks` table. Each
|
|
annotation is a JSON object with `timestamp` and `text` fields. Stored as a
|
|
JSON array, e.g.:
|
|
```json
|
|
[
|
|
{"timestamp": 1708300000, "text": "Traced to token expiry in middleware"},
|
|
{"timestamp": 1708310000, "text": "note:debug-auth-issue"}
|
|
]
|
|
```
|
|
This keeps annotations co-located with the task, avoids schema complexity,
|
|
and syncs naturally via the existing change_log triggers.
|
|
|
|
#### Open Questions
|
|
|
|
- Should annotations be searchable via filters (e.g., `opal annotation:token list`)?
|
|
- Jade-depo integration: should `opal 3 annotate --note <title>` verify the
|
|
note exists in jade-depo, or just store the reference loosely? Loose coupling
|
|
is simpler but can lead to stale links.
|
|
|
|
---
|
|
|
|
### IMP-12: Task History
|
|
|
|
**Priority:** COULD
|
|
|
|
#### Problem
|
|
|
|
The `change_log` table records every mutation for sync, but there's no
|
|
user-facing way to view a task's history. Useful for understanding what changed,
|
|
when, and debugging unexpected state.
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-12.1: `opal <id> log` COULD display the change history for a task.
|
|
2. FR-12.2: Output SHOULD show timestamp, change type, and what changed:
|
|
```
|
|
2026-02-18 09:15 created "Buy groceries" priority:D
|
|
2026-02-18 10:30 modified priority: D → H
|
|
2026-02-18 14:00 completed
|
|
```
|
|
3. FR-12.3: History MUST respect the existing change_log retention policy.
|
|
4. FR-12.4: History SHOULD be surfaced in `info` output (recent changes
|
|
section) and available in `edit` as read-only comment lines.
|
|
|
|
---
|
|
|
|
### IMP-13: Version Command
|
|
|
|
**Priority:** COULD
|
|
|
|
#### Functional Requirements
|
|
|
|
1. FR-13.1: `opal version` (or `opal --version`) MUST print the build version.
|
|
2. FR-13.2: Version SHOULD be set at build time via `ldflags`, reading from a
|
|
`VERSION` file in the repo root.
|
|
3. FR-13.3: Output SHOULD include version, commit hash, and build date.
|
|
|
|
---
|
|
|
|
## Out of Scope
|
|
|
|
- **GUI/TUI redesign** — this document covers CLI UX only.
|
|
- **New task attributes** (e.g., estimated effort, dependencies between tasks).
|
|
- **Multi-user features** — opal is a personal/household tool.
|
|
- **Plugin/hook system** — not needed at this stage.
|
|
- **Web UI changes** — covered separately in `opal-web/REQUIREMENTS.md`.
|
|
|
|
---
|
|
|
|
## Priority Summary
|
|
|
|
| ID | Improvement | Priority | Effort |
|
|
|--------|--------------------------------------|----------|----------|
|
|
| IMP-1 | Undo / uncomplete | MUST | Medium |
|
|
| IMP-2 | Better `add` feedback | MUST | Low |
|
|
| IMP-3 | Show matched tasks in confirmations | MUST | Low |
|
|
| IMP-4 | Fix `delete` display ID resolution | MUST | Low |
|
|
| IMP-5 | Handle colons in descriptions | MUST | Medium |
|
|
| IMP-6 | Consistent no-match error behavior | SHOULD | Low |
|
|
| IMP-7 | Recurring task feedback | SHOULD | Low |
|
|
| IMP-8 | Shell completions | SHOULD | Medium |
|
|
| IMP-9 | Relative dates in CLI reports | SHOULD | Low |
|
|
| IMP-10 | Dry-run flag for actions | SHOULD | Low |
|
|
| IMP-11 | Task annotations | SHOULD | Medium |
|
|
| IMP-12 | Task history | COULD | Medium |
|
|
| IMP-13 | Version command | COULD | Trivial |
|