Commit Graph

2344 Commits

Author SHA1 Message Date
Nicolas Formenton c75d835555 feat(desktop): mark a session as unread/read with a persisted watermark 2026-08-15 01:21:40 -07:00
OpenClaw Agent 71e8c35285 fix(desktop): clear unread for the whole conversation family, don't re-light read sessions
The sidebar lights the finished-unread dot for every alias of a
conversation lineage (branch children + compression root), but reading
a session cleared only the exact row id — a branched/compressed
conversation kept dots lit on sibling rows no matter how often they
were opened. And any settled completion re-lit the dot even when the
user had already viewed the session since it finished.

- setSelectedStoredSessionId now clears unread for the whole family via
  lineageAliases, not just the selected id
- handleTransition only re-arms unread when the completion settles
  strictly after the user's last read of that session (new last-read
  baseline), so an already-viewed completion never re-lights
- openSession marks read at the very top, before any focus
  short-circuit, so re-clicking an already-visible session clears its
  dot (the original gap the sidebar click could not reach)

4635 desktop tests pass (incl. new family-clear, read-baseline, and
openSession-short-circuit cases); tsc typecheck clean.
2026-08-15 01:21:40 -07:00
gitong 1abeddacc1 fix(desktop): clear unread dot on tile open + add mark-as-read actions
The green 'finished-unread' dot only cleared when a session was opened via
main-thread resume (setSelectedStoredSessionId). Opening a session in a
tab/tile (middle-click / Cmd-click / tile strip) never cleared it, so the
dot stayed while the user was actively reading the session in a tile.

With a remote hermes serve backend the effect is amplified: session.info
transitions for every backend session (CLI/cron/kanban) mark unread in the
desktop client, so unread dots accumulate from sessions the user never
opened.

Changes:
- openSessionTile now calls markSessionRead(), so tile/tab open marks the
  session read (same as main-thread resume)
- new markSessionRead / markAllSessionsRead helpers in store/session,
  reused by setSelectedStoredSessionId
- 'Mark as read' per-row action in the session context menu (shown only
  while the row is unread)
- 'Mark all as read' header action in the recents sidebar (shown only
  when unread sessions exist)
- i18n: markRead (row scope), markAllRead (sidebar scope) in en/zh + types
2026-08-15 01:21:40 -07:00
Zeus-Deus 26b4315e09 fix(desktop): profile-scope persisted unread and stop cold-boot storage clobber
Address review on the persisted unread dots, plus a latent data-loss bug
in the shared persistence helper that the restart e2e exposed.

Review findings:

- Session ids are caller-supplied and each profile backend is its own
  namespace, while the desktop's lists routinely mix profiles (cron and
  messaging slices are always cross-profile; recents are too in
  all-profiles mode). Both persisted records are now bucketed per
  profile - nested records keyed by the ROW's own profile
  (normalizeProfileKey, absent -> default), never the live gateway's,
  except the live busy->idle edge with no loaded row, which can only
  come from the active gateway. Same-id sessions in different profiles
  no longer share watermarks or markers.
- Markers are now bounded (200 per profile, oldest evicted) and cleaned
  up when a session leaves the user's world: forgetSessionUnread() is
  wired into removeSession, archiveSession, and the settings
  permanent-delete path (which bypasses the other two).

Cold-boot clobber (found by the restart e2e after the refactor):

- persistentAtom wrote its value back to storage immediately at
  creation. On a cold boot the bundle can evaluate against a storage
  snapshot that has not caught up yet, so that echo overwrote real
  records with the fallback. Creation is now read-only; only actual
  changes persist. Regression-tested in persisted.test.ts.
- The read side of the same race is handled in session-unread.ts: the
  first list arrival re-reads both records from storage (readable by
  then) and merges them under the in-memory state, so a boot that
  seeded empty atoms adopts the disk state instead of re-seeding every
  row and burying the unread gap. Unread listeners are also disabled in
  secondary windows - their partial list view must not write the
  primary's whole-record state (same isolation rule as session tiles).

