Skip to content

feat: batch select and delete sessions in sidebar#835

Merged
wesm merged 10 commits into
kenn-io:mainfrom
huaiyuWangh:feat/batch-delete-sessions
Jun 25, 2026
Merged

feat: batch select and delete sessions in sidebar#835
wesm merged 10 commits into
kenn-io:mainfrom
huaiyuWangh:feat/batch-delete-sessions

Conversation

@huaiyuWangh

@huaiyuWangh huaiyuWangh commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Add multi-select batch delete functionality to the session sidebar, allowing users to select and delete multiple sessions at once instead of deleting them one by one.

Ref #281

Changes

Backend:

  • New SoftDeleteSessions(ids []string) DB method for batch soft-delete in a single transaction (batches of 500)
  • New POST /api/v1/sessions/batch-delete endpoint accepting { session_ids: [...] }, following the existing bulk-star pattern
  • Read-only stubs added for PG and DuckDB stores
  • Store interface updated with SoftDeleteSessions

Frontend:

  • Multi-select toggle button in the session list header (checkmark icon)
  • Per-item checkboxes when select mode is active; clicking items toggles selection instead of navigation
  • Batch toolbar showing selection count with Delete and Cancel buttons, plus All/Clear for select-all
  • batchDeleteSessions(ids) store method calls the new endpoint, updates local state, tracks undo toast entries, and exits select mode automatically

Tests:

  • TestSoftDeleteSessions covering normal batch, pre-deleted skip, empty input, and non-existent IDs

@huaiyuWangh huaiyuWangh force-pushed the feat/batch-delete-sessions branch from c872b32 to d170f5e Compare June 24, 2026 11:21
@roborev-ci

roborev-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (d170f5e)

Summary verdict: One medium-impact selection bug should be fixed before merging.

Medium

  • frontend/src/lib/components/sidebar/SessionList.svelte:318
    The “Select all visible” set is built from every session in groups, not from the currently displayed/selectable rows. In collapsed group or continuation views, this can select hidden child sessions with no visible checkbox, and the batch delete button will soft-delete them.

    Fix: Build the IDs from the visible displayItems rows, or change the UI to explicitly represent selecting all loaded/filter-matching sessions before deletion.


Panel: ci_default_security | Synthesis: codex, 6s | Members: codex_default (codex/default, done, 3m26s), codex_security (codex/security, done, 54s) | Total: 4m26s

@wesm

wesm commented Jun 24, 2026

Copy link
Copy Markdown
Member

looking at this

huaiyuWangh and others added 3 commits June 24, 2026 15:20
Add SoftDeleteSessions(ids []string) to batch-soft-delete multiple
sessions in a single transaction. Add POST /sessions/batch-delete
HTTP endpoint following the existing bulk-star pattern. Includes
PG and DuckDB read-only stubs.
Add multi-select mode toggle to the session list header. When active,
checkboxes appear on each session item, a toolbar shows selection count
with Delete and Cancel buttons, and clicking items toggles selection
instead of navigation. Calls the new POST /sessions/batch-delete endpoint
to soft-delete all selected sessions at once.
The sidebar batch toolbar promised to select visible sessions, but it built that set from grouped session data. Collapsed continuation chains and collapsed group sections kept hidden child sessions in that data, so selecting all could delete rows that had no visible checkbox.

Deriving the selection target from rendered display items keeps the toolbar aligned with the rows the user can actually see and interact with.
@wesm wesm force-pushed the feat/batch-delete-sessions branch from d170f5e to 5bbb58f Compare June 24, 2026 20:31
@roborev-ci

roborev-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (5bbb58f)

Summary verdict: medium-risk batch interaction regressions remain; no security issues were found.

Medium

  • frontend/src/lib/components/sidebar/SessionItem.svelte:231
    In multi-select mode, clicking the session label/link still calls sessions.selectSession(session.id) instead of toggling selection. Because the row click handler ignores clicks inside anchors, most of the row does not behave as selectable.
    Fix: In handleSessionClick, branch on selectMode; prevent default and call sessions.toggleSelection(session.id) when select mode is active.

  • frontend/src/lib/stores/sessions.svelte.ts:1108
    Batch delete pushes one undo entry per deleted session, but the existing undo toast restores only the last entry. A user deleting multiple sessions and clicking Undo once only restores one session, leaving the rest trashed despite the action being presented as a single batch delete.
    Fix: Make undo batch-aware by storing the deleted IDs as one batch and restoring all of them, or update the toast/action so it clearly restores one session at a time.


Panel: ci_default_security | Synthesis: codex, 10s | Members: codex_default (codex/default, done, 4m51s), codex_security (codex/security, done, 51s) | Total: 5m52s

wesm added 3 commits June 24, 2026 16:34
Multi-select mode needs every visible row surface to toggle selection, including the session label link. Leaving the link on the normal navigation path made most of the row ignore the batch interaction model.

