fix(ui): restore rail-era app titlebar state (v0.50.226)#1163
fix(ui): restore rail-era app titlebar state (v0.50.226)#1163nesquena-hermes merged 3 commits intomasterfrom
Conversation
This PR removes the titlebar queue badge (_syncQueueTitlebar), but the hide button on the queue card still tells the user to "click the titlebar badge to show again" — which no longer exists in the titlebar slot (subtitle slot now shows message count). Update the tooltip and adjacent comment to reference the queue pill, which is the actual affordance that re-shows a hidden queue card via _updateQueuePill(). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
nesquena
left a comment
There was a problem hiding this comment.
Review — end-to-end ✅ (approved after small tooltip fix pushed)
What this ships
v0.50.226 — restores the rail-era app titlebar by absorbing @aronprins' PR #1141:
- Removes the
tps-chipelement (id="tpsStat") from the global app titlebar (static/index.html) - Centers the titlebar (desktop):
.app-titlebarand.app-titlebar-innerusejustify-content:center; mobile media query overrides back tospace-betweenso the hamburger keeps its slot (static/style.css:210-211, static/style.css:844-846) - Restores
n_messagescount in the subtitle slot viasyncAppTitlebar(static/panels.js:36-37) - Deletes the
_syncQueueTitlebarhelper that was hijacking the subtitle slot for queue state, plus its single call site inupdateQueueBadgeandsyncTopbar(static/ui.js)
Traced against upstream hermes-agent
Pulled fresh nousresearch/hermes-agent tarball. grep -rn "tpsStat\|app-titlebar\|tps-chip" returns nothing — pure WebUI-frontend chrome change with zero agent coupling.
End-to-end trace
Removing #tpsStat is safe
The streaming metering SSE listener at static/messages.js:827-839 is the only consumer of the chip. It has a defensive guard:
source.addEventListener('metering',e=>{
try{
const d=JSON.parse(e.data||'{}');
const el=$('tpsStat');
if(!el) return; // ← guard at line 833
...
el.textContent=`${tps} t/s · ${high}${low?' · '+low:''}`;
}catch(_){}
});With the chip gone, $('tpsStat') returns null, the guard returns immediately, and the wrapping try/catch swallows nothing because no error is thrown. ✅ The PR description's claim is accurate.
_syncQueueTitlebar removal — orphan check
grep -rn "tpsStat\|tps-chip\|_syncQueueTitlebar" static/ api/
# static/messages.js:832 ← guarded, fine
# (no other refs)Both updateQueueBadge (the line that was calling it) and syncTopbar had their _syncQueueTitlebar invocations removed in the diff. The queue is still surfaced through the existing queuePill element via _updateQueuePill at static/ui.js:1317-1347 — count>0 + flyout-not-visible → pill shows; click pill → re-opens card. The user-facing affordance for queue state is unchanged; only the titlebar duplicate is gone.
syncAppTitlebar subtitle update path
syncAppTitlebar is called from many places that matter:
syncTopbar()calls it (chat panel updates, message stream completion, send/queue events)i18n.js:4199after locale switchpanels.js:81-100, 177, 737, 1719, 1755, 1783, 2076, 2082, 2770(panel changes, rename, switch session)commands.js:336after slash-command runs
That covers every path where S.messages would change in chat panel context. The subtitle stays current.
Mobile layout
static/style.css:843-846 override:
.app-titlebar{justify-content:space-between;}
.app-titlebar-hamburger,.app-titlebar-spacer{display:flex;}
.app-titlebar-inner{flex:1 1 auto;}Inside the @media (max-width:768px) block — mobile gets space-between (so the hamburger pushes to one side and content fills) while desktop centers. Verified the override comes after the desktop rule so cascade is correct.
What I caught — stale tooltip on queue hide button
static/ui.js:1206 had:
hideBtn.title='Hide queue (click the titlebar badge to show again)';But this PR removes the titlebar queue badge entirely — there is no longer anything in the titlebar to "click to show again". The actual affordance is the queue pill (rendered by _updateQueuePill after the card is hidden). User following the tooltip would look in the wrong place.
What I pushed — 1d11646
hideBtn.title='Hide queue (click the queue pill to show again)';Plus updated the adjacent code comment. Two-line cosmetic fix; no behaviour change.
Other audit — confirmed correct
- JS syntax:
node --checkpasses onpanels.js,ui.js,messages.js. - i18n coverage:
n_messageskey exists in all 7 locales (en, ru, es, de, zh, zh-Hant, ko). - PR's tests: 4/4 pass — verifies (a)
id="tpsStat"gone from index.html, (b) centered desktop CSS present, (c) subtitle line in panels.js, (d)_syncQueueTitlebarsymbol gone from ui.js. - Race / state: no shared state mutated; pure DOM chrome.
- CI on PR: ✅ test (3.11), ✅ test (3.12), ✅ test (3.13).
Edge-case trace
| Scenario | Expected | Actual |
|---|---|---|
| Chat panel + active session, 5 messages | subtitle "5 messages" | ✅ syncAppTitlebar sets t('n_messages',5) |
| Empty fresh session | subtitle "0 messages" | ✅ (consistent with rail-era behaviour) |
| Non-chat panel (settings/files) | subtitle hidden | ✅ subText stays '' → subEl.hidden=true |
| Queue grows to N>0 | pill shows "N queued" | ✅ _updateQueuePill unchanged path |
| Hide queue card via X button | pill re-appears, tooltip says "click queue pill" | ✅ after my fix |
| TPS metering events arrive after chip removal | guard if(!el) return |
✅ messages.js:833 |
| Mobile (≤768px) | titlebar uses space-between, hamburger fits | ✅ @media override at 843-846 |
| Locale switch | i18n.js calls syncAppTitlebar | ✅ line 4199 |
| Inline rename of session title | syncAppTitlebar guarded by _renamingAppTitlebar |
✅ panels.js:46 (unchanged) |
Tests
- PR's tests (test_app_titlebar_restore.py): 4/4 pass.
- Local full suite: 2547 passed, 47 skipped, 1 PR-unrelated failure (
test_sprint3.py::test_workspace_add_rejects_system_paths— verified pre-existing on master with the same file; macOS-specific quirk unrelated to titlebar). - CI on PR: green on 3.11/3.12/3.13.
Minor observations (non-blocking)
- The "0 messages" subtitle on a fresh session is technically rendered (since the conditional is
if (typeof t === 'function')notif (vis.length > 0)). This matches the rail-era behaviour the PR explicitly restores, so it's intended — just noting for future polish. - The
tps-chipCSS rule deletion also removes its styling block; if anyone re-introduces a metering chip elsewhere they'll need to redefine the styles.
Recommendation
Approved. Clean restoration of the rail-era titlebar — TPS chip removal is safe (guarded listener), _syncQueueTitlebar removal is complete (no orphan refs), subtitle correctly switches to message count via syncAppTitlebar, mobile override preserves hamburger layout. One stale tooltip on the queue hide button caught and fixed in 1d11646. CI green; PR's 4 tests + full suite (2547 passed, only an unrelated pre-existing failure) confirm no regression. Parked at approval — ready for the release agent's merge/tag pipeline.
|
Merged as v0.50.226. Integration branch absorbed @aronprins's original PR #1141 with one reviewer fix from @nesquena ( Full gate results:
Thank you @aronprins for the clean PR — the titlebar is properly restored. |
Merged as v0.50.226. Integration branch absorbed @aronprins's original PR nesquena#1141 with one reviewer fix from @nesquena (`1d11646`: queue hide tooltip updated to reference the queue pill, not the removed titlebar badge). **Full gate results:** - 2595 tests passing ✅ - Browser QA 21/21 (desktop 1440×900 + mobile iPhone 14) ✅ - Independent review: APPROVED by @nesquena ✅ Thank you @aronprins for the clean PR — the titlebar is properly restored.
What this PR does
Restores the app titlebar to its rail-era state:
static/index.html) — it was global shell chrome that accumulated transient runtime noisejustify-content: centeron both.app-titlebarand.app-titlebar-inner(desktop), with the correctjustify-content: space-betweenoverride on mobile where the hamburger needs roomsyncAppTitlebar()inpanels.jsnow computesvis.lengthand passes it tot('n_messages', …), bringing back the behavior from the original rail branch_syncQueueTitlebar— the function that was hijacking the subtitle slot to show queue state has been deleted fromui.js. Queue affordances belong somewhere less dominant. The queue pill and flyout card are untouched.Review notes
The metering
source.addEventListener('metering', ...)handler inmessages.jsalready has an earlyif (!el) returnguard, so removing the DOM element is safe — no JS errors occur when the element is absent.Tests: 4 new tests in
tests/test_app_titlebar_restore.py. Full suite: 2595 passing.Browser QA: Tested at 1440×900 desktop and iPhone 14 mobile. Both modes pass all checks.
Changelog
See
CHANGELOG.md→[v0.50.226]