All notable changes to MobileClaw are documented in this file.
- Initial retry logic for detached mode WebSocket connections
- Xcode Cloud post-clone script to build web assets
useAppModehook — extractedisDetached/isNativedetection from inline URL param checks
- Send button flash on abort-to-disabled transition — stabilized button state transitions with memoized fadeInIds
- Send button disappearing in disabled state
- Preserve optimistic
u-*message IDs across history merges (prevents duplicate flicker) - Preserve streaming assistant message ID across history merges
- Duplicate React keys from
hist-*ID carry-over on history re-fetch - LM Studio
isStreamingflag not being set (caused stale button states) - Slow thinking indicator fade-out on abort
- Disable automatic
/commandsfetch on connect (was polluting history)
- WebSocket resume-from-sleep reconnect — detects background/sleep via
visibilitychange,focus,onlineevents, and clock-gap timer; force reconnect after 60s+, otherwise re-fetch history; 16MB message limit for large payloads - Liquid glass pill styling on chat input — rounded-full shape, transparent oklch background, backdrop blur, matching scroll-to-bottom button
- Privacy manifest and App Store compliance config for iOS
- Pre-push hook running typecheck and lint via bun
- iOS history load not scrolling to bottom
- iOS WebKit stale paint on thinking indicator dismiss
- iOS keyboard-open not scrolling to bottom
- Thin native wrapper (#22) — moved ~1,400 lines of protocol handling from Swift to web; Swift now only handles device identity (Keychain), native UI chrome, and forwarding user actions via bridge; added
WebSocketProxy.swiftfor CORS-free WS routing,identity:signbridge flow, andconfig:connectionbridge message - iOS native header polished, mode-switch state leaks fixed
- Use
callAsyncJavaScriptfor history forwarding to iOS
- Zen mode collapse toggle (#18) — collapses intermediate assistant turns in multi-turn responses, slide+fade animations, localStorage persistence,
?zenURL param - Native zen mode wiring and stream updates (#21)
- Forward subagent events to store (#20)
- Zen-mode collapsed block spacing (#19)
- iOS thinking block starts for reasoning streams
- iOS zen block spacing gap and Swift concurrency errors
- OpenClaw run ID consistency across stream events
- Restyle thinking indicator and preserve run duration across history merges
- Make context prefixes shared between iOS and web
- Align thinking loader logic between backends
- Native iOS app — SwiftUI shell wrapping WKWebView with full OpenClaw protocol, Ed25519 device auth, demo mode, bundled webapp for standalone operation
- Session switcher UI — bottom sheet with search, kind badges, relative timestamps; header shows centered session pill with tappable connection dot
- Fade-in animation for messages arriving from history fetch
- Unread tab dot for unseen incoming messages
- Native session picker with WebSocket session switching (iOS)
- Makefile with
pr-commentstarget
- Manual scroll-to-bottom not re-engaging autoscroll during streaming
- Other-tab messages not displaying until run completes
- Streaming autoscroll unpin behavior adjusted
- Refactor
page.tsxinto focused chat components (#17) — extractedChatChrome,ChatComposerBar,ChatViewportand runtime hooks - Session picker detached theme (#16)
- iOS: remove setup dialog autofocus, dismiss keyboard on open/close
- Linear-inspired dark mode palette — stepped grayscale elevation (Level 0
#0F0F10base → Level 1#151516content → Level 2#1C1C1Eoverlays → Level 3#242426buttons), off-white body text (#E2E2E2) with0.01emletter-spacing, secondary text at#8A8A8E - Desaturated chart/accent colors ~15% in dark mode to reduce chromostereopsis
- Scroll-to-bottom pill redesigned for dark mode — pill background, border, text color, and drop shadow all switched from hardcoded
rgba()values to CSS variable-based colors (oklch(from var(--background) ...),var(--foreground)) so the pill adapts to both light and dark themes - Scroll-to-bottom pill on mobile — fall back to CSS-only frosted glass (
blur(12px) saturate(1.8)) instead of SVGfeDisplacementMapfilter, which mobile WebKit/Blink don't support (#14) - Scroll-to-bottom animation — replace
scrollIntoView({ behavior: "smooth" })with custom rAF-driven ease-out quart animation; adaptive duration (sqrt-scaled, 160–420ms) for native 120fps momentum feel; ResizeObserver gated during animation to prevent mid-flightscrollTopsnaps - Mobile momentum bounce — inertial scrolling now triggers rubber-band bounce via velocity tracking in the scroll handler; rubber-band curve matches pull-to-refresh (linear to 60px threshold, 0.15x past it); smooth rAF-driven two-phase animation (ease to peak, ease back) replaces instant snap + setTimeout spring-back
- Touch bounce strengthened — multiplier raised from 0.35 to 0.4, spring-back easing switched to PTR's
cubic-bezier(0.22, 0.68, 0.35, 1) - Wheel bounce — uses same
rubberBand()curve as touch/momentum, accumulation cap raised to 400 - Keyboard-open morph overshoot — detect container height changes in
handleScrollto keep morph locked at 0 during keyboard resize, works with third-party keyboards (SwiftKey multi-step resizes) - iOS keyboard offset — skip bogus
innerHeightlag on iOS; rely on viewport resize instead of computing offset - SwiftKey keyboard overshoot — debounce viewport resize handler by 120ms so only the settled value applies
- Morph bar glitch on refresh — suppress morph during initial 600ms after mount to prevent transient
distanceFromBottomspikes from expanding thinking blocks - Thinking blocks render at full height on refresh instead of re-animating the slide-in expansion
- Fade gradients switched from hardcoded
#FAFAFAtovar(--background)for dark mode compatibility
- Ignore worktrees directory in
.gitignore
- Cloudflare Turnstile gate for bot protection — challenges when
NEXT_PUBLIC_TURNSTILE_SITE_KEYis set, server-side verification via/api/verify-turnstile, cached insessionStorage gen_maps.pyscript and generatedmaps.json- Detached mode polish — full transparency for iframe embedding, drop shadow with directional bias, top/bottom fade gradients, rounded chat area,
upload=falsequery param
- Detached mode input bar positioning and padding
- Detached mode rendering without background wrapper (iframe provides container)
- Heartbeat detection tolerant of markdown formatting (strips non-letter chars before comparing)
- Turnstile script not re-injected if already loaded
- Turnstile widget reset on re-renders — stabilize
onVerifiedcallback via ref - Log API returns 200 on write failure (no-op on read-only filesystems like Vercel)
- Debug logging skipped on non-development builds (
debugLog.ts+/api/log) - Removed upload API route (
/api/upload)
- Detached mode for embedding chat widget in iframes (#12) —
?detachedquery param hides header/setup/pull-to-refresh; supports?detached&url=wss://host&token=abcfor auto-connect - ESLint 9 + typescript-eslint with type-aware rules; type checking enforced in build and CI
- Pull-to-refresh hold gesture — requires 1-second hold past threshold before triggering refresh; lobster wobbles during hold with a progress ring on the spinner SVG
- Elastic bottom bounce — rubber-band overscroll effect when scrolling past the bottom (touch and mouse wheel), springs back smoothly on release
- Command response pills — slash commands render as expandable pills with spinner and auto-expand animation
CommandResponsePillcomponent with CSS-onlygridSlideOpenanimation (no JS timing needed)isCommandResponseandisHiddenflags onMessagetype for slash command UXgetEffectiveRunId— maps server run IDs to client placeholder IDs for seamless command response fillingparseServerCommands()— parses/commandstext output into structuredCommand[]formatCommandsText()— renders command groups as human-readable text for local/commandsresponse- Demo mode slash command responses (
/commands,/status,/whoami,/context,/model) with instant delivery --lpCSS variable (layout progress) — deadzone-adjusted--spthat stays 0 until scroll > 5%, prevents subpixel rounding shifts at the start of morph transitions- Message send pop animation (scale 0.6→1.04→1 with spring easing, origin bottom-right)
- GitHub Actions CI workflow (#10)
- Morph bar subpixel jitter — layout-affecting properties (
width,height,gap,padding) now use--lp(deadzone) instead of raw--sp - Pull-to-refresh state stuck after backgrounding app — reset on visibilitychange
- Hidden slash command messages filtered from display loop (no stale timestamps)
- Tool call parts normalized from server history (status, toolCallId, arguments)
- HEARTBEAT_OK now requires its own line to trigger heartbeat handling (#9)
- CI: specify pnpm version in
packageManagerfield (required bypnpm/action-setup@v4)
- Command palette trimmed — reorganized into Session / Options / Status / Skills / More groups with fewer commands
/compactcommand label shown in thinking indicator and tab title- Persist run-active state across page refresh
- Server-echoed user messages and system-injected context handling
- File uploads switched from catbox.moe (permanent) to Litterbox (temporary, 72h expiry)
- Attachment picker accepts all file types (was image-only); non-image files show as named pills
- Attach button icon changed from image to paperclip
- Upload size limit raised to 50MB (was 5MB)
- Native
attachmentsfield sent alongside URL text for vision-capable models - Notifications only fire when app is backgrounded (visibility check was bypassed by debug code)
- Notifications suppressed for slash command and injected responses
- Version API route (
/api/version)
/modelpicker now shows all configured providers including auth-only ones- Strip outermost
<final>tags from assistant message text - rAF scroll-pinning loop during streaming (prevents tool call pills appearing below viewport)
- Renamed page title from "OpenClaw Chat" to "MobileClaw"
- Context pills — expandable dark pills on user messages for system-injected context
- Injected pills — centered expandable pills for heartbeat/no-reply assistant messages
- Message merging — heartbeat/no-reply messages absorb preceding assistant content
- Animated width expand/collapse for context and injected pills
- Quote-reply — select assistant text to quote it into the input (desktop pointer-up + mobile long-press)
- Abort run — stop button in ChatInput sends
chat.abortto OpenClaw - Image attachments — paste, drag-and-drop, or pick images; base64 preview with lightbox
- Image upload API —
/api/uploadproxies to catbox.moe for public image URLs - Image lightbox — full-screen overlay with click/Escape to dismiss
- Floating subagent panel — pinnable panel with live activity feed and swipe-to-dismiss
- Message queue — type while the agent is running; message auto-sends when the run ends
- QueuePill UI with dismiss button (restores text to input)
/compactslash command in command palettehasUnquotedMarkerutility — detects markers outside double-quoted strings (prevents false positives on quoted NO_REPLY text)- Demo mode "long"/"essay" keyword for long-form streaming demo
SlideContentcomponent — reusable CSS grid slide animation extracted from ToolCallPilluseExpandablePanelhook — shared width + height animation logic for expandable pillsuseSwipeActionhook — swipe-left-to-reveal gesture (iOS mail style)lib/constants.ts— shared string constants and tool name helpers- PWA manifest with icons (192px, 512px) and service worker
- Global keydown capture — typing anywhere focuses the input
ImageAttachmenttype andchat.abortWebSocket method
- Service worker only registers on non-localhost (prevents dev caching issues)
- Edit tool pills start expanded when loaded from history (no re-animation)
- Tool pill expand toggle no longer shows for pills with no visible content
- Mobile Enter key detection uses
maxTouchPoints+ UA check (more reliable thanontouchstart) - History enrichment preserves client-side text content (fixes quote-reply newlines lost by server)
- Session reset detection compares against server message count only (prevents false resets from queued messages)
- Subagent history tool results now properly mark tool entries as success/error
- Subagent session status distinguishes new (history-only) vs existing (lifecycle:start) sessions
- Morph bar animation smoothed with exponential lerp (20% per frame) instead of discrete steps
- Draft restore moved to
useEffectto avoid SSR hydration mismatch - Removed duplicate
getTextFromContentin lmStudio.ts (now imported from messageUtils)
- Injected pills left-aligned with
bg-cardstyling (was centered withbg-secondary) - Context pill text uses
text-primary-foreground/70for softer contrast - ChatInput min-height simplified to fixed
46px(wascalcwith--sp) - ChatInput converted to
forwardRefwith imperativesetValuehandle - Send button has three crossfading states: stop, queue, send
@treelocator/runtimebumped to 0.3.1 (lazy-imported in dev)- Notification suppression for heartbeat and no-reply messages
toolDisplay.tsrefactored — sharedgetFilePathhelper,parseArgsexported, tool matching usesconstants.tshelpersToolCallPillacceptsisPinned/onPin/onUnpinprops for subagent pinningChatInputacceptsquoteText,isRunActive,hasQueued,onAbort, and image attachmentsSlideContentextracted fromToolCallPillinto standalone component- Removed
CommandSheetintegration from page (commands handled inline) - Removed mid-stream silence detection timer (replaced by simpler run duration tracking)
ToolIcongains "gear" icon variant for gateway tools
- Subagent history panel — view past subagent runs with full activity feed
- Markdown image rendering in assistant messages
- Subagent activity feed with live streaming status
- Mid-run WebSocket reconnect — resumes in-progress agent streams
- Smooth streaming height transitions (
SmoothGrowcomponent) - Slide-open animations for tool call pills, spawn pills, and thinking blocks
- Edit tool pills auto-expand with inline diff view on mount
- Read tool pills show path directly in title
- Demo mode "edit"/"fix" keyword with read + edit tool call simulation
- Morph bar width growth regression on wider screens
- Tool call pill icons never showing (JSX element always truthy — now checks status explicitly)
scrollbar-hideutility for subagent activity feed
- ThinkingPill rewritten from
<details>to button + state + grid animation - README overhauled with updated architecture and feature documentation
- Streamed text vanishing after tool use events
- WebSocket reconnect state tracking
- Web search and web fetch tool icons updated to globe (#8)
- Model selection dropdown in ChatInput with LM Studio model list
- Custom logo in header and favicon
- Gateway-injected message handling
- One-liner
pnpm prodquick start command
- OpenClaw streaming: device auth flow, tool event parsing, content handling
- Scroll stability improvements during streaming
- Extracted scroll, thinking, theme, and pull-to-refresh into custom hooks (#7)
- Improved model selector UX in ChatInput
- Cleaned up artifact files and reorganized docs
- Dark mode with OKLch color tokens
- Block cursor animation
- Thinking duration tracking and display
- Push notifications when agent completes responding (#5)
- Comprehensive test suite — 63 Vitest tests covering components, utils, handlers (#4)
- Expandable ChatInput textarea with max height constraint (#3)
- Migrated from
next/font/googletogeistpackage (#6) - Refactored WebSocket handler into sub-handlers, consolidated type definitions (#2)
- Improved streaming UX with thinking indicator transitions
- LM Studio backend support with OpenAI-compatible API and SSE streaming
<think>tag parsing for reasoning models- iOS keyboard layout fixes for Safari PWA
- Split monolithic
page.tsxinto modular components and shared types - Demo mode enhanced with richer keyword responses
- Content parts now render in array order
- Demo mode — fully client-side simulation with keyword triggers
- LM Studio backend alongside OpenClaw (#8e736c7)
- Settings dialog with backend selection (OpenClaw / LM Studio / Demo)
- Timestamps on messages
- Merged tool call pills
- Gateway token auth for OpenClaw WebSocket
- Vercel deployment configuration
- Major UI overhaul — settings, timestamps, merged tool pills
- Initial release — mobile-first chat UI for OpenClaw
- WebSocket streaming for OpenClaw API
- Markdown rendering and agent tool stream display
- Setup dialog gating main chat
- Animated dialog open/close transitions
- Morph bar with continuous scroll-based interpolation
- Command sheet with swipe-up and scroll-up detection
- Pill animations with opacity transitions
- PostCSS config for Tailwind CSS v4
- Chat stream rendering issues
- Sheet backdrop interaction on close