Batch deletes are presented as one user action, so the undo state now records the deleted IDs as one batch and the toast restores that batch together instead of restoring only the last deleted session.
Batch undo entries can be partially acted on from the trash screen or partially restored from the undo toast. Dropping the whole batch after one ID changed made the remaining IDs unrecoverable from the toast, and aborting on the first restore error left already-restored IDs at the front of the retry path.

Keep remaining IDs in each batch until the batch is empty, and have batch undo attempt every restore before retaining only failed IDs for retry.
A batch undo can fail after the original delete toast timer has nearly expired. Keeping that timer alive while restore attempts run can remove the retry entry before failed IDs are retained, leaving the user with no retry path.

Clear the old timer when batch undo starts and create a fresh undo timer whenever failed IDs remain for retry.
@roborev-ci

roborev-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (b70ed3d)

Verdict: One medium-risk batch delete behavior issue should be fixed before merge.

Medium

  • frontend/src/lib/components/sidebar/SessionList.svelte:338: Batch delete submits every sessions.selectedIds entry, but selection is never pruned when rows become hidden by filtering, grouping collapse, or sidebar reloads. This can delete sessions that are no longer rendered or visibly checked.

    Fix: Clear or prune selection when the rendered session ID set changes, or intersect selectedIds with allVisibleSessionIds before calling batchDeleteSessions.


Panel: ci_default_security | Synthesis: codex, 9s | Members: codex_default (codex/default, done, 7m50s), codex_security (codex/security, done, 1m40s) | Total: 9m39s

Multi-select state can outlive the rows that are currently rendered after filters, grouping changes, or sidebar reloads. Submitting the raw selected ID set could delete sessions that no longer have a visible checked row.

Drive the toolbar count, disabled state, and delete payload from selected IDs that are still in the rendered sidebar list. Also remove the hard-coded white foreground on the batch delete button so the component satisfies the frontend source guard.
@roborev-ci

roborev-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (80aafa1)

Medium: Batch delete sidebar reconciliation can corrupt counts/state at frontend/src/lib/stores/sessions.svelte.ts:1120. The local update removes only selected IDs and decrements total for every removed row, but the sidebar index includes child rows while total represents root groups. Deleting a child can undercount totals; deleting a parent without hidden descendants can leave children promoted locally even though the server sidebar would exclude them.

Suggested fix: reload the sidebar index after batch delete, or reconcile using the same root/descendant semantics as the backend: remove descendants of deleted parents and decrement total only for deleted root groups.


Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 5m52s), codex_security (codex/security, done, 1m8s) | Total: 7m7s

wesm added 2 commits June 24, 2026 20:12
The sidebar index can contain child rows while its total represents root groups, so optimistic row removal cannot safely update total or descendant visibility. Deleting a child could decrement the root-group total, and deleting a parent could leave locally promoted descendants that the backend sidebar would no longer return.

Reload the sidebar index after the batch delete completes so sessions and totals follow the backend source of truth.
Batch delete is a mutation, so its refresh cannot safely join a same-signature sidebar load that started before the delete. Reusing that stale promise can republish pre-delete rows and totals after the operation resolves.

Bypassing load coalescing for this post-delete refresh lets the existing abort and version guards keep the pre-delete request from publishing stale state.
@roborev-ci

roborev-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (f436d73)

Medium severity issue found; no Critical or High findings.

Medium

  • frontend/src/lib/stores/sessions.svelte.ts:1156 - Batch undo can reuse the in-flight post-delete sidebar reload because restoreRecentlyDeleted() calls load() without force. If undo is clicked while batchDeleteSessions() is still awaiting its forced reload, the restore path can await the pre-restore request and leave restored sessions hidden until a later refresh.
    • Suggested fix: force a fresh sidebar load after restoring the batch, for example await this.load({ force: true }), or delay exposing undo until the delete reload completes.

Panel: ci_default_security | Synthesis: codex, 12s | Members: codex_default (codex/default, done, 4m42s), codex_security (codex/security, done, 1m25s) | Total: 6m19s

Batch undo is another mutation-triggered refresh, so it cannot safely await a sidebar load that started before the restore. If undo races the post-delete reload, reusing that promise can leave restored sessions hidden until a later refresh.

Force the post-restore sidebar reload so the existing abort and version guards keep the older delete reload from publishing stale state.
@roborev-ci

roborev-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (2b94dcd)

No issues found.


Panel: ci_default_security | Synthesis: codex | Members: codex_default (codex/default, done, 3m51s), codex_security (codex/security, done, 1m17s) | Total: 5m8s

@wesm

wesm commented Jun 25, 2026

Copy link
Copy Markdown
Member

thank you!

@wesm wesm merged commit 10706c1 into kenn-io:main Jun 25, 2026
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants