crypto.randomUUID is only exposed in secure contexts (HTTPS or
localhost). When the web app is served over HTTP on a LAN IP the
attachment adapter, toast provider, message localId helper, file
attachment metadata and terminal id creation all call
crypto.randomUUID() synchronously and throw TypeError, so the UI
silently does nothing (e.g. the file picker opens and closes with no
chip).
Add a small web/src/lib/randomId helper that tries crypto.randomUUID
first, then falls back to crypto.getRandomValues-derived UUID v4,
and finally to a Date.now/Math.random string for very old
environments. Route all five call sites through it. Output format is
identical for secure contexts and UUID v4 for the getRandomValues
path, so existing DB/SSE/RPC consumers see the same shape.
* feat(web): add appearance setting (follow system / dark / light)
Add user-facing appearance preference to the settings page, allowing
users to choose between Follow System, Dark, and Light themes. The
preference is persisted to localStorage and takes priority over
automatic detection in the existing theme pipeline.
* fix(web): update theme on cross-tab appearance change
The storage event handler only updated React state without calling
updateScheme(), leaving data-theme and useTheme subscribers stale.
* fix(web): move cross-tab appearance sync to global initializeTheme
The storage event listener was inside useAppearance(), which is only
mounted on the settings page. Other pages never received cross-tab
theme updates. Move the listener into initializeTheme() so all pages
respond to appearance changes from other tabs.
* feat(web): add About section to settings page
- Add website link to hapi.run
- Display app version from CLI package
- Display protocol version from shared module
- Add Vitest testing setup with settings page tests
🤖 Generated with Claude Code
* test(web): add tests for website link and i18n key usage
Address residual risks mentioned in PR review:
- Test website link URL and security attributes (target, rel)
- Verify correct i18n keys are used for About section via spy
Simplify test setup by using real I18nProvider and en locale.
🤖 Generated with Claude Code
- Add Settings > Display > Font Size selector (80/90/100/110/120%)
- Persist preference (hapi-font-scale) and apply globally via --app-font-scale
- Normalize main UI typography to match Settings
Add support for users to select voice language preference, which is passed through to ElevenLabs agents via platform settings overrides. Includes new language mapping utilities, UI controls in settings page, and updated voice session initialization.
* feat: expand terminal quick keys
Add navigation keys plus ctrl/alt modifiers for the web terminal.
* feat: make terminal modifiers sticky
Keep Ctrl/Alt active until toggled off, Termux-style.
* style: improve terminal quick key buttons
Boost active contrast and adjust sizing for mobile.
* feat: add terminal key popups
Use long-press popups for / and - keys.
* fix: make terminal modifiers mutually exclusive
* fix(web): keep soft keyboard open on terminal buttons
* fix(web): allow mouse clicks on terminal quick keys
* refactor(web): simplify terminal quick input modifier handling
Remove the applyModifiers callback and inline modifier state application.
Add automatic modifier reset after quick input when appropriate.
* refactor(web): extract dispatchSequence helper for terminal input handling
Create a reusable dispatchSequence callback to handle modifier application
and reset logic for both terminal data input and quick input handlers.
---------
Co-authored-by: weishu <twsxtd@gmail.com>
Previously, useTerminalSocket used a relative path `/terminal` for Socket.IO,
which would connect to the frontend host instead of the actual HAPI server.
Now it uses the baseUrl from context to connect to the correct server, enabling
scenarios like hosting the frontend on GitHub Pages while the server runs elsewhere.
The selected button was using bg-[var(--app-link)] with white text, which
resulted in invisible text in dark mode (white background + white text).
Changed to use bg-[var(--app-button)] and text-[var(--app-button-text)]
with opacity-80 for proper contrast in both light and dark themes.
The old decodeBase64 implementation tried atob() first which returns
garbled text for UTF-8 content (e.g., Chinese characters), and only
fell back to correct UTF-8 decoding when atob() threw an exception
(which it doesn't for valid base64).
Changes:
- Add encodeBase64/decodeBase64 utilities to web/src/lib/utils.ts
using TextEncoder/TextDecoder for proper UTF-8 support
- Update file.tsx to use shared decodeBase64 instead of buggy local impl
- Update files.tsx to use shared encodeBase64 instead of deprecated
escape/unescape approach
Add p-3 class to terminal page container to create spacing between
the terminal and page edges, matching the padding used elsewhere on
the page (header, error messages, etc.).
- Add CLI-side terminal management via Bun.Terminal with TerminalManager
- Implement server-side Socket.IO proxy for terminal I/O between web and CLI
- Create web terminal UI component with xterm.js and support for resize/reconnect
- Add terminal route and navigation button in session chat
- Include comprehensive terminal implementation plan and architecture docs
Introduces two reusable loading components to standardize loading states:
- Spinner: Base spinner with accessibility support (role, aria-label)
- LoadingState: Semantic loading indicator combining spinner and label
Updates loading patterns across the app:
- Full-screen/block loading now uses LoadingState (App, router, files, file)
- Inline button loading states show spinner + text with aria-busy
- Message list loading replaced with MessageSkeleton component
- Removes duplicate SpinnerIcon definition from PermissionFooter
Improves UX and accessibility:
- Loading screens centered and full-height
- Consistent ellipsis character (…)
- Semantic accessibility (role="status", aria-live, aria-busy)
- Adds CSS variables for banner styling
Adds maxWidth.content: '720px' to tailwind.config.ts for centralized width management across the web app. Replaces all hardcoded max-w-[720px] references with the new max-w-content class. Adds missing max-width constraints to file.tsx, files.tsx, and SessionList.tsx. Moves border-b dividers from outer full-width divs to inner max-w-content divs for consistent visual hierarchy.
This fixes an issue where viewport-fit=cover and black-translucent statusbar
configuration allowed content to extend under the iOS statusbar. Added env(safe-area-inset-top)
CSS handling to all main layout components:
- SessionHeader with pt-[env(safe-area-inset-top)]
- SessionsPage, MachinesPage, SpawnPage with safe-area padding
- FilesPage and FilePage headers with safe-area padding