|
| 1 | +--- |
| 2 | +"@objectstack/example-todo": patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(example-todo): remove the inert `is_completed` / `is_overdue` flags and repair every filter that read them (#7226) |
| 6 | + |
| 7 | +`examples/app-todo/src/objects/task.object.ts` declared `is_completed` and |
| 8 | +`is_overdue` as `readonly: true` booleans defaulting to `false`. Nothing in the |
| 9 | +app ever wrote either one — no hook leg, no flow node, no action handler, and |
| 10 | +the seed data set neither — so both were `false` on every row for the life of |
| 11 | +the app, while **twelve** view / dashboard / report / flow filters read them as |
| 12 | +if they were maintained. |
| 13 | + |
| 14 | +The consequence was not cosmetic. Every surface asking `is_completed: true` was |
| 15 | +permanently empty: the "Completed Today" tile, the "Weekly Task Completion" |
| 16 | +trend, and both the "Completed Tasks" and "Time Tracking" reports. So was the |
| 17 | +whole "Overdue Tasks" list view, which asked `is_overdue: true`. The eight |
| 18 | +surfaces asking `is_completed: false` were vacuously true instead — they matched |
| 19 | +completed tasks too. `task.hook.ts` also carried an `afterUpdate` branch gated |
| 20 | +on `data.is_overdue && previous && !previous.is_overdue`, which could never run. |
| 21 | +Since #7036 started stamping `completed_date` on the completion transition, the |
| 22 | +divergence was directly readable in the shipped app: a task could carry a |
| 23 | +completion date and `is_completed: false` at the same time. |
| 24 | + |
| 25 | +**Removed rather than derived as formula fields, for a measured reason.** A |
| 26 | +`Field.formula(...)` computes both correctly — including the temporal one |
| 27 | +(`date(record.due_date) < today()` evaluates per read, with a per-call `now` |
| 28 | +snapshot) — so deriving looks like the obvious repair. It is not: a `formula` |
| 29 | +field is virtual, no driver materialises a column for it, and so a *filter* |
| 30 | +naming one matches nothing. Measured on this app's own sqlite-wasm driver, |
| 31 | +`where { is_completed: false }` against a formula field returns **0 rows with no |
| 32 | +error**, where the stored boolean returned every row. Deriving would therefore |
| 33 | +have silently emptied the "Due Today" view, the daily reminder flow and both |
| 34 | +open-task reports — trading a wrong answer for an invisible one. |
| 35 | + |
| 36 | +`status` and `due_date` are stored, indexed columns that already carry the |
| 37 | +information, and both are declared dimensions on the `task_metrics` dataset, so |
| 38 | +every consumer now asks the semantic layer's own vocabulary directly: |
| 39 | + |
| 40 | +| was | is now | |
| 41 | +|---|---| |
| 42 | +| `is_completed == true` | `status equals 'completed'` | |
| 43 | +| `is_completed == false` | `status not_equals 'completed'` | |
| 44 | +| `is_overdue == true` | `due_date less_than '{today}'` AND `status not_equals 'completed'` | |
| 45 | + |
| 46 | +Updated across `task.object.ts`, `task.hook.ts`, `task.view.ts`, |
| 47 | +`task.dashboard.ts`, `task.report.ts`, `task.flow.ts`, the three translation |
| 48 | +bundles and the README. The hook's dead overdue branch is removed rather than |
| 49 | +re-armed against `due_date`: becoming overdue is the passage of time, not a |
| 50 | +record write, so a record hook is structurally the wrong instrument — the |
| 51 | +clock-driven `overdue_escalation` scheduled flow already covers it. |
| 52 | + |
| 53 | +Pinned by `examples/app-todo/test/derived-flag-removal.test.ts`, which walks the |
| 54 | +app's real `defineStack` for any surviving reference, drives the replacement |
| 55 | +filters across **both** sides of the completion transition (so a filter cannot |
| 56 | +pass for the same reason the old flag did — everything being false), and records |
| 57 | +the formula-filter measurement that decided the route. |
0 commit comments