* 🐛 Require thread access when marking threads as read
mark-all-threads-as-read took any list of thread ids and wrote the read
status of the caller on each of them, without checking that the caller
can access the file that owns the thread. It was the only comment
mutation that did not check permissions, and it took the ids as an
unbounded array, so a single request could keep a database connection
and a transaction open for a long time.
The rpc now resolves the threads in one query and checks the comment
permissions of every distinct file, answering not-found when a thread
does not exist or is not accessible. Both cases answer the same error
on purpose, so the response cannot be used to tell existing threads
apart.
The status rows are now written with a single insert, keeping the
modified-at date one second in the future that makes the thread count
as read. Repeated ids are deduped, the command returns nil again like
the sibling commands with no result, and the schema limits the number
of threads per call to 500 while the dashboard sends the ids in batches
of that size.
Tests cover access rejection, unknown threads, mixed batches that write
nothing, repeated ids, empty lists, marking twice, and the frontend
batch split.
Relates to GHSA-j4vc-cmf6-jhr7
AI-assisted-by: minimax-m3.1-flash
* 🐛 Refresh the unread list when a batch fails
The dashboard sends the thread ids in batches of 500 and each batch
commits on the server. A failure in a later batch left the unread list
stale: the re-read only ran when every batch succeeded, so the threads
already marked as read stayed visible as unread.
The re-read now runs in both paths. On failure the event is emitted
before the error is propagated, so the error toast still shows and the
success toast stays limited to a full success.
The backend also answers an empty list without opening a transaction,
since there is nothing to check and nothing to write.
Relates to GHSA-j4vc-cmf6-jhr7
AI-assisted-by: deepseek-v4.1-flash
* 🔥 Remove unused comment profiles fan-out in dashboard
The dashboard event retrieve-unread-comment-threads issued one
get-profiles-for-file-comments call per distinct file id and stored
the result in :current-team-comments-users, a state key no one
reads. The dashboard renders each thread author from the thread
payload via get-owner, so the fan-out was N+1 dead work.
Drop the per-file branch and the state write; the event now issues
a single get-unread-comment-threads call.
AI-assisted-by: deepseek-flash
* 🔥 Remove unused file comments users event and state
fetch-file-comments-users had no callers and passed a :team-id
where get-profiles-for-file-comments requires :file-id. Its only
effect was writing :file-comments-users, a viewer state key no
one reads.
Remove the event and the unused state key. The viewer still
loads comment profiles through fetch-profiles.
AI-assisted-by: deepseek-flash
Store the hide-resolved filter in user storage and restore it when
entering the workspace or viewer, consistent with canvas comment
visibility from #10239. Match the comments filter separator styling to
the main menu and add the missing mentions option in the viewer
dropdown.
Closes#10686
Signed-off-by: Andres Gonzalez <andres.gonzalez79@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This upgrade also includes complete elimination of use spec
from the backend codebase, completing the long running migration
to fully use malli for validation and decoding.