Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
03b63b9
docs: add v1.0 Kanban board design (#202)
ZFordDev Sep 10, 2026
087d665
docs: record Kanban delivery model
ZFordDev Sep 10, 2026
de0e420
Merge pull request #219 from ZFordDev/issue/202-kanban-board-design
ZFordDev Sep 10, 2026
c5c9b4d
Merge remote-tracking branch 'origin/main' into feature/kanban-board
ZFordDev Sep 10, 2026
728a445
feat: add Kanban board view with opt-in board_stage (#203)
ZFordDev Sep 10, 2026
eb3d36c
style: satisfy ruff format gate
ZFordDev Sep 10, 2026
0b8c754
Merge pull request #220 from ZFordDev/issue/203-kanban-board-ui
ZFordDev Sep 10, 2026
a7b5eec
feat: drag cards between Kanban columns with undo (#204)
ZFordDev Sep 10, 2026
1698e92
Merge pull request #222 from ZFordDev/issue/204-kanban-drag-drop
ZFordDev Sep 11, 2026
3a4ee7c
feat: board search, On Board filter, and keyboard navigation (#205)
ZFordDev Sep 11, 2026
e84befc
Merge pull request #223 from ZFordDev/issue/205-kanban-search-a11y
ZFordDev Sep 11, 2026
752ed5d
fix: evict stale board cards on rebuild, preserving column spacer
ZFordDev Sep 11, 2026
997b15b
feat: support unscheduled tasks for board planning (#225)
ZFordDev Sep 11, 2026
91795c7
Merge pull request #224 from ZFordDev/fix/kanban-card-eviction
ZFordDev Sep 11, 2026
df11bcd
Merge pull request #226 from ZFordDev/issue/225-unscheduled-tasks
ZFordDev Sep 11, 2026
1139dad
chore: bump experimental branch to v0.10.1 and document Kanban work
ZFordDev Sep 11, 2026
02413d2
feat: sort and filter unscheduled tasks, disable repeat for unscheduled
ZFordDev Sep 11, 2026
d0c70c4
feat: refine board cards, add icon actions, and drop board delete
ZFordDev Sep 11, 2026
93322d9
docs: finalize v0.10.1 notes and README for the Kanban rollout
ZFordDev Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Changelog

## v0.10.1 (2026-09-23)

### Info
> Rollout note: the Kanban board is new in this release. It is published to the
> Snap `edge` channel first so the Linux community can help surface issues
> before it is promoted to a stable channel.

### Added
- Kanban planning board with an opt-in **board stage** (backlog / in progress /
done): an "Add to Kanban" option in the create and edit dialogs, a board page
reachable from the sidebar with per-column counts and empty states, and
per-card Complete and Edit actions [#203].
- Drag-and-drop card movement between board columns, saved as a normal task
edit so Ctrl+Z undoes a move; moving a card between stages never changes its
completion state [#204].
- Board search by card text, an **On board** filter in the Tasks view,
arrow-key navigation across columns and cards, double-click or Enter to edit,
and accessible names and descriptions on cards and columns [#205].
- **Unscheduled** tasks for deadline-independent planning: tasks can be created
and edited without a date and time, board cards are labelled "Unscheduled",
and date-less tasks are kept out of the calendar and the Today/Upcoming
filters [#225].
- A **Scheduled only** task filter hides date-less tasks, and date-less tasks
now sort as if due now (nearest to today) in every order.
- Board cards alternate plain/tinted backgrounds so adjacent cards read as
separate entries, and card actions are compact icon buttons (tick =
complete, pencil = edit) with tooltips. The board deliberately has **no delete
action**: a task can only be removed in the Tasks/Calendar views (or taken
off the board by clearing "Add to Kanban").

### Fixed
- Dragging a card to another column no longer leaves a stale copy behind in the
original column; a board rebuild now evicts every card and restores the
column's spacer [#192].
- Version-derived packaging metadata (Windows version resource and Snap
manifest) is kept in sync with the project version so release metadata no
longer lags the code version.
- The Repeat fields are disabled while a task is marked Unscheduled, since a
recurring task needs a due date.
- Completing a recurring task no longer duplicates it on the board: the next
occurrence is created off the board, so only the completed card remains.

### Docs
- Added `docs/kanban-board-design.md`, the approved design for the Kanban board
covering the data model, storage layer, user interface, and delivery model
[#202].

### Plan
- Add a `--board` stage option to `schedplus add` and `schedplus edit`, making
`--date`/`--time` optional so unscheduled planning cards can also be created
from the CLI [#228].
- Publish Kanban board documentation through DocsHub now that the feature's
shape is confirmed [#192].

## v0.9.3 (2026-09-09)

### Info
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ control.
- Create, edit, delete, search, filter, and sort scheduled tasks.
- Plan through native month, week, and day calendar views.
- Create tasks from the calendar and drag them to reschedule.
- Plan with a Kanban board: drag cards between Backlog, In progress, and Done
columns, search cards, and mark them complete (`docs/kanban-board-design.md`).
- Create unscheduled (date-less) tasks for planning, with a "Scheduled only"
filter when you need just dated tasks.
- Choose the advanced PyQt workspace, lightweight Tkinter interface, or CLI.
- Keep task data in a platform-appropriate local SQLite database.
- Create versioned local backups and portable JSON task exports.
Expand Down
270 changes: 270 additions & 0 deletions docs/kanban-board-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# Kanban board design

Status: **Approved — Session 1 of #192 complete**
Related: #192 (parent), #202 (design), #203 (UI), #204 (drag/drop + undo), #205 (search/a11y)
Applies to: PyQt workspace only (v1.0 scope)

## 1. Purpose

Add a Kanban planning view that complements the existing Tasks and Calendar
views for active planning. It is an **opt-in** surface: a task appears on the
board only when the user deliberately adds it, and each task sits in exactly
one planning stage. Nothing is derived automatically from task fields, and the
board never changes what a task means in Tasks or Calendar.

## 2. Data model

### 2.1 New field

Add `board_stage` to the `Task` dataclass:

```python
board_stage: str = "" # "" = not on the board; else one of BOARD_STAGES
```

**Decided:** the parent tracking text originally said "nullable `board_stage`
(`NULL` = off-board)". This codebase represents every optional field — `notes`,
`priority`, `duration`, `category`, `recurrence`, `reminder`, `completed` — as
`TEXT NOT NULL DEFAULT ''`, never `NULL`. A `NULL` column would be inconsistent
with every existing field and force guards throughout storage, serialization,
and the UI. Approved approach: store `""` for "not on the board". The semantic
("a task without a stage is not on the board") is unchanged.

### 2.2 Canonical stages

```python
# src/logic/board.py (new module, UI-independent)
BOARD_STAGES = ("backlog", "in_progress", "done")
```

- Values are lowercase, matching `priority` (`low/medium/high`) and
`recurrence` (`daily/weekly/monthly/yearly`) conventions.
- Display labels are a UI concern: **Backlog**, **In progress**, **Done**.
- No user-defined columns or multiple boards in v1.0 (#192 scope).

### 2.3 Stage vs completion

- A board stage is a planning state, **not** task completion.
- Moving a card to `done` does **not** complete the task.
- Completing a task does **not** move its card.
- A completed task stays in whatever stage it is in until the user moves it.

## 3. Schema migration

Append **migration 7** to `src/logic/storage/migrations.py` (never edit a
released migration):

```python
def _migration_7(connection):
connection.execute(
"ALTER TABLE entries ADD COLUMN board_stage TEXT NOT NULL DEFAULT ''"
)
```

- `CURRENT_SCHEMA_VERSION` becomes 7.
- The existing transactional `migrate_database()` already creates a
pre-migration backup for existing databases and migrates in order.
- Existing rows get `''` → automatically off-board. **No data is relocated,
hidden, or duplicated.**

### Storage surface (`src/logic/storage/sqlite_storage.py`)

Add `board_stage` to every SQL statement and row mapping:
`create_entry`, `update_entry`, `get_entry`, `list_entries`,
`list_completed_entries`, `replace_entries`, `import_entries`,
`_task_from_row` (index 15, keep the defensive `if len(row) > 15` guard), and
`_task_values`.

## 4. Validation

Extend `src/logic/validation.py`:

```python
stages = ("backlog", "in_progress", "done")
def validate_board_stage(value: str) -> str:
value = value.strip()
if value and value not in stages:
raise ValidationError(f"Board stage must be one of {', '.join(stages)}.")
return value
```

- Normalize `" "`/`None`-like emptiness to `""`.
- Reject any value outside `BOARD_STAGES` so the database and UI can never
disagree about what a valid stage is.
- `validate_task()` calls `validate_board_stage` on `getattr(task,
"board_stage", "")` so every persistence path is covered.
- Unscheduled (date-less, time-less) tasks are valid for planning; a task with
only one of date or time set is rejected. The create/edit dialog disables the
Repeat fields while `Unscheduled` is checked, since a recurring task is
meaningless without a due date. (Repeat values already present on a dateless
task are left untouched and ignored.)

## 5. Serialization (backup / restore / export / import)

`src/logic/data_transfer.py`:

- Add `"board_stage"` to the `optional` set in `_parse_tasks`.
- Old files without the key load as `""` (off-board) — backward compatible.
- Export picks the field up automatically via `asdict(task)`.
- `FORMAT_VERSION` stays `1`: the field is additive/optional, exactly like
`notes`, `category`, and `reminder` were.

## 6. Scheduler

- `Scheduler.add_task(..., board_stage="")` — pass through to `Task`.
- `Scheduler.update_task(task)` — unchanged; it persists the whole object.
- Board moves are ordinary task edits. A move produces a modified `Task` and
calls `update_task`, so no new scheduler surface is required.

## 7. Undo

No new `UndoAction` type. A board move is recorded with the existing
`UndoManager.record_edit(snapshot_of_task_before_move)`; `Ctrl+Z` runs the
existing `edit` undo path and restores the prior `board_stage` (and any other
fields edited at the same time). This is the same mechanism the edit dialog
already uses (`window.open_edit_dialog`).

## 8. Card content

A card renders the existing task attributes — nothing stored on the card
beyond the task itself:

- text (primary, wraps)
- date + time
- category badge
- priority (styling)
- duration
- recurrence
- reminder
- completion indicator (informational; does not govern board position)

## 9. UI model

### 9.1 Sidebar

Add **Kanban** as a fourth navigation entry in `window.py`:

- `_build_sidebar()`: new `_navigation_button("Kanban")`, consistent with the
existing Tasks/Calendar/Settings buttons.
- `self.pages.addWidget(board_page)` after the calendar page.
- `show_page("board")` switch in the same `QStackedWidget`.

### 9.2 `BoardView(QWidget)` — new `src/ui/pyqt/board_view.py`

- One column widget per stage, rendered from `scheduler.get_tasks()` grouped
by `task.board_stage`. Cards are only tasks whose `board_stage` matches.
- Signals mirror `TaskListWidget`: `add_requested`, `edit_requested`,
`complete_requested` — wired in `window.py` to the existing handlers
(`open_add_dialog`, `open_edit_dialog`, `complete_task`). The board has no
delete action: cards are never deleted from the board (the task list and
calendar are the only places that delete tasks). A card can be taken off the
board by clearing "Add to Kanban" in the edit dialog.
- Cards alternate a plain/tinted background so adjacent cards read as separate
entries, and carry small icon-only action buttons (tick = complete,
pencil = edit) with tooltips and accessible names.
- `refresh()` rebuilds from `scheduler.get_tasks()` so board data is always
the task list itself (Task and Calendar remain the source of truth).
- Empty states: a whole-board empty state and a per-column "no tasks" spill.

### 9.3 Create / edit dialog (`add_dialog.py`)

- Add an **"Add to Kanban"** checkbox (unchecked by default on create).
- When checked, reveal a **Board column** combo (`backlog`, `in_progress`,
`done`) defaulting to `backlog`.
- On edit, the checkbox is pre-checked when `task.board_stage` is set, and the
combo preselected. Unchecking clears `board_stage` (removes from board).
- `get_values()` returns `board_stage`; update both callers in `window.py` and
the affected tests.

### 9.4 Tasks page filter (`task_list.py`, optional for #205)

Add an `On Board` filter option (`task_filter: "board"`) that shows only
tasks with a non-empty `board_stage`. Touches: `FILTERS` in
`settings_dialog.py`, `TaskFilterProxyModel.filterAcceptsRow`, and the
`task_filter` choice set in `data_transfer._validate_ui_preferences`.

### 9.5 Startup view (non-goal for v1.0)

`startup_view` stays `tasks | calendar`. A `board` startup option is possible
later; leaving it out keeps the settings surface unchanged.

## 10. CLI

Keep CLI surface minimal (parent constraint). Recommend only:

```bash
schedplus add "Refactor auth" --date 2026-09-20 --board backlog
schedplus edit 7c94a2 --board done # also accepts --board "" to remove
```

- Invalid stage exits `2` (existing CLI validation path).
- `list` gains no new filter in v1.0.

## 11. Migration & rollback

- Upgrading adds one column with `''`; the existing pre-migration backup
mechanism already protects every upgrade.
- Rollback = restore the automatic pre-migration backup (same workflow as any
schema migration today).
- No data movement, no prompts, no hidden reassignment.

## 12. Non-goals (v1.0)

- User-defined columns; WIP limits; swimlanes; multi-board.
- Auto-derivation of board membership from priority/dates/completion.
- Card ordering within a column beyond stable insertion order.
- Web/online sync of board state.
- Theme/token work for cards (uses existing QSS tokens).

## 13. Test plan

| Area | Coverage |
| --- | --- |
| Schema | Migration 7 runs last; existing migrations 1–6 untouched; `_task_from_row` guards index 15 for pre-7 rows |
| Validation | `""` and each stage accepted; invalid/whitespace-cased values rejected; restored/imported tasks normalized |
| Serialization | Export includes `board_stage`; old import without it loads as `""`; round-trip preserves stage |
| Scheduler | `add_task(board_stage=...)`; `update_task` stage change persists |
| Board model | grouping is UI-independent; off-board tasks excluded |
| Undo | Board move → `Ctrl+Z` restores prior stage |
| PyQt (offscreen) | Sidebar shows Kanban; board lists only opted-in tasks; dialog controls set/clear stage; empty + populated states; `On Board` filter |
| CLI | `--board` on add/edit; invalid stage → exit 2 |

## 14. Files touched (summary)

- `src/logic/scheduler.py` — `Task.board_stage`, `add_task(board_stage=)`.
- `src/logic/board.py` — **new**: `BOARD_STAGES`, grouping helper.
- `src/logic/validation.py` — `validate_board_stage`.
- `src/logic/storage/migrations.py` — migration 7.
- `src/logic/storage/sqlite_storage.py` — column mapping.
- `src/logic/data_transfer.py` — `board_stage` optional field.
- `src/ui/pyqt/board_view.py` — **new**: board page.
- `src/ui/pyqt/window.py` — sidebar entry, wiring, dialog callers.
- `src/ui/pyqt/add_dialog.py` — "Add to Kanban" + column controls.
- `src/ui/pyqt/task_list.py` + `settings_dialog.py` — `On Board` filter.
- `src/cli/commands.py` — `--board` on add/edit.
- `tests/` — new `test_board.py`; updates to schema, data-transfer, CLI,
PyQt-workspace, and dialog tests.

## 15. Approval checklist (matches #202 acceptance criteria)

- [x] Opt-in model, column set (`backlog`/`in_progress`/`done`), and
stage-vs-completion semantics approved.
- [x] `""`-instead-of-`NULL` convention confirmed.
- [ ] Serialization, validation, and undo impact specified.
- [ ] Migration keeps existing tasks off-board by default (`''`), no
duplication, no hidden reassignment.
- [ ] Tasks and Calendar remain the source of truth.

## 16. Delivery model

Kanban work never merges directly to `main`. All sub-issue PRs (#202–#205)
integrate into the parent branch **`feature/kanban-board`**, which is created
off the release point of `main`. When the feature is complete and reviewed,
the maintainer decides to either:

- **integrate**: open a final PR from `feature/kanban-board` into `main`; or
- **cut**: close the parent branch and the sub-issues without merging, leaving
`main` untouched.

This keeps `main` shippable at any time and lets the feature be reworked or
dropped without touching released code paths.
8 changes: 4 additions & 4 deletions packaging/pyinstaller/version_info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Keep filevers and prodvers aligned with pyproject.toml for each release.
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(0, 9, 3, 0),
prodvers=(0, 9, 3, 0),
filevers=(0, 10, 1, 0),
prodvers=(0, 10, 1, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
Expand All @@ -16,12 +16,12 @@ VSVersionInfo(
StringTable('040904B0', [
StringStruct('CompanyName', 'ZFordDev'),
StringStruct('FileDescription', 'SchedPlus desktop scheduler'),
StringStruct('FileVersion', '0.9.3'),
StringStruct('FileVersion', '0.10.1'),
StringStruct('InternalName', 'SchedPlus'),
StringStruct('LegalCopyright', 'Copyright (C) ZFordDev'),
StringStruct('OriginalFilename', 'SchedPlus.exe'),
StringStruct('ProductName', 'SchedPlus'),
StringStruct('ProductVersion', '0.9.3')
StringStruct('ProductVersion', '0.10.1')
])
]),
VarFileInfo([VarStruct('Translation', [1033, 1200])])
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "schedplus"
version = "0.9.3"
version = "0.10.1"
description = "Modern, local-first scheduling and productivity application"
readme = "README.md"
license = { text = "GPL-3.0-only" }
Expand Down
2 changes: 1 addition & 1 deletion snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: schedplus
base: core24
version: "0.9.3"
version: "0.10.1"
platforms:
amd64:
summary: Plan tasks and time with a local-first scheduler
Expand Down
Loading
Loading