Tests: cross-profile same-id regression, live-edge profile fallback,
forgetSessionUnread cleanup, marker cap, persistentAtom creation
read-only; the restart e2e passes again end to end.
2026-08-15 01:21:40 -07:00
Zeus-Deus d2c2b0b6d4 fix(desktop): persist sidebar unread dots across app restarts
The green "finished — unread" session dot lived only in the transient
$unreadFinishedSessionIds atom, written by a live busy->idle edge the
renderer had to witness. Closing and reopening the app grayed out every
dot, and a session that finished while the app was closed could never
be flagged at all.

Add a persisted layer (session-unread.ts), ported from the webui's
proven design:

- Seen watermarks (hermes.desktop.sessionSeenCounts): the message_count
  last acknowledged per session, keyed by the durable lineage id (same
  rule as session colors). A row whose live count exceeds its watermark
  paints unread on every list refresh - this reconstructs dots after a
  restart AND surfaces sessions that finished while the app was closed.
  First sight of an unknown session seeds the watermark so a fresh
  install doesn't light up every row.
- Explicit finish markers (hermes.desktop.unreadFinishedSessions): the
  live edge, persisted, covering the gap before the sidebar list
  refreshes its counts.

Opening a session acks both; the selected session's watermark tracks
its live count so on-screen activity never reads as unread. Chat and
cron rows get full watermark treatment; messaging rows keep explicit
markers only, so inbound messages don't paint false completion dots.
Profile switches keep persisted markers (keyed by durable id) and only
wipe the transient paint layer, so a round-trip repaints them.

