- GET /api/v1/tasks/{id}/comments?q=&authorId= โ closes the named rev-128 next-sprint candidateRev 128's running state explicitly named 'per-thread comment search on the v1 surface' as the natural rev-129 candidate, citing rev-46 v1 comment listing as the load-bearing primitive that needed the search axis. Rev 129 closes that. The dashboard endpoint accepts optional `q` (โค200 chars, case-insensitive substring match across body + author name) and `authorId` (scope to one author) query params; replies always surface with their parent so the thread frame never breaks (rev-128 dashboard semantics exactly). Returns `{ comments, total (unfiltered count), filtered (matched count), authors (distinct authors with counts, sorted desc) }` so MCP hosts rendering 'show me Steve's comments on this task' can pre-populate an author picker without parsing the comment list themselves. Pure derived state โ no schema, no migration. Pairs with rev-46 comment listing + rev-29 comment reactions as the three-axis comment surface on the protocol-bound side: read (rev 46) + write (rev 26 POST) + react (rev 29) + search (rev 129). The MCP server (Q3 #1) gains one more pre-typed surface with nothing left to design on the comment-search axis.
- GET /api/v1/tasks/{id}/engagement โ aggregate reaction summary on the v1/MCP surfaceMirrors the rev-128 dashboard `.ld-task-reaction-summary` chip on the v1/MCP surface. Pure derived state โ sums every reaction on every comment in the task's rev-26 comments JSONB. Until rev 129 the engagement signal was dashboard-only on the protocol-bound side; an MCP host driving the desk could read every comment + reaction by enumerating `/api/v1/tasks/{id}/comments` but had to re-aggregate the per-emoji totals themselves. Rev 129 collapses that to one bearer-auth call. Returns `{ commentCount, totalReactions, topEmoji, topEmojiCount, reactionTotals (full rev-29 vocabulary, 0 when unused), topReactedComments (โค5 by reaction count) }` so MCP hosts rendering 'what's the team reacting to most on this task?' have a one-call answer. Closes the per-task engagement-visibility axis at parity in the same cycle as rev-129 search.
- OpenAPI typed coverage for both rev-129 endpoints โ closes the typed-contract gap in lockstepThe cadence pattern from rev 78 onward (every v1 enhancement gets typed in the OpenAPI 3.1 spec in the same cycle it ships) holds unbroken through rev 129. Both new endpoints typed: the rev-129 comments endpoint gets the new `q` + `authorId` query parameters typed with full response shape (comments, total, filtered, query, authorId, authors with the per-author count breakdown); the rev-129 engagement endpoint is fully typed with the `reactionTotals` Record<emoji, integer> shape, the `topReactedComments` array of per-comment shapes, and the full rev-29 emoji enum on `topEmoji`. The OpenAPI spec changelog header gains a rev-129 block explaining the v1 mirror pattern. The MCP server (Q3 #1) inherits one more pre-typed surface with nothing left to design.
- Cumulative dashboard polish โ Esc clears the rev-128 thread filter + inline 'Clear filter' chip on empty-stateCumulative micro-polish (every rev 22+ has carried at least one). Two small but cumulative pieces: (a) the rev-128 filter input now binds Esc to clear BOTH keyword AND author scope so an operator who narrowed deeply can escape with one key โ pairs with the rev-119 useComposerShortcuts Esc-to-cancel vocabulary across the dashboard typed-input surface, (b) the empty-state when filter has zero matches now carries an inline brand-color 'Clear filter' chip + a count of total comments so the operator sees 'Clear filter to see the 23 comments on this task' instead of an inert 'no matches' message. Until rev 129 an operator who narrowed too aggressively had to manually re-tap Everyone + erase the keyword to recover. Rev 129 collapses both into one tap. The thread filter now reads as a complete affordance โ narrow, escape, clear, all reachable through three orthogonal input modalities (keyboard / chip / mouse).