Skip to content

fix(example-todo): remove the inert is_completed/is_overdue flags and repair every filter that read them - #8295

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-7226-todo-derived-flags
Aug 13, 2026
Merged

fix(example-todo): remove the inert is_completed/is_overdue flags and repair every filter that read them#8295
os-zhuang merged 2 commits into
mainfrom
claude/issue-7226-todo-derived-flags

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#7226

The card's premise held, and understated the cost

is_completed / is_overdue were readonly: true booleans defaulting to false that nothing in the app ever wrote — verified on origin/main: no hook leg, no flow node, no action handler, and the seed data set neither.

The card says "no user-facing surface currently branches on either flag — no view filter, dashboard, report or dataset in the app reads them". That is no longer accurate, and it is the one part of the body I could not confirm. Twelve live references read them:

surfacefiltereffect on main
overdue list viewis_overdue == true, is_completed == falsepermanently empty
dashboard "Completed Today"is_completed: true + completed_datepermanently empty
dashboard "Overdue Tasks"is_overdue: true, is_completed: falsepermanently empty
dashboard "Weekly Task Completion"is_completed: true + completed_datepermanently empty
reports "Completed Tasks", "Time Tracking"is_completed: truepermanently empty
due_today view, 3 distribution charts, 2 open-task reports, reminder flowis_completed: falsevacuously true — matched completed tasks too
recurrence flow create_recordwrites is_completed: falsewrite to a flag nothing reads

So this was a live defect, not observation-class. Six user-visible surfaces rendered a permanent zero while looking like they worked.

Why REMOVE and not derive — the measurement that decided it

The dispatch ruled is_completed to derive-as-formula outright, and is_overdue to derive if a formula field may use the temporal functions. I verified the temporal half and it passes — but the route still fails, for a different reason that the ruling could not have anticipated.

Temporal formula fields are fully supported.packages/objectql/src/engine-write-formula-hydration.test.ts pins now()-valued formula fields as a platform surface with a per-call snapshot determinism guarantee. Measured directly: date(record.due_date) < today() in a Field.formula evaluates correctly on all four states (past-due open, future, no due date, completed).

But a formula field cannot be filtered. It is virtual — no driver materialises a column — so a where naming one matches nothing, silently. Measured on this app's own sqlite-wasm driver, same engine, same rows:

where { is_completed: true } -> 0 rows, no error
where { is_completed: false } -> 0 rows, no error <-- on the stored boolean: EVERY row
where { is_overdue: true } -> 0 rows, no error
CONTROL where { status: 'completed' } -> 1 row
CONTROL where { status: { $ne: 'completed' } } -> 1 row

The middle line is decisive: eight of the twelve filters use exactly that predicate. Deriving would have taken the "Due Today" view, the daily reminder flow and both open-task reports from working to silently empty — trading a wrong answer for an invisible one, in a reference app whose whole purpose is to be copied.

This is corroborated by the platform's own vocabulary: formula is refused on the ORDER BY axis (#7095, #6994) and the search axis (#6674) for precisely this storage fact. The filter axis has no such door — assertFilterFieldsExist judges only unknown, not unmaterializable — which is why this fails silently rather than loudly. (Filed as a separate finding; see below.)

Since derive is infeasible for both flags for one shared reason, and removal is not contested — the issue body itself names status / due_date as already carrying the information, and both are declared dimensions on the task_metrics dataset — this takes the remove route the dispatch authorises as the fallback.

What changed

wasis now
is_completed == truestatus equals 'completed'
is_completed == falsestatus not_equals 'completed'
is_overdue == truedue_date less_than '{today}' AND status not_equals 'completed'

Across task.object.ts, task.hook.ts, task.view.ts, task.dashboard.ts, task.report.ts, task.flow.ts, the three translation bundles and the README. Canonical operator spellings (not_equals, less_than), not the historical camelCase aliases.

The hook's afterUpdate overdue branch is removed, not re-armed. Becoming overdue is the passage of time, not a record write — a task nobody touches crosses its due date with no update to observe — so a record hook is structurally the wrong instrument and any version of that branch would fire late or never. The clock-driven sweep already exists in the right place: the overdue_escalation scheduled flow, which selects on due_date directly.

Verification

examples/app-todo/test/derived-flag-removal.test.ts (new):

  1. Nothing references the removed fields — a recursive walk of the app's real defineStack over keys and values, so both { is_completed: false } (key) and { field: 'is_completed' } (value) forms are caught, across objects, views, dashboards, reports, datasets, flows, translations and seed data in one pass.
  2. The replacements really select, on BOTH sides of the transition — the anti-vacuity half. A task is driven into completed through the real hook and back out; the open/done sets must swap each way. Asserting only on a never-completed task would be green for exactly the reason the old flag was green.
  3. Reverse verification of the route choice — the formula-shaped object is registered and measured: reads correct, filters empty, stored-column control correct.

Reverse verification of the pins themselves, direction predicted before running: restoring origin/main's app source turns the 3 removal pins RED and leaves the 4 engine-level tests GREEN (they exercise the engine, not the app's declarations). Observed exactly that — 3 failed, 4 passed.

pnpm --filter @objectstack/example-todo test 106 passed (4 files)
pnpm --filter @objectstack/example-todo typecheck clean
pnpm check:nul-bytes OK (7528 files)
pnpm check:query-options-erasure OK (baseline verified against fc71b84, no files added)
pnpm check:type-check-coverage OK (64/77 packages)

The last two are convention-scoped gates my new test file triggers; they were not in the dispatch's named set and were surfaced by re-deriving scripts/pm/dispatch-gates.mjs against the actual diff. The path derivation confirmed the PM's reading — no path-scoped family matches examples/app-todo.

Out of scope, filed separately

The filter axis has no unmaterializable verdict, so filtering a virtual formula field returns 0 rows silently on both backends while the sort and search axes refuse the same field with a 400 and a remedy. That asymmetry is what made this card's failure invisible, and it is a platform gap in packages/metadata-protocol — a report, not an edit here, per the dispatch's scope fence.


Generated by Claude Code

… repair every filter that read them
Both were readonly booleans defaulting to false that nothing ever wrote, while
twelve view/dashboard/report/flow filters read them as if maintained. Every
is_completed:true surface and the whole Overdue Tasks view were permanently
empty; the eight is_completed:false filters matched completed tasks too.
Removed rather than derived as formulas: a formula field is virtual, so a
filter naming one matches nothing -- measured at 0 rows with no error, where
the stored boolean returned every row. Deriving would have silently emptied
the Due Today view, the reminder flow and both open-task reports.
Every consumer now asks status / due_date directly. The hook's unreachable
overdue branch is removed rather than re-armed: becoming overdue is the
passage of time, not a record write, and the overdue_escalation scheduled
flow already covers it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARidKDYSCD56LaygrvDPnk
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 3:47am

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 13, 2026 04:29
@os-zhuang
os-zhuang added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit c11b699Aug 13, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7226-todo-derived-flags branch August 13, 2026 04:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

examples/app-todo: is_completed and is_overdue are readonly flags that nothing ever maintains — permanently false, and one of them is read by a hook

2 participants

@os-zhuang@claude