Covered by store unit tests and an e2e spec that boots the app three
times: dot appears on a background finish, survives a restart, clears
on open, and stays cleared after another restart.
2026-08-15 01:21:40 -07:00
Jefftree a5b50437e4 fix(desktop): key the completed-unread dot on the focused session, not the selected one 2026-08-15 01:21:40 -07:00
hermes-seaeye[bot] 77be513de1 fmt(js): npm run fix on merge (#86813)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 08:13:01 +00:00
Teknium 77fcc2ea31 feat(display): honor display.timestamps across desktop transcript and TUI
One config key everywhere (#41531): the same display.timestamps that stamps
[HH:MM] on classic-CLI labels now gates the desktop transcript's timeline
timestamps and renders dim [HH:MM] labels on TUI user/assistant rows.

- desktop: $displayTimestamps store fed from config.yaml via
  use-hermes-config; TimelineTimestamp renders nothing while the key is off
  (the default). Hover tooltips with the exact time stay ungated (#70450).
- TUI: tui_gateway forwards each persisted row's timestamp in the display
  projection; toTranscriptMessages threads it as Msg.createdAt; live rows
  are stamped at append (the #82840 rule); MessageLine shows a dim [HH:MM]
  above user/assistant rows when display.timestamps is on.
- No new config keys, no HERMES_* env vars; display-only, prompt-cache safe.
2026-08-15 01:04:19 -07:00
liguoyu c9d0cbaba3 fix(desktop): show exact timestamps in tooltips (#70450) 2026-08-15 01:04:19 -07:00
Nick Morales 5f2a15a669 fix(desktop): separate system timestamp text 2026-08-15 01:04:19 -07:00
Nick Morales a896322b46 feat(desktop): show detailed transcript timestamps 2026-08-15 01:04:19 -07:00
Teknium aaaea589f6 test+style: align session.new binding pin with #76185 and eslint --fix 2026-08-15 01:04:06 -07:00
Teknium a9361cfaae fix(desktop): add zh locale strings for the Disable F12 toggle
zh.ts is typed strictly against Translations, so the new
disableF12Title/Desc keys must exist there (other locales fall back via
defineLocale).
2026-08-15 01:04:06 -07:00
Teknium dacb99d5b8 fix(desktop): keep the deliberate mod+N chord for session.new
#76185 removed both defaults; only the bare shift+n chord hijacks normal
typing (uppercase N / IME input outside an input field). Cmd/Ctrl+N is a
deliberate two-key chord that matches every browser and chat app, so it
stays. Follow-up narrowing of the salvaged fix.
2026-08-15 01:04:06 -07:00
kas-cor 3b5d5e48d1 fix: address review feedback on Disable F12 DevTools PR
- Add disableF12Title/disableF12Desc to i18n/types.ts contract
- Use focused BrowserWindow from menu click callback
- Persist and restore disable-f12 from main process (cold-launch)
2026-08-15 01:04:06 -07:00
Hermes Agent d4fa3f7352 feat(desktop): add option to disable F12 DevTools shortcut
- Replace built-in menu role 'toggleDevTools' (which had F12 accelerator)
  with explicit menu item using Ctrl+Shift+I / Cmd+Opt+I only
- Add f12Blocked flag in main process, controllable via IPC
- Add 'Disable F12 DevTools' toggle in Settings → Advanced
- F12 still opens DevTools by default; toggle blocks it
- Ctrl+Shift+I (or Cmd+Opt+I on Mac) always works regardless
2026-08-15 01:04:06 -07:00
Brian Sweatt 89f9375bc0 feat(desktop): archive the current session via hotkey and ⌥+⇧-click
Adds a rebindable 'session.archive' keybind action (shipped unbound, like
session.togglePin) plus an ⌥+⇧-click gesture on sidebar session rows,
extracted into a pure, unit-tested click resolver so modifier precedence
(⌥⇧ archive vs ⇧ pin vs ⌘/⌃⇧ new window) stays correct.

Salvaged from #59759. Closes #59308.
2026-08-15 01:04:06 -07:00
273-B_L0 34f484542e fix(desktop): show platform Kanban modifier 2026-08-15 01:04:06 -07:00
273-B_L0 f61fb52f94 fix(desktop): show platform layout modifier 2026-08-15 01:04:06 -07:00
273-B_L0 a590321de2 fix(desktop): show platform commit shortcut 2026-08-15 01:04:06 -07:00
Vladimir Rogozhin Assistant 101dfd782d Add PageUp and PageDown keybind support 2026-08-15 01:04:06 -07:00
BAS Lam 13acf7759e fix(desktop): remove session.new keybind hijacking typed N keys
The session.new default keybindings included 'mod+n' and 'shift+n',
which fired when users typed uppercase N (Shift+N) or accidentally
pressed Ctrl+N while using an IME to type Chinese — silently creating
new sessions mid-conversation.

Remove both combos so New Session is only reachable via the sidebar
button. Ctrl+Shift+N (session.newWindow) is unaffected.
2026-08-15 01:04:06 -07:00
Teknium 1c9de87ffb fix: drop duplicate composingRef declaration from rebase weave 2026-08-15 01:03:57 -07:00
Teknium 89df391580 fix(desktop): keybinds skip IME composition keys; pin draft survival across Settings navigation
- use-keybinds: bail out of the global keydown dispatcher while an IME
  composition is active. Windows Chinese IMEs use Ctrl+, as their
  punctuation toggle, which also matched nav.settings and navigated away
  mid-word — destroying the unsent composer draft (#41079).
- use-composer-draft.test: regression-pin the unmount-stash/remount-restore
  contract so route navigation (Settings) can never again drop an unsent
  draft with the React tree.
- use-composer-actions.test: the bounded-preview pipeline keys attachments
  to the durable path and resolves thumbnails asynchronously; the dropped-
  screenshot test now asserts the durable-path contract instead of the
  retired full-res previewUrl field.
2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d0642e61e5 fix(desktop): preserve legacy attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz e2a2fba4aa fix(desktop): preserve in-flight attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 462e7d8e6b fix(desktop): preserve tile attachment ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 193c199b23 fix(desktop): make image attachment updates occurrence-safe 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 7d5e9757ec fix(desktop): preserve image preview ownership across drafts 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 6dfe63e1e2 fix(desktop): preserve image attachment occurrence ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 5e09b4008b fix(desktop): serialize composer image previews 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d345f0831f fix(desktop): bound multi-image thumbnail raster cost 2026-08-15 01:03:57 -07:00
David Metcalfe 485e1e8f69 fix(desktop): keep full-res previewUrl separate from downscaled thumbnail
Addresses review feedback on #68744: downscaling inside
attachmentPreviewDataUrl made previewUrl the 2048px PNG, but current main
(d8cb73b4ab) feeds that field to ImageLightbox and useImageDownload, so
large attachments would open and download at reduced resolution.

- attachmentPreviewDataUrl returns the full-resolution data URL again
- ComposerAttachment gains thumbnailUrl?: string (store/composer.ts)
- attachImagePath stores previewUrl (full-res) + thumbnailUrl (downscaled)
- Attachment pill renders thumbnailUrl ?? previewUrl; lightbox/download
  keep the full-res previewUrl
- optimisticAttachmentRef prefers thumbnailUrl for the in-flight bubble
  display ref (same main-thread decode freeze at send time)
- Attachment-level regression test in use-composer-actions.test.ts:
  full-res previewUrl preserved while thumbnailUrl is a separate
  downscaled value (4000x3000 -> 2048x1536)
2026-08-15 01:03:57 -07:00
David Metcalfe 24fc613318 fix(desktop): downscale large images in composer preview to prevent UI freeze 2026-08-15 01:03:57 -07:00
Johnny 0d447712a0 fix(desktop): keep composer stable across compression 2026-08-15 01:03:57 -07:00
Gille 224530211e fix(desktop): let composer status titles use row width 2026-08-15 01:03:57 -07:00
xrwang8 47970a9e4c fix(desktop): clear edit-composer submitting latch after 200ms cooldown
- Add submitting state + IME composition guard to prevent double-submit
- submitEdit() sets latch, then clears after 200ms timeout
- handleKeyDown() guards on composing so IME Enter doesn't submit
- Shift+Enter inserts newline without submitting
- Test validates Enter calls onEdit, latch clears for second session

Fixes #70771

Signed-off-by: xrwang8 <xrwang8@gmail.com>
2026-08-15 01:03:57 -07:00
mollusk 652ae4877a fix(desktop): let the edit composer scroll long prompts
The inline edit composer caps height at max-h-48 but had no overflow
rule, so long prompts were clipped with no way to reach the tail.
Add overflow-y-auto to match the main composer editor.
2026-08-15 01:03:57 -07:00
Lester Liang 49fbca1880 fix(desktop): prevent accidental composer popout 2026-08-15 01:03:57 -07:00
Denis 354abd7c57 fix(desktop): keep empty composer from collapsing
Fixes #68134\nFixes #68095
2026-08-15 01:03:57 -07:00
infinitycrew39 2a82c6ffbc style(desktop): align empty-state width with full chat column 2026-08-15 01:03:57 -07:00
infinitycrew39 006a4a8873 fix(desktop): restore full-width chat composer column 2026-08-15 01:03:57 -07:00
hermes-seaeye[bot] af585de28e fmt(js): npm run fix on merge (#86801)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 07:46:57 +00:00
Paul BlackSwan d5a865882a fix(desktop): save remote gateway files natively 2026-08-15 00:37:00 -07:00
DannyFengTianYu 07b9090259 fix(desktop): preserve complete history when branching
Read the durable display transcript when creating a branch instead of copying the compacted model projection. Hydrate the Desktop branch boundary from persisted history, avoid stale whole-chat counts, and seed the new tile from the backend snapshot. Add regression coverage for compacted histories, visible-message counts, selected prefixes, and hydration races.

(cherry picked from commit c3d2d759ae104395adde215b2e293ccf8e895684)
2026-08-15 00:35:40 -07:00
mj 73e9b1a124 docs(desktop): align getGateway docstring with $gateway atom subscription
Residual hunk from PR #84832's picker-rebind commit; the functional
surfaces.tsx change landed on main via #86250.

(cherry picked from commit 990936d6f6e752ddb474787c1f7fdc3ad4f4dc60, docstring hunk only)
2026-08-15 00:35:28 -07:00
mj 20e5d78f54 fix(desktop): recover failed gateway catalogs
(cherry picked from commit ef1d2cc5e541f37fbd12b41f686a4c18a9b85320)
2026-08-15 00:35:28 -07:00
mj 1ef7a0bdda fix(desktop): recover empty model catalogs
(cherry picked from commit 72a06b9e0b392c494be011dc74ccf948ca693e3f)
2026-08-15 00:35:28 -07:00
Teknium ddf6456794 test: stub virtualizer.measure in virtual-session-list mock (density invalidation call) 2026-08-15 00:34:57 -07:00
Teknium 29c28136fe style(desktop): prettier/eslint pass on salvaged files 2026-08-15 00:34:57 -07:00