1-line CSS: adds touch-action:manipulation + -webkit-tap-highlight-color:transparent to
button/.icon-btn/.send-btn/.approval-btn/[onclick] inside the touch/compact media block
(@media max-width:1399.98px), removing iOS Safari's ~300ms double-tap-zoom delay so taps
fire on first touch. Added a regression test pinning the rule.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* stage #4660 v2 (franksong2702): full-screen PWA mobile sidebar drawer — REBUILT on current master
REBUILD: the prior stage was cut before tonight's #4597/#4584/#4583 ships, so its 3-dot
diff falsely appeared to revert them (Codex CORE/SILENT flags). Rebuilt on current master
(authoritative head a035b1343): clean apply, tonight's fixes confirmed INTACT (_apply_sidebar_
state_db_overrides, bottomDistance, overflow-x:hidden all present). #4660's own change is
additive — full-screen drawer width, edge-guard, mobile close button, overlay->display:none;
does NOT touch the scroll/perf/mobile-pan lines. Nathan concept-greenlit + visual signed off.
71 mobile-layout+swipe tests pass.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
* fix#4660 edge-guard regression: defer preventDefault until horizontal intent + touch-action:pan-y; update 2 tests to assert corrected behavior
* fix#4660 edge-guard hit-test interception (Codex CORE): pointer-events:none + drop guard listener
Deeper fix than the prior preventDefault change: the #pwaSidebarEdgeGuard was a fixed
interactive element (z-index 198, no pointer-events:none) that intercepted hit-testing for
taps/vertical-scrolls starting in the left 24px strip (since .messages is the real scroller
under body:overflow:hidden). Now guard is pointer-events:none with NO dedicated touch
listener; the existing window-level CAPTURE handlers still catch the edge-swipe-to-open.
Removed the orphaned _onPwaSidebarEdgeGuardStart fn. Updated 2 tests to assert the new
non-interactive design. 71 mobile tests pass.
---------
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Re-staged from the contributor's rebased head (0cee92c9) which independently converged on
the same 5-test-file class-tolerance fixups I'd made — shipping the contributor's exact
version, not my private variant. Tags md/markdown/mdx fenced blocks with md-source-block
class in both renderers; CSS wraps them while code/diff/csv keep horizontal-scroll. 90
renderer-family tests pass.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
#4584 (fixes#4295): scroll re-pin requires deliberate downward intent (deltaY<-30 unpin
threshold, re-pin only if not user-unpinned OR bottomDistance<=80, reset counter) so reading
mid-stream isn't hijacked back to bottom. ui.js only + 5 scroll-regression tests (4 existing
assertions adjusted, all green; new test_issue4295_scroll_pin_reentry).
#4583 (fixes#4553): mobile transcript overflow-x:hidden + max-width:100% + word-break so wide
content can't pan sideways. style.css only + own test. Disjoint files. 32 scroll-family tests pass.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
#4644: close mobile sidebar drawer after a rail-click panel switch (was left open).
#4645: animate drawer open/close with transform:translateX instead of left offset.
Disjoint product files (panels.js vs style.css); both add tests to test_mobile_layout.py
in distinct functions (no conflict). Mobile-only, desktop unaffected. 60 mobile-layout
tests pass. Code byte-faithful from PR heads.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
UI enable/disable of installed manifest extensions via a bounded fail-safe
extension-overrides.json state file. Safe boundary: no manifest edits, no asset
fetches, no sidecar proxy, no extension-owned routes. /api/extensions/toggle is
auth+CSRF gated; anchored _EXTENSION_ID_RE (no path traversal); atomic UTF-8 write
under _EXTENSION_STATE_LOCK; 32KiB/512-id DoS bounds; disabled override suppresses
asset/sidecar injection end-to-end. Nathan greenlit the extensions-phase-2 concept.
Code byte-faithful from PR head 8769b4a3902e. 49 own tests pass.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* stage #4612 (santastabber): surface loopback sidecar diagnostics + CHANGELOG
Settings -> Extensions surfaces manifest-declared loopback sidecar companion services
(type:loopback, http(s) on 127.0.0.1/localhost/::1 only, sanitized origin+health_path)
as read-only health cards w/ a browser-side credentials-omitted health probe. Backend
hard-whitelists scheme + loopback host, rejects userinfo/path/query/traversal, rebuilds
output from parsed components (no echo of rejected input); frontend esc()'s every field,
whitelists badge status, fetch never reads body. Purely additive to GET /api/extensions/status.
Code applied byte-faithful from PR head 7080929f3727. 33 own tests pass.
Nathan concept-approved (loopback-companion direction).
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* fix#4612 Codex gate findings: reject encoded query/fragment in health_path + align CSP loopback allowlist
Codex SHIP-WITH-FIXES (2 reproduced SILENT findings):
1. _normalize_sidecar_health_path percent-decoded AFTER the raw query/fragment ban,
so /health%3Ftoken=abc -> ?token=abc survived into the probed URL. Now re-rejects
decoded ? and # + regression test (%3F/%23 skipped with sidecar_health_path_rejected).
2. The origin validator accepts https:// and [::1], but CSP connect-src only listed
http 127.0.0.1/localhost -> accepted https/IPv6 loopback sidecars would be CSP-blocked.
Aligned _CSP_CONNECT_BASE (added https v4/localhost + http(s) [::1]) + updated the 4
exact-string CSP tests + a regression test asserting all loopback forms are allowed.
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* Release v0.51.564 — Release TW (loopback sidecar diagnostics; #4612)
* fix(#4612): drop browser-invalid [::1]:* CSP source (browser-smoke fail)
The CSP-loopback-alignment fix added http(s)://[::1]:* to connect-src, but CSP
host-source grammar can't express a port wildcard on a bracketed IPv6 literal —
Chromium rejects '[::1]:*' as an invalid source, which browser-smoke flags as a
console error (CI fail). Keep the valid https://127.0.0.1:*/localhost:* additions;
drop the IPv6 wildcard entries. IPv6 [::1] sidecars are still accepted by the
validator (displayed in the card); their browser health probe surfaces as
'blocked' under CSP unless an operator adds a specific-port [::1]:<port> via
HERMES_WEBUI_CSP_CONNECT_EXTRA. Tests updated to match + assert [::1]:* absent.
---------
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* stage #4380 (santastabber): render selected context as quote cards + CHANGELOG
Evolves the shipped #2543 named-context-blocks: composer chips -> quote cards
(accent rail, editable label w/ rename, remove, clipped excerpt) + thread render
of labeled selections as <figure> cards. Label+quote esc()'d on every path
(adversarial harness 0 survivors). 3 new i18n keys in all 13 locales (parity green).
Code applied from PR head 2eefc11c02a7 (own 3-dot contribution, CHANGELOG re-stamped).
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* fix#4380 Codex gate finding: selection-only reply must enable the primary Send button
Codex CORE: #4380 moved selected context out of the textarea into _pendingSelections,
but _composerHasContent() only checked textarea+files -> getComposerPrimaryAction()
returned disabled for a selection-only reply (desktop Enter calls send() directly so
it worked, but click/tap/mobile users were blocked). Fix: expose
window._hasPendingSelections() from messages.js, fold it into _composerHasContent(),
and call updateSendBtn() from _renderSelectionChips() so add/remove/clear refreshes
the button. + regression test.
* Release v0.51.562 — Release TU (selected-context quote cards; #4380)
---------
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
The outline FAB was position:fixed at z-index 9999 and could stack on top of the
scroll-to-bottom button. Anchor it inside the messages shell (position:absolute,
z-index 12) so the two controls don't overlap.
Co-authored-by: Mohamed Habib <Habib1001-m@users.noreply.github.com>
- Hamburger button: 32x32 -> 44x44 tap target (iOS HIG minimum), SVG icon
20 -> 24px. flex-shrink:0 preserved; spacer keeps the title centered.
- Edge-swipe-to-open-sidebar: edge zone 28 -> 80px, trigger 72 -> 64px, max
vertical 48 -> 56px; removes the _isPwaStandalone() gate in
_onPwaSidebarSwipeStart so the gesture works in a normal mobile browser, not
only the installed PWA. Still _isDesktopWidth()-blocked, touch/pen-only,
interactive-target-excluded, and requires horizontal intent (dx>=64 &&
dx>|dy|*1.5), so scroll/tap won't trigger it.
Known tradeoff (Nathan-approved ship-as-is): in mobile BROWSER the 80px left
edge zone overlaps iOS Safari's back-swipe; degrades gracefully (PWA, the
primary mobile target, has no back gesture).
Verified live at 390x844: hamburger renders 44x44 + tap opens sidebar; a
left-edge swipe opens the sidebar in a non-PWA context. 66 existing mobile/
swipe/titlebar tests pass.
Co-authored-by: kaishi00 <kaishi00@users.noreply.github.com>
Paused/disabled cron jobs are partitioned out of the active list into a
collapsible 'Paused (N)' <details> section so they don't drown the active
jobs. _cronList stays the single source of truth — only the render is split,
so openCronDetail / _cronNewJobIds / detail-refresh are untouched. Reuses the
already-translated cron_status_paused label (zero new i18n keys). Collapsed
state persisted in localStorage.
Maintainer polish on absorb: wrapped both localStorage read/write in try/catch
(repo convention — a storage throw in private mode must not dump the cron
sidebar into the error banner) + added the CHANGELOG entry.
Co-authored-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
The inline PDF preview rendered only page 1, so multi-page documents
looked clipped. loadPdfInline now renders every page (capped at MAX_PAGES=20
for memory) sequentially into a scrollable .pdf-preview-body, shows the page
count in the header, and appends a "Showing the first N of M pages" notice
(pdf_truncated, 13 locales) when the PDF exceeds the cap.
Robustness: renderPage(i) attaches each canvas only AFTER its render
promise resolves, and a per-page failure is caught and skipped
(`.then(()=>renderPage(i+1)).catch(()=>renderPage(i+1))`) so one corrupt
page can't halt the preview or surface an unhandled rejection. XSS-safe
(esc(fname), t() with numeric args).
Co-authored-by: Gouranga satapathy <29521030+gourangasatapathyvit@users.noreply.github.com>
"Reply with selection" no longer appends the selected text straight into
the composer. It now adds a named chip ("Context N") to a row above the
message box (composerSelectionChips). Chips can be renamed (double-click,
esc()-escaped) or removed (x); multiple stack. On send(),
_flushSelectionBlocksToComposer() materializes pending blocks into the
composer as **Name:** + markdown-quoted text, then the normal send path
runs unchanged on composer.value.
Regression-safety:
- _flushSelectionBlocksToComposer early-returns when no blocks are pending,
so send() is byte-identical for anyone who never uses the feature.
- loadSession() AND newSession() both clear pending blocks before replacing
S.session, so chips never leak across a sidebar switch OR a New Chat
(the New Chat clear + its regression test were added per Codex review).
- chip name/text rendered via esc(); XSS-safe.
Verified live (headless CDP drive): add/render/remove/rename, flush
produces correct markdown, empty-flush no-op, session-switch clears,
injected <img onerror=...> chip-name escaped (not executed).
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Ships @rodboev's searchable-settings feature (#3850/#3936): a search box in
Settings that finds any field across all 5 sections with breadcrumbs +
click-to-navigate+highlight+clear. Rebased fresh onto master (post v0.51.470),
5 feature files byte-identical to PR head, re-stamped v0.51.471 (was v0.51.469,
collided with the shipped transcript-virtualization release).
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Builds on #4279's collapsible-group foundation and extends it into a complete
provider-group UX:
- Groups start COLLAPSED except the group that owns the selected model, so the
picker opens scannable instead of as a long flat list. Search auto-expands
matching groups (and searches hidden overflow models); clearing the search
restores the collapsed-except-selected view.
- 'Show more' restyled as a distinct '+ Show all N models' expander (muted
italic, indented, no model-id line) that reveals overflow IN PLACE — the group
stays open, other groups keep their state, and the view scrolls to the newly
revealed models instead of resetting to the top. Falls back to a full
re-render in minimal/headless DOM. Endpoint-error groups (#4253) and the
expanded group persist correctly across a search round-trip.
- Per-row provider chip suppressed for rows already under their own provider
heading (removes the repetitive 'provider on every row' noise); kept for
hoisted/search rows.
Verified end-to-end on desktop + mobile 390px (collapse/expand, search +
overflow-search + clear-reset + no-results, load-more in-place + stay-open +
scroll-to-new, endpoint-error visibility). #3691 driver tests updated for the
collapsible-group structure + de-noised chip. In-place-path CI coverage tracked
in #4331.
Co-authored-by: akrhin <akrhin@users.noreply.github.com>
* perf(chat): virtualize long message transcripts (#500)
* fix(chat): preserve virtual height cache on append (#500)
* fix(chat): bound virtual tail follow-up (#500)
* fix(chat): surface older history and bound virtual rerenders (#500)
* fix(chat): stabilize virtual history fallback (#500)
* fix(chat): preserve virtual compression anchors (#500)
* Keep virtualization regressions aligned with rebased sources (#500)
* fix(chat): preserve virtual heights across prepends (#500)
* test(update): isolate restart timing checks (#814)
* fix(chat): restore render contracts around virtual cache
* fix(chat): preserve legacy assistant anchor predicates
* fix(#3714): restore worklog-disclosure capture after HTML-cache early-return
Deep-review finding: the capture of _captureWorklogDetailDisclosureState was
moved before the HTML-cache fast-path return, failing the invariant test
test_render_rebuild_preserves_worklog_detail_disclosure_click_state (it must
not traverse the previous-session DOM when the cache path returns early).
Moved it back to just before inner.innerHTML='' (after the cache return).
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* fix(#3714): restore Transparent Stream rehydration in HTML-cache fast path
Full-suite gate caught test_issue3820_chat_activity_display_mode failure: the
virtualization rewrite of the renderMessages HTML-cache fast-path block dropped
the _rehydrateTransparentStreamDom(inner) call (master has it; PR had 2 of 3
occurrences). On a cache-restore session switch, Transparent Stream controls
lost their property-assigned handlers. Restored the call in the same position
master has it (right after _sessionHtmlCacheSid=sid).
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* fix(#3714): preserve live turn on scroll-rebuild even with zero streamed text
Opus ship-review residual (now fixed, not deferred): the live-turn re-attach
after a scroll-triggered virtualized rebuild was gated on _preservedLen>0, so a
turn showing only Activity/tool/worklog blocks with no streamed text yet (mid
tool-call) would not be preserved — scrolling up into history on a >80-row
transcript mid-stream could blink those live-only blocks for a frame. Broaden
the gate to also restore when the preserved turn has MORE structural blocks than
the rebuilt (lagging-S.messages) turn. Hoisted the _structuralCount helper above
the gate; whole-turn-vs-segment restore logic unchanged.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* chore(release): stamp v0.51.423 (Release OJ) for #500 transcript virtualization
---------
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
* feat(workspace): add Todos tab to workspace panel behind a setting (#2942)
* fix(workspace): use DOM attribute to check active tab across script scope
* Prefer the live todos snapshot in the workspace tab
* Fill the missing workspace Todos locale keys
* feat(workspace): add optional Todos tab to workspace panel (#3564)
Rebased rodboev's #3564 onto current master + CHANGELOG entry.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* fix(#3564): apply workspace Todos tab visibility on cold boot
Codex gate: boot.js set window._workspaceTodosTab from saved settings but
never called _applyWorkspaceTodosTabVisibility(), so a previously-enabled
Todos tab stayed hidden on a normal page reload (only re-appeared after
opening Settings). Call the visibility helper after assigning the flag in
both the settings-apply and the fallback/default paths.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* chore(release): stamp v0.51.422 (Release OI) for #3564
---------
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Combined light-UX release NW. Both rebased onto current master (were 96 and
247 behind; net 3-dot diffs applied, byte-identical to PR heads). Both reuse
existing renderers (chat image lightbox / chat CSV table renderer).
Review fix (#4025, Codex): the CSV table preview previously stripped the
workspace Edit affordance .csv had when it fell through to the code preview.
Restored: csv mode is editable, renderCsvPreviewContent caches raw content for
the edit textarea, and saving an edited CSV re-renders the table (not markdown).
Verified the full edit round-trip live + regression test.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Re-pushed by rodboev fixing both bounce findings (parent streaming-state
isolation + nested-fork batch-select) plus a select-handler selector fix.
On current master (0 behind); sessions.js byte-identical to PR head.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Functional (Codex+Opus trifecta findings):
- C1: restoreLiveTurnHtmlForSession now calls _rehydrateTransparentStreamDom (controls were dead after active-session live-turn restore).
- O-Bug1: per-turn echoed-thinking dedup in the transparent settled path (mirrors the compact seenReasons), fixes duplicate out-of-order Thinking rows.
- O-Bug2: tool completion preserves the row's open state + Full/Output tab.
- O-Edge: settled/reloaded done===false tools show 'Interrupted' (not a permanent Running shimmer); progress bar clears for Interrupted too.
Visual (trifecta CSS):
- V1: reset .thinking-card to flat/transparent (was the heaviest object in the stream).
- V2: failed=error color + left border, running/interrupted legible (was an invisible muted badge).
- V3: scoped !important reset beats the 6 per-skin .tool-card re-card rules.
- V4: inline key:value args (was a tall stacked form).
- V5/V7: output pre flattened to a quiet left-rail, max-height:none (one expanded zone, no nested scroll).
- V6: drop redundant tool/status pseudo-args from the detail body.
- V8: row fading gated to the live turn only + floor raised for WCAG; settled history stays full-opacity.
- V9: entrance animation scoped to #liveAssistantTurn (was replaying on every renderMessages).
- V10: larger mobile header tap targets + active detail-tab underline.
O-Bug3 (live thinking overwrite when no seq) intentionally deferred — documented follow-up; settled render is already correct and a live heuristic risks a streaming regression.
Adds an opt-in, default-off, desktop-only floating panel that lists the user's
questions in a conversation as a numbered jump-list (click to scroll + flash the
message). Gated behind a Settings → Preferences toggle (show_conversation_outline)
wired through the existing show_* preference boot/load/autosave/save paths.
Review follow-ups applied on absorb:
- Outline is now strictly chat-only: leaving the chat view (Settings, Tasks,
Insights, …) hides the toggle button AND closes the panel; returning to chat
restores the toggle (panel stays closed until re-opened). Implemented by gating
_outlineAllowed() on the active panel and re-evaluating via a MutationObserver on
the <main> showing-<panel> class (switchPanel is a global fn declaration that
can't be reliably wrapped from this script).
- Fixed a latent visual bug: #outlinePanelWrapper{display:flex} (id selector)
outranked the UA [hidden]{display:none}, so wrapper.hidden=true never actually
hid the panel — the × close button and auto-close had no visual effect. Added
#outlinePanelWrapper[hidden]{display:none;}.
- Regression tests for both.
Closes#2124.
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* feat(composer): add saved prompts library with per-profile storage (#2732)
* fix(composer): move saved-prompts popup out of .composer-left to preserve DOM test (#2732)
* fix(composer): correct ARIA roles, add server-side prompt limits (#2732)
* fix(composer): surface save-prompt errors instead of silent success toast (#2732)
* Release v0.51.338 — Release LB (saved prompts library, #3571)
Composer saved-prompts library (@rodboev): bookmark button → popup of saved
prompts; click to insert, save current input, delete. Persists to
$HERMES_HOME/webui/saved_prompts.json with server-side caps (8000 chars / 200).
Maintainer work (per Nathan): conditions were (a) verify it actually works and
(b) hide on mobile. Both met:
- Live-verified load/save/delete all persist through the UI.
- Added mobile-hide (#btnSavedPrompts,.saved-prompts-popup display:none in the
@media max-width:640px + 900px composer blocks). DOM-verified visible at 1280px,
vision-confirmed absent from the composer at 390px.
- Added missing Polish (pl) i18n for the 5 saved_prompts_* keys (PR had en+others
but not pl — failed locale-parity).
- Added tests/test_issue3571_saved_prompts.py (mobile-hide + caps + wiring guards).
Full suite green, ESLint/scope-undef CLEAN, Opus SHIP-safe (auth-gated, CSRF,
XSS-safe, sane caps), Codex SAFE-TO-SHIP.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
---------
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* fix(streaming): show new-message cue when preserving scroll position (#3545)
# Conflicts:
# static/ui.js
# tests/test_issue1690_scroll_completion.py
# tests/test_tars_scroll_reset_regressions.py
* i18n: add missing Polish (pl) translation for session_new_message keys
The PR added session_new_message / session_new_message_label to 12 of 13
locales; Polish was missing both, which fails the per-locale parity test.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* fix(streaming): keep forced follow path for pinned users in preserve-scroll branch
Codex CORE catch: the PR's preserve-scroll branch used
'if(_scrollPinned) scrollIfPinned()' which skips the synchronous bottom
write unless distance>500 and can have its delayed settles cancelled by the
DOM-rebuild scroll event — leaving a pinned reader a few lines above the
settled final response. Restore master's _followMessagesAfterDomReplace()
forced-scrollToBottom() path for pinned/near-bottom users; only genuinely
scrolled-up (unpinned, not near bottom) users restore their viewport and
get the new-message cue. Updated the 3 structure-pinning tests to assert the
corrected (safer) shape while preserving their behavioral intent.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* Release v0.51.334 — Release KX (new-message cue when scrolled up, #3631)
New-message cue on the jump-to-bottom button when the user has scrolled up
during a live turn (#3545/#3631, @rodboev). Deep-reviewed (Opus+Codex);
maintainer fixes during re-gate: (1) restored master's forced follow path
for pinned/near-bottom users (Codex CORE: scrollIfPinned could leave a pinned
reader short of the settled response) + updated 3 structure-pinning tests to
the corrected shape; (2) added missing Polish (pl) i18n keys (PR had 12/13).
Full suite 8308, ESLint/scope-undef CLEAN, Opus SHIP-safe, Codex SAFE-TO-SHIP.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
---------
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Hermes Agent <hermes-agent@nesquena-hermes.local>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* feat(streaming): collapse old interim progress notes after 3 visible (#2403)
* fix(streaming): delegated handler for interim-collapse toggle survives live-turn restore
The interim-collapse toggle attached its click listener via per-element
addEventListener at creation time. snapshotLiveTurnHtmlForSession /
restoreLiveTurnHtmlForSession rebuild the live turn via outerHTML/innerHTML
on session switch, which strips JS listeners — so a restored toggle was
visible but inert and the collapsed interim notes became permanently
unreachable for the rest of the turn.
Replace with a stateless document-level delegated click handler
(_interimCollapseDelegatedClick) that resolves the toggle via closest(),
reads state from the DOM (.interim-collapsed) + data-threshold, and works
on both freshly-created and innerHTML-restored toggles. Add 4 regression
tests pinning the delegated-handler contract.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
* Release v0.51.333 — Release KW (collapse old interim progress notes, #3574)
Collapse old interim progress notes after 3 visible during a live turn
(#3574, @rodboev). Maintainer fix during re-gate: replaced the per-element
toggle listener with a stateless document-level delegated handler so the
toggle survives the live-turn DOM restore (Codex caught: innerHTML rebuild
dropped the listener → collapsed notes unreachable). Full suite 8303,
ESLint/scope-undef CLEAN, Opus SHIP-safe, Codex SAFE-TO-SHIP after fix,
collapse + manual-expand-guard + restore-path delegated handler all live-verified.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
---------
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Hermes Agent <hermes-agent@nesquena-hermes.local>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Script cron jobs (no_agent) in the Tasks panel now show a script badge,
banner, script path + working directory, and 'Script output' run labels
instead of an empty Prompt card (#3589, @pamnard).
Self-rebased onto v0.51.331 (was CONFLICTING, CHANGELOG-only stale base).
Maintainer fix: added the 10 new cron i18n keys to Polish (pl), which the
PR had missed (12/13 locales) — all keys now 13/13, locale tests pass.
Full suite 8285 passed, ESLint/scope-undef/ruff CLEAN, Opus SHIP-safe.
Co-authored-by: pamnard <pamnard@users.noreply.github.com>
#3518 (@rodboev): in-app Help tab in Settings (docs + GitHub issues links), redesigned as polished icon-led cards. UX-approved by Nathan. Full suite 8242, CI 11/11. Co-authored-by: rodboev <rodboev@users.noreply.github.com>