"..." on the session title should provide a menu of quick actions on sessions #1

Closed
opened 2026-07-25 05:31:53 +00:00 by hugo · 1 comment
Owner

required actions:

  • rename session
  • retitle session via AI (send the context to request a short title from the AI - this must be a one-shot communication that does not leave behind a session but only gets the summarized title outside the current session. this one-shot mechanism is reused in #4 )
  • pin the session (has it show up first in the projects session list - essentially pinned sessions, then normal sessions)
  • archive the session (moves it to the bottom of the list of sessions on a project and shows them only when a user requests to see archives sessions - so show pinned sessions, then normal sessions, then the text "archived sessiosn" and if the user clicks it, show the archived sessions. show that text only if there are archived sessions)
  • unarchive session (removes the archived flag, only visible if a session is archived)
  • unpin a session (removes from pinned section, only visible if a session is pinned)
required actions: - rename session - retitle session via AI (send the context to request a short title from the AI - this must be a one-shot communication that does not leave behind a session but only gets the summarized title outside the current session. this one-shot mechanism is reused in #4 ) - pin the session (has it show up first in the projects session list - essentially pinned sessions, then normal sessions) - archive the session (moves it to the bottom of the list of sessions on a project and shows them only when a user requests to see archives sessions - so show pinned sessions, then normal sessions, then the text "archived sessiosn" and if the user clicks it, show the archived sessions. show that text only if there are archived sessions) - unarchive session (removes the archived flag, only visible if a session is archived) - unpin a session (removes from pinned section, only visible if a session is pinned)
hugo added the enhancement label 2026-07-25 05:32:09 +00:00
Author
Owner

Implemented in 3508758.

All six actions are reachable from a ... button, which now exists on every session row in the sidebar and next to the title in the chat header. The menu opens as a sheet listing only the moves that apply to the session's current state.

Lifecycle is one enum, not two flags. SessionState { Normal, Pinned, Archived } lives in a single state TEXT column with a CHECK constraint (migration 20260725120000_add_session_state), following the existing reasoning_mode pattern. The Session.state field is private, so the typed accessor is the only way to read it and a pinned-and-archived session cannot be constructed or observed. This replaced an earlier two-boolean draft that needed an "archiving clears the pin" fixup in the DB layer — the fixup was the tell that these are states, not flags.

Per action:

  • Rename — dialog with the current title prefilled. It stays open if the write fails, so a rejected edit is not lost. Empty and whitespace-only titles are rejected in Database::rename_session.
  • Retitle via AI — see below.
  • Pin / Unpin — Pin is offered only from Normal, Unpin only from Pinned.
  • Archive / Unarchive — Unarchive is offered only from Archived; archiving is offered from both other states. Archiving a pinned session moves it out of the pinned group, which is now a consequence of the state transition rather than a separate cleanup step.

Sidebar ordering is pinned → normal → archived, sorted by SessionState::rank() with a stable sort so creation order is preserved within each group. The › Archived sessions (n) row renders only when a project actually has archived sessions, and expansion is tracked per project. Pinned sessions carry a small pin glyph so the group boundary is visible.

The one-shot mechanism (reused by #4). App::request_title loads the session's stored messages, appends a short title instruction, and runs the turn with reasoning_mode = Direct and a 48-token cap. It goes through a new CheckpointTarget::OneShot, which shares the transient content-addressed KV cache with the HTTP endpoint: no session row, no per-session checkpoint, nothing left behind but cache entries that were already shared infrastructure.

OneShot is a new variant rather than a reuse of Transient for one reason worth recording: Transient reports as WorkSource::Http in metrics, so reusing it would have logged every AI retitle as endpoint traffic in the Stats tab. OneShot handles KV identically but reports LocalChat. That distinction matters more once #4 lands and every new session triggers a title request.

The reply is reduced to a sidebar-sized line by session_title: first non-empty line, surrounding quotes and markdown stripped, truncated at 60 characters with an ellipsis. A reply that yields nothing usable surfaces an error instead of writing a blank title.

Tests — state exclusivity and rank ordering, title sanitizing including the truncation and empty-reply paths, and a database round-trip covering state ordering, rename validation, and the archive-a-pinned-session transition. 44 passing. cargo fmt --check, cargo clippy --all-targets --all-features -D warnings, make bundle and cargo test --all-features all clean.

Not verified visually. The app was not launched, so the sheet's proportions and the two new icons (pin.svg, archive.svg) have not been looked at on screen — worth a glance, particularly the pin glyph at 12px in the sidebar row.

Implemented in 3508758. All six actions are reachable from a `...` button, which now exists on every session row in the sidebar and next to the title in the chat header. The menu opens as a sheet listing only the moves that apply to the session's current state. **Lifecycle is one enum, not two flags.** `SessionState { Normal, Pinned, Archived }` lives in a single `state TEXT` column with a `CHECK` constraint (migration `20260725120000_add_session_state`), following the existing `reasoning_mode` pattern. The `Session.state` field is private, so the typed accessor is the only way to read it and a pinned-and-archived session cannot be constructed or observed. This replaced an earlier two-boolean draft that needed an "archiving clears the pin" fixup in the DB layer — the fixup was the tell that these are states, not flags. Per action: - **Rename** — dialog with the current title prefilled. It stays open if the write fails, so a rejected edit is not lost. Empty and whitespace-only titles are rejected in `Database::rename_session`. - **Retitle via AI** — see below. - **Pin / Unpin** — Pin is offered only from Normal, Unpin only from Pinned. - **Archive / Unarchive** — Unarchive is offered only from Archived; archiving is offered from both other states. Archiving a pinned session moves it out of the pinned group, which is now a consequence of the state transition rather than a separate cleanup step. **Sidebar ordering** is pinned → normal → archived, sorted by `SessionState::rank()` with a stable sort so creation order is preserved within each group. The `› Archived sessions (n)` row renders only when a project actually has archived sessions, and expansion is tracked per project. Pinned sessions carry a small pin glyph so the group boundary is visible. **The one-shot mechanism (reused by #4).** `App::request_title` loads the session's stored messages, appends a short title instruction, and runs the turn with `reasoning_mode = Direct` and a 48-token cap. It goes through a new `CheckpointTarget::OneShot`, which shares the transient content-addressed KV cache with the HTTP endpoint: no session row, no per-session checkpoint, nothing left behind but cache entries that were already shared infrastructure. `OneShot` is a new variant rather than a reuse of `Transient` for one reason worth recording: `Transient` reports as `WorkSource::Http` in metrics, so reusing it would have logged every AI retitle as endpoint traffic in the Stats tab. `OneShot` handles KV identically but reports `LocalChat`. That distinction matters more once #4 lands and every new session triggers a title request. The reply is reduced to a sidebar-sized line by `session_title`: first non-empty line, surrounding quotes and markdown stripped, truncated at 60 characters with an ellipsis. A reply that yields nothing usable surfaces an error instead of writing a blank title. **Tests** — state exclusivity and rank ordering, title sanitizing including the truncation and empty-reply paths, and a database round-trip covering state ordering, rename validation, and the archive-a-pinned-session transition. 44 passing. `cargo fmt --check`, `cargo clippy --all-targets --all-features -D warnings`, `make bundle` and `cargo test --all-features` all clean. **Not verified visually.** The app was not launched, so the sheet's proportions and the two new icons (`pin.svg`, `archive.svg`) have not been looked at on screen — worth a glance, particularly the pin glyph at 12px in the sidebar row.
hugo closed this issue 2026-07-25 10:16:39 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: hugo/DS4Server#1