Commit graph

552 commits

Author SHA1 Message Date
allenliang2022
f657014aa3 fix(webui): stop mobile scroll jump-back — scope content-visibility + hold unpinned position (#5637)
Two linked causes of the mobile scroll jump-back, both confirmed by the
maintainer against origin/master in #5637.

Primary (CSS): the @media (pointer: coarse) block applied a flat
`content-visibility: auto; contain-intrinsic-size: auto 1px` to every .msg-row.
Off-screen assistant rows can be multi-thousand px (tool-result / long answers),
so a 1px reservation collapses their off-screen height; scrollHeight lurches down
by tens of thousands of px on a long transcript, the browser force-clamps
scrollTop, and the viewport is thrown to the top. A flat intrinsic-size cannot
both reserve tall rows and keep scrollHeight stable for iOS flick momentum (why
1px was chosen), so content-visibility is scoped to the short, size-predictable
user rows; assistant rows keep real rendered height. Mirrors the existing
#liveAssistantTurn opt-out pattern.

Secondary (JS): _restoreMessageScrollSnapshotSameFrame fell back to an absolute
snapshot.top when the semantic anchor restore failed. During streaming the live
activity-scene refresh fires this every tick; for a reader scrolled up into
history (unpinned), snapping to a stale absolute top nudges the viewport backward
by an amount that grows with scrollHeight. Now: hold position (no scrollTop write)
for the unpinned/anchor-failed case and let the browser's own scroll anchoring
keep the reader put. Pinned/near-bottom readers keep the tail-relative restore.

Adds tests/test_issue5637_mobile_scroll_clamp_jumpback.py (mutation-checked:
reverting either fix fails the matching assertion).
2026-07-05 22:52:59 +08:00
nesquena-hermes
7ad9707c4b fix(ui): align mobile sidebar close (X) with the new-conversation (+) button
On the mobile sidebar header the close (X) button rendered an 18px glyph — the
only 18px .panel-head-btn icon — sitting ~6px lower than the adjacent 14px
new-conversation (+) button, an inconsistent pair. Match the X glyph to 14px and
vertically center it on the ~41px panel-head row so both icons share one size and
baseline (top:calc(safe-top - 2px) keeps the existing safe-area offset so the X
still clears the notch, with no extra drawer spacing). 44x44 tap target kept for
mobile touch; mobile @media(max-width:640px) only, and the X is display:none on
desktop, so desktop is unaffected. + CHANGELOG + source-guard tests.
2026-07-05 06:40:26 +00:00
nesquena-hermes
9be4048c72 fix(#5501): fold in Fable a11y hardening — live region, aria-disabled, focus restore, ellipsis
Fable UX gate SHIP-WITH-UX-FIXES (non-blocking). Applied all: (1) role=status +
aria-live=polite on the error note so retry failure/restore is announced to
screen readers; (2) pending state uses aria-disabled (not the disabled property)
so the button retains keyboard focus, with click/keydown guards keeping it inert;
(3) on a failed retry, the fresh rebuilt Retry button reclaims keyboard focus
(_retryFailedFocus flag, rAF-guarded) so keyboard users aren't dropped to <body>;
(4) label uses a true ellipsis 'Retrying…' matching the app's other progress
string. CSS styles [aria-disabled=true] alongside [aria-busy]. Tests updated +
new a11y assertions; 8/8 pass, node --check clean.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes
7349a9434f fix(#5501): improve session-list retry feedback
The sidebar's load-error Retry button behaved like a raw idle browser button while
the next slow fetch was pending, so it looked broken/unresponsive. Retry now paints
an immediate pending state (label 'Retrying...', disabled, aria-busy), keeps the
retry state local to the error block via _renderSessionListLoadErrorNote(), and
lets the existing success/failure paths replace the error object when the request
settles (restoring 'Retry' on failure). Also adds proper styling.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes
39a4d67e5c fix(#5385): increase approval card height cap on mobile
The mobile command/tool approval popup rendered too small — options didn't all fit
and required scrolling within the cramped popup to select (Android WebUI wrapper).
Raises the mobile @supports(dvh) .approval-inner max-height cap from
min(52dvh,360px) to min(60dvh,420px) so the options fit without in-popup scrolling;
still overflow-y:auto so very tall content scrolls rather than clipping. Scoped to
the mobile dvh rule; desktop unaffected. Adds a source-level guard test.

Contributor stage; gate-pass. Co-authored-by: nankingjing <nankingjing@users.noreply.github.com>
2026-07-05 04:04:02 +00:00
nesquena-hermes
cebfdf41f9 fix(#5541): iOS PWA streaming freeze + SSE reconnect reliability
Two mobile reliability fixes on the crown-jewel chat streaming path:
(1) content-visibility:auto on off-screen .msg-row under @media(pointer:coarse)
so WKWebView skips layout/paint for off-screen rows during streaming (kills the
long-chat freeze). The LIVE turn is kept content-visibility:visible via the
STABLE #liveAssistantTurn id (covers ALL render modes — Compact Worklog,
Transparent Stream, restored-live — not just the Transparent-Stream path that
stamps data-live-assistant-turn), so a normal live turn on touch never blanks
mid-stream and its height keeps growing so the new-message cue still fires.
contain-intrinsic-size:auto 1px preserves flick-scroll momentum. Scoped to
touch — desktop find-in-page untouched. Deliberately does NOT set
overflow-anchor:none (inert on iOS WebKit; re-opens #4856/#5338).
(2) SSE reconnect ladder extended 4->6 steps + a last-ditch
_restoreSettledSession full-session poll (8s watchdog) after retries exhaust, so
a response completed during an iOS Tailscale/VPN reconnect is recovered without
an error banner.

Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-04 21:51:38 +00:00
nesquena-hermes
7a8b5ef7cf Merge commit '6771fc2cdc34' into release/stage-5456 2026-07-04 07:45:19 +00:00
Stacey2911
82a76eda4b fix: stabilize transparent stream thinking scrollbar 2026-07-04 13:23:37 +10:00
Rod Boev
af3eb88225 Drop redundant live-row suppression after removing the animation source 2026-07-03 13:48:20 -04:00
Rod Boev
904d741ed2 fix(#5367): suppress transparent stream live row animation 2026-07-02 23:31:57 -04:00
nesquena-hermes
012c66d12c Hide inactive flyouts from screen readers (+ visual-hide CSS)
Clean rebase of sheldon-im's #5404 (rebase-first).

Co-authored-by: sheldon-im <sheldon-im@users.noreply.github.com>
2026-07-02 07:15:53 +00:00
nankingjing
cabbb9a841 fix(ux): add expand control for update summary panel (#4705)
Move scrolling to an inner container and add an Expand/Collapse toggle
so long generated summaries are readable on narrow viewports.

Fixes #4705
2026-07-02 05:14:00 +00:00
ruizanthony
0c13a17231 fix(webui): stop live streaming message flicker
Opt live assistant streaming nodes out of the global theme color/background transitions so token-by-token markdown updates do not flash or fade on light themes.

Adds a targeted regression test in test_smooth_text_fade.py while keeping the opt-in smooth text fade feature intact.
2026-07-01 07:38:44 +00:00
Paladin173
d8f0f8f97f Fix composer control reorder rebase collision
Reapply footer control ordering on current upstream/master while preserving the required situational chip renderer. Persist composer_control_order with backend validation, keep the settings descriptions reorder-aware, and make primary/situational chip renderers participate in same-group drag ordering.

Verified with: node --check static/boot.js; node --check static/panels.js; git diff --check; ./scripts/test.sh tests/test_issue4598_composer_control_visibility.py
2026-06-30 21:11:54 -05:00
nesquena-hermes
3e84d7bf11 Merge #4738 (savagebread neon skins + skin-persistence fix) into stage 2026-06-30 22:21:28 +00:00
nesquena-hermes
0b74397fa6 Merge #5120 (rodboev budget limit) into stage 2026-06-30 21:28:51 +00:00
nesquena-hermes
8a5172275d Merge #5002 (per-project quick-create, opt-in) into stage 2026-06-30 20:56:05 +00:00
Rod Boev
11882aab2a feat(projects): add per-project new conversation shortcuts (#4676) 2026-06-30 19:28:27 +00:00
nesquena-hermes
d954d4c861 Merge #4871 (rodboev Mermaid toolbar) into stage 2026-06-30 19:27:15 +00:00
nesquena-hermes
492fdfb84a Merge #4574 (rodboev mobile titlebar) into stage 2026-06-30 19:10:38 +00:00
nesquena-hermes
61cc0d167b Merge #5019 (disco32r mobile touch targets) into stage 2026-06-30 18:54:36 +00:00
savage_bread
2bd1f68805 Add Neon Soft and Neon Paint skins
- Neon Soft: lighter dark-mode surfaces with the same neon purple/cyan palette
  as the existing Neon skin.
- Neon Paint: cyberpunk-themed skin with hot pink + electric cyan + warm amber
  on a deep purple-gray background.

Both skins include full light and dark mode variable sets plus component-level
overrides (selection, titlebar, sidebar, inputs, buttons, scrollbars, tool
cards, tree nodes, session badges, diff blocks, and links). Skin registration
is updated in boot.js and the inline skin whitelist in index.html
2026-06-30 18:28:33 +00:00
Rod Boev
761ffc5d96 fix(#5250): let settings search results escape the menu clip 2026-06-29 22:23:00 -04:00
nesquena-hermes
c6370235be feat(composer): fit footer controls by overflow (#4657, rebased onto master)
Rebased Paladin173's #4657 onto current master. The per-control visibility
toggles half already shipped via his own #4598, so this rebase carries only the
net-new JS fit-by-overflow engine (_fitComposerFooter: ResizeObserver +
MutationObserver measure .composer-left overflow, step full -> cf-icons ->
cf-burger). Co-authored-by: Paladin173 <Paladin173@users.noreply.github.com>
2026-06-29 01:19:02 +00:00
nesquena-hermes
863dc4e041 Splash logo: drop app-icon tile, free-breathing mark with per-theme glow
The new-conversation splash mark was wrapped in a glossy navy rounded-rect
that reads as a macOS app-launcher tile ('this is an app') rather than a
brand mark. Major AI chat UIs (Claude, ChatGPT, Gemini) all render their
empty-state mark bare. Remove the tile, scale the winged caduceus up
(~88px), and back it with a soft cyan/blue radial 'spirit' bloom.

Light and dark get separately tuned marks: dark keeps the bright
cyan->blue gradient + luminous bloom; light uses a deeper #0AA6CC->#1E63D6
gradient + cooler restrained bloom so the mark holds contrast on the pale
background instead of washing out. Entrance is gated behind
prefers-reduced-motion.
2026-06-28 23:42:02 +00:00
nesquena-hermes
f968de8d35 merge #5155 extension settings surface 2026-06-28 23:07:00 +00:00
Rod Boev
a76c794d6d feat(extensions): add browser-local extension settings surface (#5094) 2026-06-28 17:28:20 -04:00
nesquena-hermes
1cbf364f48 Merge remote-tracking branch 'origin/master' into pr5113 2026-06-28 20:05:50 +00:00
Ryan via Axis
e2176e12b6 Clarify mobile touch target comments 2026-06-28 11:01:25 -04:00
Rod Boev
664b29d403 fix: address adversarial review findings for budget feature 2026-06-28 10:24:30 -04:00
Rod Boev
62e25ad9c1 feat(#692): add configurable budget limit with percentage-used tracking 2026-06-28 10:10:35 -04:00
nesquena-hermes
7ec7b9c942 fix(mobile): PWA reflow guard + avoid redundant same-session refresh (salvage of #3976)
Salvages PR #3976 by ksj5131513-ops, ported fresh onto current master and
re-applied on top of #4195's external-refresh gate (the original PR predated
#3916/#4195's rework of refreshActiveSessionIfExternallyUpdated).

Two parts:

1. Avoid an unconditional same-session force reload the moment streaming
   settles. _scheduleActiveSessionIdleReload previously called
   loadSession(force:true) on every idle reconcile, which on mobile PWA caused
   a visible end-of-turn flash and could briefly restore the pane with stale
   layout geometry. It now reconciles against server metadata first via
   refreshActiveSessionIfExternallyUpdated('idle-reconcile', {ignoreStreamJustFinished:true})
   — reloading ONLY when the message count actually changed — and falls back to
   the original forced reload only if the metadata probe itself fails.

   This COEXISTS with #4195: the poll-only external-session early-return
   (if((reason||'poll')==='poll' && !_isExternalSession(S.session)) return) is
   left untouched. The idle path uses a non-'poll' reason so it never trips that
   gate, and opts (read via arguments[1], same pattern as loadSession) only
   bypasses the post-stream cooldown, not the external gate. The function keeps
   its (reason) signature and now returns a status string
   ('skipped'/'unchanged'/'reloaded'/'failed') for callers that want it.

2. Mobile viewport reflow guard for PWA visualViewport changes. When the
   on-screen keyboard / browser chrome shows or hides, the phone layout can stay
   painted against stale geometry. _forceMobileViewportReflow() toggles a
   one-frame html.viewport-reflow class (cheap GPU-promotion transform) to force
   a repaint, then resyncs the workspace panel + sidebar aria. Wired to
   visualViewport resize/scroll (debounced) where available, falling back to the
   window resize handler otherwise. Adds safe-area-inset padding to topbar,
   messages-inner, and composer-wrap on phones. Uses overflow-x:clip (NOT hidden)
   on .layout per the #4856 lesson to avoid coercing overflow-y into a scroller.

Co-authored-by: ksj5131513-ops <ksj5131513-ops@users.noreply.github.com>
2026-06-28 09:02:10 +00:00
nesquena-hermes
04066b4b43 feat(insights): surface prompt-cache hit rate (salvage of #3912)
Surfaces prompt-cache hit rate on the Insights page so cache-efficiency
(a key cost-optimisation signal) is visible at a glance.

Backend (api/routes.py, _handle_insights):
- Aggregate cache_read_tokens per model and per day, plus a total.
- Graceful fallback for older agent state.db schemas lacking the
  cache_read_tokens column (COALESCE + OperationalError second query).
- New response fields: total_cache_read_tokens, total_cache_hit_percent,
  per-model cache_read_tokens + cache_hit_percent, per-day cache_read_tokens.

Frontend:
- Models table gains a Cache column (panels.js + style.css 6-col grid).
- Daily bar tooltip includes the bounded cache hit rate.
- New i18n keys insights_model_cache / insights_cache_hit in all 13 locales.

Correctness fix (maintainer-flagged on #3912): the original computed
hit-rate as cache_read / input_tokens (reads-over-misses), which EXCEEDS
100% on cache-heavy sessions. This uses the FULL prompt total as the
denominator -- cache_read / (input_tokens + cache_read) -- via the shared
api.usage.prompt_cache_hit_percent helper, so it is bounded 0-100% and
means "% of the prompt served from cache". Returns None (rendered as a
muted dash) when there are no cache reads.

Tests (tests/test_insights.py):
- Updated existing assertions for the new response shape.
- Added a non-vacuous <=100% boundary test: a cache-heavy case
  (cache_read=9900, input=100) where the OLD formula reports 9900% but the
  bounded denominator yields 99%. Verified the test FAILS under the old
  formula and PASSES under the fix.

Salvage note: PR #3912's diff was ~99 releases stale (mostly re-derived old
code, illusory +5405). This ports ONLY the real ~190-line feature fresh
onto current master.

Closes #3911

Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
2026-06-28 08:11:22 +00:00
nesquena-hermes
5eff106e14 Swap brand to new cyan caduceus: favicon family, titlebar + empty-state logo (theme-safe navy badge), remove per-skin titlebar fill overrides
New brand caduceus (vector mark, cyan #08EBF1->#3889FD) on a radial-depth navy
badge. Favicon family regenerated (svg/ico/png/apple-touch). Titlebar and
empty-state logos now use the self-contained navy badge so they render correctly
on every theme (light + dark) without washing out. Removed 5 per-skin CSS rules
that force-filled the titlebar icon to a flat accent color (they assumed the old
monochrome caduceus).
2026-06-28 07:44:16 +00:00
Rod Boev
861c29b35b fix(#4731): enlarge mobile message action tap targets 2026-06-28 06:41:16 +00:00
Frank Song
06465bbd91 Improve extension gallery source and permissions display 2026-06-28 12:44:57 +08:00
nesquena-hermes
3c3c9047bb skins: add GitHub, Codex, and Terracotta opt-in appearance skins (#4634, #4636, #4635)
GitHub (#4634): graphite + Primer-blue. Codex (#4636): minimal editor, sage accent.
Terracotta (#4635): warm clay accent, renamed from the originally-proposed name to a
descriptive material name. All CSS-only, namespaced under [data-skin], opt-in via
Settings > Appearance. Gate: Codex SAFE + full suite 10778 pass + 3 accents verified live.

Co-authored-by: gottipx <gottipx@users.noreply.github.com>
2026-06-27 23:57:37 +00:00
nesquena-hermes
11260c8df5 feat(extensions): show post-install next steps in the gallery (#4964)
Renders generic registry post_install guidance + lifecycle requirements in the
Settings extensions gallery card (manifest-driven, no vendor special-casing).
Fixes #4959. Rebased onto current master (squash-applied #4964's net diff so it
coexists with the #4979 sidecar-runtime-status feature that landed in v0.51.697).

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-27 05:44:10 +00:00
Frank Song
d2eedd85f8 Show extension sidecar runtime status 2026-06-27 05:12:50 +00:00
nesquena-hermes
1ff3f2f3ac Merge PR #4997 into tier-1 batch 2026-06-27 03:39:10 +00:00
nesquena-hermes
59f14f05cd Merge PR #4888 into tier-1 batch 2026-06-27 03:39:10 +00:00
Axis Canary
feb380253f Improve mobile WebUI touch targets 2026-06-26 20:35:11 -04:00
Rod Boev
0c7b625104 fix(mobile): hide the dashboard rail link on narrow viewports (#4712) 2026-06-26 11:05:05 -04:00
luandnh
046a0f68b8 fix: render cron run output as literal pre code, not via renderMd
Cron run output is never authored Markdown -- it is logs/JSON/plain text.
Using renderMd() with any heuristic or synthetic fence is fragile:
- Content starting with #, |, > becomes headings/tables/blockquotes
- Embedded triple-backtick in content breaks the synthetic fence

Fix: use DOM-created pre/code with textContent throughout:
- No heuristic, no synthetic fence -- every byte renders verbatim
- Both render paths (initial load + View full output) use the same approach
2026-06-26 11:56:09 +00:00
Hoang Luan
c3d1798622
Merge branch 'master' into fix/tab-visibility-chip-graphite-v2 2026-06-26 09:23:41 +07:00
Rod Boev
779d46a1cf fix(#4784): show the reload button outside standalone mode 2026-06-25 20:41:46 +00:00
Hoang Luan
b85240c6e2
Merge branch 'master' into fix/tab-visibility-chip-graphite-v2 2026-06-25 13:53:19 +07:00
Hoang Luan
3bf1e841f4
Merge branch 'master' into fix/tab-visibility-chip-graphite-v2 2026-06-25 12:21:27 +07:00
nesquena-hermes
3acd802588 fix(#4856): mobile transcript collapses unscrollable — overflow-x:hidden coerces overflow-y:auto
The mobile .messages-inner rule used overflow-x:hidden, which per the CSS
overflow spec coerces overflow-y from visible to auto — turning the inner
into a scroll container. A scroll container's min-height:auto resolves to 0,
so the inner (a flex-shrink:1 item inside the .messages column flexbox)
collapsed to the viewport height instead of growing to its content. The
transcript then overflowed the clipped inner rather than the .messages
scroller, leaving nothing to scroll and pinning scrollTop at 0 — every
open/send/receive stranded mobile users at the top with no way to scroll down.

Fix: overflow-x:clip suppresses horizontal overflow (the original #4583/#4553
intent) WITHOUT coercing overflow-y or creating a scroll container, so
min-height:auto stays min-content and the inner grows to full height.

Verified empirically via headless Chrome mobile emulation (390x844, mobile=true):
scrollableDelta 0 -> 7746; open/send/receive all land at bottom; mid-scroll
position preserved across re-render. Desktop unaffected.

Updated #4553 test (clip satisfies its horizontal-pan goal) + added dedicated
2026-06-25 05:18:32 +00:00
Hoang Luan
8417012383
Merge branch 'master' into fix/tab-visibility-chip-graphite-v2 2026-06-25 11:38:59 +07:00