- Multi-device sync of per-thread filter scope โ closes the named rev-134 next-sprint candidateRev 134 shipped per-task discussion filter persistence in localStorage so an operator returning to the same task's discussion sees their last filter scope auto-restored. Rev 134's running state explicitly named multi-device sync via the rev-78 dashboardPrefs JSONB as the rev-135 candidate, citing the 'pairs with rev-78 panel-collapse multi-device sync at the per-thread filter axis' shape that has run since rev 78. Rev 135 closes that. New `taskCommentFilters` field on `DashboardPrefs` keyed by taskId, carrying the rev-128 keyword + author + rev-130 reactions-only scope plus an `at` timestamp. Server-side LRU eviction at 30 entries by `at` timestamp keeps the JSONB lean as a workspace accumulates filter scope across many tasks; partial-merge patch shape (sending one taskId upserts only that entry, sending one with empty scope deletes it, sending an empty object {} clears every entry for the rev-135 reset path) means a single-thread filter change doesn't blow away other tasks' stored scope. Same dual-layer sync pattern as rev-127 costPanelOrder + rev-78 collapsedPanels โ localStorage stays the immediate write-through cache for sync render so the dashboard is never blocked on a network round-trip; server JSONB is the source of truth for cross-device drift; fire-and-forget debounced PUT (~600ms after last filter change) keeps the network cost off the render hot path. The TaskComments client component prefers `serverInitialFilter` over localStorage on first render so cross-device drift wins (Machine A's most-recent narrowing follows the operator to Machine B). Closes the per-thread filter cluster on the multi-device-sync axis: write (rev 134 localStorage) + read (rev 135 server JSONB winning over localStorage) + sync (rev 135 fire-and-forget debounced PUT).
- Synced/Restored chip palette swap distinguishes cross-device vs same-device restorationRev 134's 'Restored' chip flagged that the operator's last filter scope on this task was loaded from localStorage; rev 135 extends the same chip with an `is-synced` modifier when the scope was restored from server-side dashboardPrefs (cross-device sync) vs localStorage (same-device persistence). Brand-purple `rgba(107,78,214,*)` palette + 'Synced' copy distinguishes the cross-device path from the rev-134 brand-color teal 'Restored' palette so multi-device operators see explicit confirmation that their scope follows them across machines, not just across sessions. Distinct pulsing-dot animation + tooltip copy + aria-label so screen-reader users get the same distinction. Closes the visibility gap on the rev-135 sync path: until rev 135 the cross-device sync would have happened invisibly to the operator โ rev 135 makes it explicit + ambient + tooltipped, the strongest possible 'this is portable' trust signal without screaming.
- Partial-merge patch shape on dashboardPrefs.taskCommentFilters โ single-thread updates don't clobber other tasksUntil rev 135 the rev-78 dashboardPrefs PUT endpoint did a top-level shallow merge (`{ ...current, ...patch }`), which would have made a per-task filter PUT clobber every other task's stored scope. Rev 135's setDashboardPrefs special-cases taskCommentFilters: an `'taskCommentFilters' in patch` check triggers entry-by-entry merge against the existing server map. Empty-scope entries (no q + no author + reactions=false) are deleted from the merged map (so a 'just got cleared' client write removes the server-side ghost rather than leaving a stale entry); empty-object patch (`{}`) clears the entire map (used by the rev-84 DELETE reset-to-defaults path which now also resets taskCommentFilters); non-empty entries upsert. The merged map is then validated entry-by-entry (a tampered client can't poison the row) and capped at 30 entries with LRU eviction by `at` timestamp ascending so the *oldest* untouched entries fall out first when the cap is reached. Closes the partial-update semantic gap that would have made server-side sync of any high-cardinality dictionary unsafe.
- OpenAPI 3.1 typed coverage on the rev-135 taskCommentFilters field โ 58th unbroken cadence revCloses the typed-contract gap on the rev-135 dashboardPrefs primitive in the same cycle it ships. The OpenAPI spec's `/workspace/dashboard-prefs` GET response shape + PUT request body gain a typed `taskCommentFilters` field with the per-entry shape (q, author nullable, reactions, at timestamp) + the partial-merge semantic + LRU cap documented inline. The rev-78 cadence pattern (every dashboardPrefs field gets typed in the OpenAPI spec in the same cycle it ships) reaches its 58th unbroken rev with rev 135. MCP-host code generators reading the spec see a typed contract for the cross-device sync of per-thread filter scope immediately. The OpenAPI spec changelog header gains a rev-135 block explaining the multi-device sync pattern + how the field closes the named rev-134 candidate. Pairs with rev-127 (cost-panel order multi-device sync) + rev-82 (panel order) + rev-83 (active-work sort) + rev-84 (cost-panel column visibility) + rev-78 (panel collapse) as the seven-axis dashboardPrefs sync cluster โ every operator-tunable layer on the dashboard now follows them across devices.