Conversation
📦 npm preview dry run —
|
…us facet The Tasks table's search box only ever matched title, branch and workflow, so a task known by its tracker number — the way it is named in a PR body or a standup — could only be found by remembering its title. `referenceNeedle` recognizes `open-mercato#909`, a bare `909`, `pr 909` and `issue 42`, and `filterRuns` answers them against `taskReferences`: the same list the row's own chip is built from, so search can never surface a number the table would not show. Alongside it, the pure model the status filter needs: `TaskListFilters`, `filterTaskList` (facet AND search), `statusFacetOptions` with counts computed the way the global page computes them, and the toggle/count helpers the Clear affordance reads. The facet's vocabulary is the status PILL's word (`deriveAttention().label`), not `RunRecord.status`, so it can offer `scheduled` and `monitoring` — sub-states the record spells as failed/running — and can never disagree with the column beside it.
… supports `lib/task-selection.ts` is the pure half of multi-edit: toggling a row, the tri-state header checkbox, and which of archive / restore / mark-read / mark-unread a given selection can actually carry out. Two rules it exists to enforce. A selection is always read against the rows CURRENTLY ON SCREEN, so an id that outlived its row — filtered away, archived, patched out by an SSE tick — is inert rather than able to act. And an action offers itself only for the rows it would really change, using the SAME gates the single-row actions use: a `review` run is not archivable by checkbox any more than it is by the "Archive finished" broom, and only a `canBeUnread` row can be put back to unread.
…ion of rows The Tasks table gets the two things it was missing next to the global page: a Status facet — the same searchable multi-select pill, with per-option row counts — and a selection column whose bulk bar can archive, restore, mark read and mark unread any number of rows at once. The facet reads the status PILL's word, so it can offer `scheduled` and `monitoring`, and it ANDs with the search box; Clear counts and undoes both. An empty result now blames the filter when a filter is what emptied it, instead of reporting an empty archive. Multi-edit is deliberately conservative. Select-all covers the FILTERED list and never the rows a filter is hiding; each action is offered only for the rows it would really change (a `review` run is no more archivable by checkbox than by the broom); the fan-out is `allSettled` over the per-run endpoints, so one refused write neither cancels the batch nor gets reported as a success — the toast says "Archived 1 of 2 tasks — 1 failed: …" and the list is refetched from the server rather than trusted from the cache.
🏷️ label rationale —
|
|
🤖 |
Review finding (minor, user-facing copy): `Marked read 2 tasks.` is a log line, not a sentence. English puts the object in the middle of "marked … read", so the verb table now carries a trailing complement and the toast says "Marked 2 tasks read." — and "Marked 0 of 1 task read — 1 failed" on the partial path.
🔍 Code Review: feat(tasks): filter the project task list by status, search it by PR/issue number, and bulk-edit a selection🎯 SummaryThis PR extends the per-project Tasks list ( The shape of the change is the right one for this codebase. All of the decision-making is pure and lives in Two design choices are worth calling out as good, because they are the ones that usually go wrong in a multi-select feature. First, the status facet's vocabulary is the status pill's word ( One finding was raised and fixed during this pass (see 🔹 Minor). Nothing else blocks. Verdict✅ approve — there are no blockers and no majors. Every changed surface is additive, the full validation gate is green on the reviewed head, the behavior changes ship with unit and route-level tests (including the partial-failure path of the bulk fan-out), and the one minor finding was fixed in Self-approval note: GitHub refuses a formal approving review on one's own PR, so this report is posted as a review comment rather than through 🧪 Validation Gate
One environmental caveat, stated rather than buried. Six server tests (
Findings🔹 Minor
💅 Nit
💥 Breaking Changes
Two additional repo-specific gates, both clean: the graceful-degradation promise is unaffected (this is presentation over data the cockpit already had; with no On the UI performance gate: nothing became client-rendered that was not already (the cockpit is a Vite SPA end to end), no route-specific heavy module moved into a global provider, and the new imports are modules the bundle already contained. The one measurable addition is this feature's own code; 🧪 Test CoverageWhat is covered, and how. 40 new cases across three files, all of which fail without the change:
Gaps, honestly. (1) No live-browser verification — |
|
🤖 The pass ran in autofix mode (this run authored the PR). It found no blockers and no majors, fixed the one minor finding it did raise — the bulk-edit receipt's wording — in CI, disclosed rather than waited on: “Unit, build, E2E, and package” passed (3m22s) on this head. “Publish npm snapshot” is still pending — it is a publishing job, not a correctness signal, and this agent will not block on it. No |
…ist-filters-bulk-edit
🤖
|
|
ℹ️ The same branch is now also open upstream as open-mercato#920 (open-mercato#920), which is where the review for merging should happen — this PR stays as the mirror carrying the run's artifacts (execution plan, code-review report, run summary). The diffs are identical; close whichever one you do not want to keep. |
Tracking plan: .ai/runs/2026-08-23-task-list-filters-bulk-edit.md
Status: complete
🎯 Goal
What Changed
packages/web/src/lib/tasks-table.ts— the pure filter model.referenceNeedle()recognizes#909, a bare909,pr 909andissue 42;filterRuns()answers them againsttaskReferences(), the same list the row's own chip is built from, so search can never surface a number the table would not show. Alongside it:TaskListFilters,filterTaskList()(facet AND search),statusFacetOptions()(options from the statuses present, counts computed the way the global page computes them), plustoggleStatusFilter/activeFilterCount/hasActiveTaskFiltersfor the Clear affordance. The facet's vocabulary is the status pill's word (deriveAttention().label), notRunRecord.status, so it can offerscheduledandmonitoringand can never disagree with the column beside it.packages/web/src/lib/task-selection.ts(new) — the pure selection model:toggleSelected,toggleAllVisible, the tri-stateselectionSummary(which intersects the selected ids with the rows currently on screen, so a stale pick is inert),bulkActionTargets(per-action gating on the same rules the single-row actions use), andbulkResultMessagefor an honest receipt.packages/web/src/routes/tasks-overview.tsx— the Status pill and Clear in the header, a selection column on the table (tri-state header box) and on the<mdcards, the bulk action bar, a filter-aware empty state, and the route-level fan-out:Promise.allSettledover the existing per-run endpoints, one invalidation, and a toast that can say "Archived 1 of 2 tasks — 1 failed: …".No server, contract or persisted-state change: every action already had a per-run route (
POST /runs/:id/archive,/read,/unread).🧪 Tests
npm run typecheck✅ ·npm test✅ 6221 passed / 327 files ·npm run test:unit✅ 36 ·npm run build✅ (incl.check:pack) ·npm run test:package✅ 15.lib/tasks-table.test.ts(reference needle parsing, reference search, status vocabulary, facet counts, the status-order drift guard),lib/task-selection.test.ts(toggling, stale picks, per-action gating, select-all under a filter, receipt wording) androutes/tasks-overview.test.tsx(filtering through the real facet popover, Clear, empty-state wording, selection mechanics, action gating, checkbox-does-not-navigate on both row and card, and a route-level bulk archive where one run is refused).om-auto-review-pr --autofixreturned approve — no blockers, no majors; one minor (the bulk receipt's wording) was fixed ina4ab6fecand the gate re-run green on that commit. The repository's own CI workflow (Unit, build, E2E, and package) passed on this head.npm run test:e2ecould not provision theagent-browserprovider here and exitedTEST_E2E_STATUS=skipped, which is not a pass. This is a UI change, so that gap is real — see the run-summary comment.TMPDIRpoints inside the repository (theymkdtempa "not a git repo" fixture, which then resolves to this repo). The gate above was run withTMPDIR=/tmp; the failures are unrelated to this change and reproduce on a clean checkout.💥 Breaking Changes
data-column-id="select"and are excluded from the column assertions).📋 Progress
See the Progress section in the tracking plan.