* fix(web): trust live hub connectivity over navigator status
* fix(web): align offline and reconnecting banner precedence
* test(web): provide i18n context to banner tests
Hub: getOrCreateMachine now merges incoming machine-owned metadata over
the stored row (first-write-wins previously kept rows registered without
a host name nameless forever; hub-only fields like displayName survive).
Web: session-list machine labels are cached in localStorage so machines
whose row is gone or whose query has not loaded yet keep their last
known name instead of flickering to the 8-char id prefix.
useLongPress binds both touch and mouse handlers. After a tap, touch
browsers emit compatibility mouse events (~300ms later) that the page did
not preventDefault, so onClick fired twice: once from touchend, once from
the synthesized mouseup. On the wide tablet sidebar layout the list stays
under the finger, so the second onClick lands on whatever row slid into
that position and navigates to the wrong session.
preventDefault() on touchend for every handled tap, and additionally
swallow mouse events that arrive within 700ms of a touch so browsers that
still dispatch the compatibility sequence cannot re-trigger onClick.
Based on the fix by RiriAgent in the fork (commits 1bbfcc20, 5e3d135a).
Co-authored-by: RiriAgent <39219425+RiriAgent@users.noreply.github.com>
SessionChat fetched Codex models through the session-scoped endpoint,
so the CLI listed models in the session process cwd, where a missing
directory or project-level Codex config could skew or break the result.
Use the machine-scoped endpoint (already used by NewSession) and drop
the now-unused session route and RPC plumbing.
Fixes#1072
Searching the session list forced every directory group to expand all
sessions (expanded: isFiltering) and hid the Show more button, so the
user's per-group preview fold was ignored during filtering. Stop
overriding the preview state while filtering and keep the Show more /
Show less control available.
Fixes#1068
* fix(web): exit scratchlist mode after successful promote-to-queue (#959)
After Send to queue accepts, call onExitScratchlistMode so the operator
can continue normal chat. Rejected sends keep mode on. Unit + Playwright
smoke coverage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): add execStartedAt/execCompletedAt to ToolCard test mock
Upstream ChatToolCall gained exec timestamps; ToolCard.test.ts mock
was missing them and broke CI typecheck after rebase onto main.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Single-machine users no longer expand a redundant machine layer; with
multiple machines a chip filter bar (persisted, with hover health popup)
replaces the collapsible machine headers. Directory groups now render
top-level with machine-name suffixes when unfiltered. Also removes the
redundant session/project count header text.
* fix(web): preserve composer attachments across session switches
Persist composer files per session and restore completed uploads without
uploading them again. Clear attachment drafts alongside text after send and
cover restoration, isolation, and adapter reuse with regression tests.
Fixes#465
* fix(web): keep cleared attachment drafts tombstoned
Retain an empty in-memory cache entry until the queued IndexedDB delete completes so a fast remount cannot restore stale files. Add regression coverage for the clear/remount race.
* fix(web): defer attachment restore for inactive sessions
Only restore or clear attachment drafts while the session attachment adapter is available. Preserve saved files when an inactive session mounts without attachment support and add regression coverage.
* fix(web): use dedicated split breakpoint for compact tablets
Some compact Android tablets (e.g. OPPO Pad mini) report a landscape
CSS viewport below Tailwind's `lg` (1024px) despite having enough
physical screen space, so the sessions layout fell back to a single
column. Add a dedicated `split` breakpoint at 920px and use it for the
sessions split layout and the sidebar width/resize CSS, leaving the
global `lg` breakpoint (and all other pages) untouched.
* fix(web): cap sidebar width against viewport on compact split
A persisted sidebar width (up to 600px from resizing on desktop) could
shrink the detail pane to 316px at the new 920px split breakpoint, below
the previous 1024px worst case of 420px. Cap the sidebar width at
min(var(--sidebar-w), calc(100vw - 424px)) so the detail pane keeps at
least 420px down to 920px, with no effect on desktop.
* fix(web): seed sidebar drag from rendered width
When the compact-split viewport cap renders the sidebar narrower than the
persisted width, dragging the handle to shrink it had a dead zone until
the stored width fell below the rendered width. Seed the drag from the
sidebar's rendered width so it responds immediately; unchanged on desktop
where rendered and stored widths match.
The mermaid fallback previously showed only the raw source with no reason,
making a supposedly-valid diagram that fails in HAPI (but passes the Mermaid
CLI) impossible to diagnose from the running UI. Every diagnostic was
swallowed: setParseErrorHandler no-op, suppressErrorRendering, parse with
suppressErrors returning false, and an empty catch.
renderMermaidSvg now returns a { svg, error } outcome. On a failed
suppressErrors parse it re-parses once (no side effects: parse-error handler
is a no-op, suppressErrorRendering stays on) purely to capture the thrown
reason; render() failures capture their message too. The fallback surfaces
that reason plus a data-mermaid-error attribute for automation, while keeping
the raw source verbatim. The failure notice is gated on an actual error so the
async load window does not flash a false 'could not render' banner.
Preserves the #785/#813 hardening (no error-SVG injection, no crash, no new
XSS surface). Addresses the weak-feedback half of #1117.
Refs #1117
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): autolink markdown links, inline code, and .mmd file paths in chat
Chat autolinking previously only worked for bare file paths in plain text.
Fancier markdown forms silently produced dead links:
- COMMON_FILE_EXTENSIONS omitted common agent-cited types (mmd, puml, rst,
csv, ini, etc.), so bare diagram.mmd never linked.
- inlineCode nodes were never processed, so `path/to/file.md` never linked.
- explicit [label](relative/file.md) links kept a raw relative URL that the
SPA router treated as a dead route under /sessions/.
Changes:
- Expand COMMON_FILE_EXTENSIONS with justified doc/diagram/config/lang exts;
deliberately exclude TLD-lookalikes (org/com/io) to avoid domain false
positives.
- Autolink inlineCode nodes whose ENTIRE value is a single path pattern match
(whitespace-free, allowlisted ext), wrapping an inlineCode child to keep
monospace. Real code snippets are left untouched.
- Rewrite explicit markdown links whose target is a repo-relative allowlisted
file path into hapi-file: hrefs (aligns with #1113). Preserves label.
Security invariants preserved: shouldLinkPath still rejects abs / ~/ / ../ /
Windows-drive / scheme:// paths; scheme-bearing link urls are left for the
deny-scheme layer; deny-scheme handling untouched.
Refs tiann/hapi#1120
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): don't rewrite explicit links in standalone markdown preview
Codex review (#1142): rewriteFileLinkNode ran on the standalone file-preview
surface too, but that surface has no HappyChatContext so the shared `A` anchor
collapses hapi-file: links to plain text (returns props.children when !chat).
That turned an explicit [label](file.md) link in a README preview from an
anchor into plain text.
Gate explicit-link rewriting behind a rewriteExplicitLinks option (default on
for chat) and disable it for the standalone renderer via new
MARKDOWN_PLUGINS_STANDALONE(_WITH_BREAKS) arrays. Bare-path and inlineCode
autolinks are kept — they were already inert on the standalone surface, so no
behavior change there.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): add Copy reference to session context menu
Refs tiann/hapi#950
Adds a More actions item that copies a cross-session citation
(see session "title" (/sessions/id) for context) instead of a
bare share URL.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): sanitize session titles in copy-reference text
JSON-escape titles and collapse whitespace so arbitrary session
names cannot inject prompt text into cross-session citations.
Addresses Codex review on tiann/hapi#951.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
The Codex import button used the same circular-arrow SVG as the
session-list refresh button, making the two adjacent actions look
identical. Switch the import affordance to a download-into-tray icon
to match its 'import sessions' semantics.
Closes#1135
* fix codex session import merge (#1123)
修复 Codex 会话导入合并后列表为空的问题。
Fix Codex session import merge so a session is not detected as a duplicate of itself and deleted during merge.
Co-authored-by: LIUZHIRU <ryuu@fine-net.co.jp>
* fix(web): honor explicit older-history loads
Clear the initial scroll-settling window before Load older, outline
load-more, and outline target fetches so a user click is not swallowed
during the first-scroll-to-bottom settle period.
Automatic top-sentinel loads still respect settling.
Fixes#1067
---------
Co-authored-by: Himehane <36065996+Himehane@users.noreply.github.com>
Co-authored-by: LIUZHIRU <ryuu@fine-net.co.jp>
* fix codex session import merge (#1123)
修复 Codex 会话导入合并后列表为空的问题。
Fix Codex session import merge so a session is not detected as a duplicate of itself and deleted during merge.
Co-authored-by: LIUZHIRU <ryuu@fine-net.co.jp>
* fix(web): keep overlays below PWA status bar
Account for env(safe-area-inset-top) on fixed top banners, image
preview chrome, and session action menus so installed PWA mode no
longer draws controls under the OS status bar.
Fixes#1066
---------
Co-authored-by: Himehane <36065996+Himehane@users.noreply.github.com>
Co-authored-by: LIUZHIRU <ryuu@fine-net.co.jp>
Use @lobehub/icons (already a dependency) for per-agent SVG logos in the
session list, session header, and new-session agent selector.
- Color variants for claude/codex/gemini; Mono (currentColor) for
cursor/grok/opencode so glyphs track the theme text color
- kimi uses Mono as well: KimiColor's main glyph is hard-coded #fff and
vanishes on the light theme
- pi and unknown flavors keep the letter-badge fallback (no logo shipped)
- Deep component imports avoid the package root's ./features re-export,
which pulls uninstalled peer deps (antd, @lobehub/ui)
* feat(shared,cli): whitelist away_summary so auto recap reaches the hub
Claude Code's local TUI writes an automatic away-summary recap to the
session transcript on window blur/focus (5min+ idle), but
VISIBLE_CLAUDE_SYSTEM_SUBTYPES dropped it before it ever reached the
hub. Add it to the whitelist so the local launcher forwards it like
the other system subtypes, and cover the forwarding + Zod passthrough
of the recap `content` field with tests.
* feat(web): render Claude Code's automatic away recap in the chat
Once away_summary reaches the hub (previous commit), the web chat
still dropped it silently: normalizeAgent had no branch for the
subtype, so it fell through to `return null`. Add a `recap` AgentEvent,
a normalizeAgent branch mirroring the existing turn_duration/compact
subtype branches, and a presentation entry that prefixes the text with
`recap:` so it reads distinctly from the manual /recap assistant
bubble (which already renders as a normal message). No new render
component needed: it flows through the existing generic system-event
row (SystemMessage.tsx + getEventPresentation) that every other system
subtype already uses.
* fix(web): drop inaccurate manual-/recap comparison from recap comments
* fix(web): improve machine health sidebar UX
Keep machine names visible, align health metrics, and make nested health tooltips usable with touch and keyboard input.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): preserve tooltip focus reveal groups
Keep the unnamed group used by existing focus reveal classes while retaining named hover groups for nested machine health tooltips.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): add exec* timestamps to ToolCard test fixture
Unblocks typecheck after #1036 made execStartedAt/execCompletedAt
required on ChatToolCall; fixture was missing both fields.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): align machine health status with meters
Right-align the capacity status with the utilization meter edge to balance the tooltip header without shortening the bars.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix(web): restore machine health disclosure semantics
Expose the machine group's expanded state on its toggle and describe the health trigger with the tooltip body for assistive technologies.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>