From the June 2026 quality review. File references use path:line.
Bug fixes
- Edit dialog crash on "custom" repeat —
simpledialogwas used but not imported inui/dialogs.py. Added the import. - Edit dialog rejected bare times (
14:00/1400). Introduced a single canonical parser,core/dates.parse_due_entry(), now used by the Add box, Edit dialog, Set Due, and import alike — so every text entry behaves identically. - "Misc" couldn't be re-selected in the Edit dialog — added
Miscto the priority dropdown and mapped it to the storedXcode. - Ultra/Misc priorities were invisible to reminders —
core/reminders.pyused ad-hoc priority maps that omittedU. It now uses the canonicalpriority_rank()/PRIORITY_ORDERfromcore/constants.py, so hazard-escalated (Ultra) tasks get reminders. (Covered by a regression test.) - Docs advertised unsupported date formats —
tomorrow/today/yesterdayand month-name dates (Sept 29,September 29 2027), with optional trailing times, are now actually parsed incore/dates.py. - Latent infinite-loop guard —
scheduler.advance_repeating_tasksnow breaks when a (malformed) repeat makes no forward progress.
Cleanup / hygiene
- Removed dead code: empty
core/query.py(deleted); the unreachableon_tree_double_click/edit_task_for_iidmethods (referenced a non-existentself.tree); and the no-opcheck_reminders+ its startup timer. - Replaced
print()debugging incore/actions.pywithlogging(quiet by default). - De-duplicated the month math (
add_months_dateonlyis now an alias ofmonth_add). - Filled in
pyproject.tomldescription and removed the placeholder author.
Modularization (no behavior change)
- Extracted
app.py's filter/search/sort predicates intocore/filters.py. - Moved the recurrence/midnight logic into
core/scheduler.py(the formerly empty module, now repurposed). - Moved mantra selection into
core/documents.py(pick_mantra_of_day/pick_random_mantra).
Tests — added a stdlib unittest suite under tests/ (89 tests) covering
dates, filters, scheduler, reminders, io_import, and model. Run with
python -m unittest discover -s tests. A full GUI smoke test (in a temp data dir)
also passes: add → filter → group → search → mark-done → journal/doc write.
UI polish (round 1)
- Fixed the reported color clash: the Misc row color and the group-header
color were both pale blue, so a Misc task read like a header. Group headers are
now a neutral gray bar (
ui/listview.py). - Added a bottom status bar (showing / open / done-today counts).
- Added sort-direction arrows (▲/▼) on the active column header.
- Slightly taller table rows, bold sortable headers, and a visible blue
selection highlight via
ttk.Style(app.py).
UI polish (round 2)
- New
ui/theme.pycentralizes all colors into Light/Dark palettes. - Light / Dark mode toggle (View menu), persisted via
ui_themesetting. v1: main window fully themed; some popup dialogs may still render light. - Removed the stray "(Modular)" from the window title.
- See DESIGN.md for the broader UI direction and the web/mobile plan.
UI polish (round 3)
- App icon (window + Windows taskbar) — generated by
tools/make_icon.py(tasklistprogram/ui/assets/icon.png/.ico). - ★ Today button — jumps back to the daily focus view (active + due today).
- Calmer priority colors — softened the light-mode row tints so dense lists read more professionally; the colored priority emoji carries the strong signal.
Web prototype — a static, dependency-free preview of the planned web version
lives in web/: sidebar nav (Today/Upcoming/Habits/All/
Completed + groups), stat cards, calm task rows with priority left-bars, a habit
streak heatmap, light/dark, an Add modal, search, and a responsive layout.
Backend (Phase 2 — first cut) — tasklistprogram/webserver.py is a stdlib
HTTP server that serves the web UI and a JSON API (/api/tasks, toggle/done, add,
delete, stats) backed by the real core/ logic and data file. Run
python -m tasklistprogram.webserver; the front-end auto-detects the API and runs
in live mode (real data, changes persist) or falls back to sample data.
Verified end-to-end (add / recurring-done advances the due date / soft-delete /
stats). Still local-only, no auth — Phases 3–4 add DB, auth, HTTPS, hosting,
and web push (see DESIGN.md).
Data location override — both the desktop app and the web server honor the
TINYTASKLIST_DATA_DIR env var (sync folder / separate DB / tests).
Web parity (round 4) — closed the big gaps vs. the desktop:
- Edit a task on the web (click a row, or right-click for a context menu with
Edit / Mark done / Suspend / Delete) via a new
PATCH /api/tasks/{id}. - Suspended tasks are now hidden from active/today/all (matching the desktop) — fixes the "AI deep learning / NCF ghosts." Added a Suspended view to manage them.
- Undo after a tap (toast with Undo for done / suspend / delete; restores a pre-action snapshot, so even a recurring "done" that advanced the date is reverted).
- Completed view is ordered by most-recently-completed.
- Desktop gained View → Open Web App (starts the local server if needed).
- Robustness:
_load_jsonnow tolerates a UTF-8 BOM, so atasks_gui.jsonsaved by Notepad/PowerShell no longer crashes either front-end. (Regression test added.) - Fixed a JS syntax error that had broken the whole front-end (also
node --checkguard) and made dark mode follow the OS theme.
Known caveat: editing in the desktop and web at the same time can clobber (last-writer-wins) — see ARCHITECTURE.md. The SQLite migration (below) is the fix.
Web parity (round 5) — driven by a 39-finding multi-agent audit
- CRITICAL fixed: concurrent web writes could lose data — the read-modify-write
cycle is now serialized with a lock (
webserver.py). - Collapsible group view (▤ Group) in the main list — see multiple groups and collapse the ones you don't need (the original point of groups), state remembered.
- Full filters mirroring the desktop: a Category sidebar (Active / Overdue / Repeating / Completed / Suspended / Deleted / All) × a When time filter × a Min priority filter × search.
- Deleted management: Deleted view with Restore and Delete permanently (new hard-delete endpoint); the API now returns all tasks so the client filters like the desktop.
- Hazard escalation: ⚠ shown on escalated tasks; ⚙ → Reset hazard escalation (new endpoint) mirrors the desktop.
- Fixed the "Done today" stat to count recurring completions via history; Habits heatmap now excludes suspended tasks.
- Hardened:
_load_jsontolerates a UTF-8 BOM (regression test); added op_update / op_reset_hazard / client_tasks tests (110 total).
Data safety + docs (2026-06-08)
- Automatic daily backups:
save_dbwrites a dated snapshot todata/backups/(14-day rotation) alongside the.bak. Cheap; never breaks a save. - Documentation system: added
CLAUDE.md(project profile),docs/FEATURES.md(parity matrix),docs/CHANGELOG.md,docs/IDEAS.md,docs/BUGS.md, and adocs/README.mdindex. Ideas now live in IDEAS.md; this file is the active plan.
- ✅ SQLite single source of truth — done (2026-06-08). Persistence moved to
data/tasks.dbbehindmodel.pywith atomic transactions; lossless JSON migration (kept as.premigration); desktop reload-on-focus via arevcounter closes the clobber window for normal use. Future hardening: per-row granular writes. - Auth layer scaffolding (DESIGN.md Phase 3) — now the top priority, before anything is reachable off-machine. Single-user password + session tokens, HTTPS.
- Smart / fewer groups. With ~20 groups the list is noisy. Ideas: collapse rarely-used groups, group-of-groups (sections), or auto-grouping related tasks by context (e.g. supplements taken together, a "morning routine"). Possibly offer a few designed templates (Supplements, Meals, Morning) instead of free-form groups.
- Clearer recurring instances. A task that recurs as just "meal" reads ambiguously; consider per-occurrence labels (Breakfast/Lunch/Dinner) or a title pattern so the day's instance is self-explanatory.
- Remaining web polish (from the audit): custom date-range filter; multi-select bulk actions; click-header column sort; accessibility (ARIA/semantic HTML); reduce per-row chip clutter; an aggregated "N overdue" banner on Today when data is stale; Ultra/priority icon shown in the editor.
-
Time filter hides date-less tasks. With Time ≠
any, tasks with no due date are filtered out (core/filters.passes_time_filter), and the default view isactive/today, so a freshly added no-due task "disappears." This is now documented (README/User Guide), but consider showing no-due active tasks undertoday/week/month, or adding a UI hint. -
Streak off-by-one.
stats_summarycounts streaks starting yesterday (core/model.py), so a task completed every day including today reads one fewer. Decide whether "today counts" and adjust. -
Reminder chip vs. dialog acknowledgement. The ⏰ chip lights for any elapsed unacknowledged checkpoint, but the Reminders dialog surfaces only the latest one per task — so clearing the chip can take several acknowledgements. Consider surfacing all elapsed checkpoints, or having the chip track only the latest.
-
Stray
data/mantras.txt. A leftover sits next to the livedata/mantras.md(left untouched — it's your personal data dir). Safe to delete locally. -
.idea/is tracked. Not sensitive, but most public repos gitignore it. Optional.
- Configurable data location.
DATA_DIRis hard-coded incore/model.pyandcore/documents.py. An env var or config file would let users point it at a synced folder (Dropbox/OneDrive) and keep data out of the package tree. (This would also make GUI smoke-testing trivial — the test currently monkeypatches the paths.) - Export. Import exists but there's no export; round-tripping the pipe format would be handy for backups/sharing.
- Undo for delete. Soft-delete + Restore exists; a quick "undo last action" toast would smooth the workflow.
- Periodic reminder pop-up. The old
check_remindershook was a no-op and was removed; if you want active nudges (not just the chip + Reminders list), add a real timer that opensRemindersDialogwhen checkpoints elapse.
A staged path from low-risk polish to bigger redesigns. Each tier is independent — pick whatever appeals; nothing here is required.
Tier 1 — quick wins (low effort, low risk)
- Tooltips on the toolbar/filter controls and priority codes (Tk has no native tooltip, but a ~20-line helper covers it).
- Empty state: when the list is empty, show a friendly "No tasks match these filters — try Time = any" hint instead of a blank table.
- Clear-search "✕" button and an "overdue" count in the status bar.
- Remember window size/position between runs (save geometry in settings).
- Confirm-on-quit only if there are unsaved edits (currently saves are immediate, so this may be unnecessary — verify).
Tier 2 — interaction polish (medium effort)
- Inline date picker: replace the free-text Due field's reliance on typing with
an optional calendar popup (a small custom widget, since
tkcalendaris a 3rd- party dep we'd otherwise avoid). Keep text entry as the fast path. - Drag-free reordering / manual priority within a group via context-menu "move up/down", or a dedicated sort mode.
- Keyboard-first flow: arrow keys already move selection; add
eto edit,ddone,Deldelete (Del exists), and a/to focus search. - Better Settings dialog grouping (Reminders / Display / Hazard sections with separators) and live preview of "min priority to show".
Tier 3 — visual redesign (larger)
- Theme pass / dark mode. Centralize all colors in one
core/theme.py(or a dict) and offer Light/Dark. This also makes the priority palette tunable in one place. Thettk"clam" theme is the most themeable starting point. - Toolbar with icons instead of text buttons (Tk supports
PhotoImage; ship a few small PNGs or use unicode glyphs to stay dependency-free). - Card/detail pane: a right-hand panel showing the selected task's full notes and document preview, instead of opening an external editor.
- Responsive layout: let columns/filters wrap on narrow windows.
How to approach UI changes safely
- Centralize styling first (one theme module) so changes are one-line and reversible — this is the single highest-leverage refactor for UI work.
- Make one change at a time and eyeball it; keep the
ttk.Style/color choices in that module so diffs stay readable. - The GUI can be smoke-tested headlessly (build the window, drive it, tear down — see the review notes), so wiring changes can be caught without manual clicking; only the look needs a human glance.
No rewrite needed — the app.py / core / ui split is sound. After this pass,
app.py is meaningfully slimmer (filtering, scheduling, and mantra logic now live
in core/), and the pure-logic layer is test-covered.