diff --git a/docs/releases.md b/docs/releases.md index d053eb9477..343ea5d6bd 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -17,60 +17,60 @@ Maestro can update itself automatically! This feature was introduced in **v0.8.7 **Latest: v0.15.2** | Released March 12, 2026 -# Major 0.15.x Additions - -๐ŸŽถ **Maestro Symphony** โ€” Contribute to open source with AI assistance! Browse curated issues from projects with the `runmaestro.ai` label, clone repos with one click, and automatically process the relevant Auto Run playbooks. Track your contributions, streaks, and stats. You're contributing CPU and tokens towards your favorite open source projects and features. - -๐ŸŽฌ **Director's Notes** โ€” Aggregates history across all agents into a unified timeline with search, filters, and an activity graph. Includes an AI Overview tab that generates a structured synopsis of recent work. Off by default, gated behind a new "Encore Features" panel under settings. This is a precursor to an eventual plugin system, allowing for extensions and customizations without bloating the core app. - -๐Ÿท๏ธ **Conductor Profile** โ€” Available under Settings > General. Provide a short description on how Maestro agents should interface with you. - -๐Ÿง  **Three-State Thinking Toggle** โ€” The thinking toggle now cycles through three modes: off, on, and sticky. Sticky mode keeps thinking content visible after the response completes. Cycle with CMD/CTRL+SHIFT+K. - -๐Ÿค– **Factory.ai Droid Support** โ€” Added support for the [Factory.ai](https://factory.ai/product/cli) droid agent. Full session management and output parsing integration. - -## Change in v0.15.2 - -Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 RC. - -### New Features - -- **Cmd+0 โ†’ Last Tab:** Remapped Cmd+0 to jump to last tab; Cmd+Shift+0 now resets font size -- **Unsent draft protection:** Confirm dialog before closing tabs with unsent draft input -- **Read-only CLI flag:** Added `--read-only` flag to `maestro-cli send` command -- **Gemini read-only enforcement:** Gemini `-y` flag now works in read-only mode -- **Capability-based providers:** Replaced hardcoded agent ID checks with capability flags and shared metadata - -### Bug Fixes - -- **Sticky overlay scroll:** Fixed sticky overlays breaking tab scroll-into-view -- **Director's Notes stats:** Count only agents with entries in lookback window -- **SSH remote config:** Check `sessionSshRemoteConfig` as primary SSH remote ID source -- **.maestro file tree:** Always show .maestro directory even when dotfiles are hidden -- **Provider hardening:** Prototype safety, capability gates, stale map cleanup -- **Session search:** Per-session error resilience and metadata-based title matching -- **File tree stale loads:** Load sequence counter prevents stale file tree updates -- **File tree Unicode:** NFC normalization prevents duplicate entries -- **File tree duplicates:** Tree-structured data resolves duplicate entries -- **File tree auto-refresh:** Timer no longer destroyed on right panel tab switch -- **Menu z-index:** Branding header menu renders above sidebar content -- **Dropdown clipping:** Fixed hamburger menu and live overlay dropdown clipping -- **Font size shortcuts:** Restored Cmd+/- font size shortcuts lost with custom menu -- **Draft input preservation:** Replaying a previous message no longer discards current draft -- **SSH directory collision:** Skip warning when agents are on different SSH hosts -- **IPC error handling:** Handle expected IPC errors gracefully -- **Auto-focus on mode switch:** Input field auto-focuses when toggling AI/Shell mode -- **OpenCode parser:** Preserve JSON error events; reset resultEmitted on step_start -- **NDJSON performance:** Eliminated triple JSON parsing on hot path -- **Agent config overrides:** Apply config overrides in context groomer before spawning -- **Stale closure fix:** Resolved model not saving in wizard agent config - -### Visual Polish - -- **Light theme contrast:** Improved syntax highlighting contrast across all light themes -- **Context warning sash:** Dark text colors in light mode for readability -- **Session name dimming:** Use `textMain` color to prevent visual dimming -- **Session name pill:** Allow shrinking so date doesn't collide with type pill +### Major 0.15.x Additions + +๐ŸŽถ **Maestro Symphony** โ€” Contribute to open source with AI assistance! Browse curated issues from projects with the `runmaestro.ai` label, clone repos with one click, and automatically process the relevant Auto Run playbooks. Track your contributions, streaks, and stats. You're contributing CPU and tokens towards your favorite open source projects and features. + +๐ŸŽฌ **Director's Notes** โ€” Aggregates history across all agents into a unified timeline with search, filters, and an activity graph. Includes an AI Overview tab that generates a structured synopsis of recent work. Off by default, gated behind a new "Encore Features" panel under settings. This is a precursor to an eventual plugin system, allowing for extensions and customizations without bloating the core app. + +๐Ÿท๏ธ **Conductor Profile** โ€” Available under Settings > General. Provide a short description on how Maestro agents should interface with you. + +๐Ÿง  **Three-State Thinking Toggle** โ€” The thinking toggle now cycles through three modes: off, on, and sticky. Sticky mode keeps thinking content visible after the response completes. Cycle with CMD/CTRL+SHIFT+K. + +๐Ÿค– **Factory.ai Droid Support** โ€” Added support for the [Factory.ai](https://factory.ai/product/cli) droid agent. Full session management and output parsing integration. + +## Change in v0.15.2 + +Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 RC. + +### New Features + +- **Cmd+0 โ†’ Last Tab:** Remapped Cmd+0 to jump to last tab; Cmd+Shift+0 now resets font size +- **Unsent draft protection:** Confirm dialog before closing tabs with unsent draft input +- **Read-only CLI flag:** Added `--read-only` flag to `maestro-cli send` command +- **Gemini read-only enforcement:** Gemini `-y` flag now works in read-only mode +- **Capability-based providers:** Replaced hardcoded agent ID checks with capability flags and shared metadata + +### Bug Fixes + +- **Sticky overlay scroll:** Fixed sticky overlays breaking tab scroll-into-view +- **Director's Notes stats:** Count only agents with entries in lookback window +- **SSH remote config:** Check `sessionSshRemoteConfig` as primary SSH remote ID source +- **.maestro file tree:** Always show .maestro directory even when dotfiles are hidden +- **Provider hardening:** Prototype safety, capability gates, stale map cleanup +- **Session search:** Per-session error resilience and metadata-based title matching +- **File tree stale loads:** Load sequence counter prevents stale file tree updates +- **File tree Unicode:** NFC normalization prevents duplicate entries +- **File tree duplicates:** Tree-structured data resolves duplicate entries +- **File tree auto-refresh:** Timer no longer destroyed on right panel tab switch +- **Menu z-index:** Branding header menu renders above sidebar content +- **Dropdown clipping:** Fixed hamburger menu and live overlay dropdown clipping +- **Font size shortcuts:** Restored Cmd+/- font size shortcuts lost with custom menu +- **Draft input preservation:** Replaying a previous message no longer discards current draft +- **SSH directory collision:** Skip warning when agents are on different SSH hosts +- **IPC error handling:** Handle expected IPC errors gracefully +- **Auto-focus on mode switch:** Input field auto-focuses when toggling AI/Shell mode +- **OpenCode parser:** Preserve JSON error events; reset resultEmitted on step_start +- **NDJSON performance:** Eliminated triple JSON parsing on hot path +- **Agent config overrides:** Apply config overrides in context groomer before spawning +- **Stale closure fix:** Resolved model not saving in wizard agent config + +### Visual Polish + +- **Light theme contrast:** Improved syntax highlighting contrast across all light themes +- **Context warning sash:** Dark text colors in light mode for readability +- **Session name dimming:** Use `textMain` color to prevent visual dimming +- **Session name pill:** Allow shrinking so date doesn't collide with type pill - **Scroll-to-bottom arrow:** Removed noisy indicator from terminal output view ### Previous Releases in this Series @@ -83,41 +83,41 @@ Patch release with bug fixes, UX improvements, and cherry-picks from the 0.16.0 **Latest: v0.14.5** | Released January 24, 2026 -Changes in this point release include: - -- Desktop app performance improvements (more to come on this, we want Maestro blazing fast) ๐ŸŒ -- Added local manifest feature for custom playbooks ๐Ÿ“– -- Agents are now inherently aware of your activity history as seen in the history panel ๐Ÿ“œ (this is built-in cross context memory!) -- Added markdown rendering support for AI responses in mobile view ๐Ÿ“ฑ -- Bugfix in tracking costs from JSONL files that were aged out ๐Ÿฆ -- Added BlueSky social media handle for leaderboard ๐Ÿฆ‹ -- Added options to disable GPU rendering and confetti ๐ŸŽŠ -- Better handling of large files in preview ๐Ÿ—„๏ธ -- Bug fix in Claude context calculation ๐Ÿงฎ -- Addressed bug in OpenSpec version reporting ๐Ÿ› - -The major contributions to 0.14.x remain: - -๐Ÿ—„๏ธ Document Graphs. Launch from file preview or from the FIle tree panel. Explore relationships between Markdown documents that contain links between documents and to URLs. - -๐Ÿ“ถ SSH support for agents. Manage a remote agent with feature parity over SSH. Includes support for Git and File tree panels. Manage agents on remote systems or in containers. This even works for Group Chat, which is rad as hell. - -๐Ÿง™โ€โ™‚๏ธ Added an in-tab wizard for generating Auto Run Playbooks via `/wizard` or a new button in the Auto Run panel. - -# Smaller Changes in 014.x - -- Improved User Dashboard, available from hamburger menu, command palette or hotkey ๐ŸŽ›๏ธ -- Leaderboard tracking now works across multiple systems and syncs level from cloud ๐Ÿ† -- Agent duplication. Pro tip: Consider a group of unused "Template" agents โœŒ๏ธ -- New setting to prevent system from going to sleep while agents are active ๐Ÿ›๏ธ -- The tab menu has a new "Publish as GitHub Gist" option ๐Ÿ“ -- The tab menu has options to move the tab to the first or last position ๐Ÿ”€ -- [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) can now contain non-markdown assets ๐Ÿ“™ -- Improved default shell detection ๐Ÿš -- Added logic to prevent overlapping TTS notifications ๐Ÿ’ฌ -- Added "Toggle Bookmark" shortcut (CTRL/CMD+SHIFT+B) โŒจ๏ธ -- Gist publishing now shows previous URLs with copy button ๐Ÿ“‹ - +Changes in this point release include: + +- Desktop app performance improvements (more to come on this, we want Maestro blazing fast) ๐ŸŒ +- Added local manifest feature for custom playbooks ๐Ÿ“– +- Agents are now inherently aware of your activity history as seen in the history panel ๐Ÿ“œ (this is built-in cross-context memory!) +- Added markdown rendering support for AI responses in mobile view ๐Ÿ“ฑ +- Bugfix in tracking costs from JSONL files that were aged out ๐Ÿฆ +- Added BlueSky social media handle for leaderboard ๐Ÿฆ‹ +- Added options to disable GPU rendering and confetti ๐ŸŽŠ +- Better handling of large files in preview ๐Ÿ—„๏ธ +- Bug fix in Claude context calculation ๐Ÿงฎ +- Addressed bug in OpenSpec version reporting ๐Ÿ› + +The major contributions to 0.14.x remain: + +๐Ÿ—„๏ธ Document Graphs. Launch from file preview or from the FIle tree panel. Explore relationships between Markdown documents that contain links between documents and to URLs. + +๐Ÿ“ถ SSH support for agents. Manage a remote agent with feature parity over SSH. Includes support for Git and File tree panels. Manage agents on remote systems or in containers. This even works for Group Chat, which is rad as hell. + +๐Ÿง™โ€โ™‚๏ธ Added an in-tab wizard for generating Auto Run Playbooks via `/wizard` or a new button in the Auto Run panel. + +### Smaller Changes in 0.14.x + +- Improved User Dashboard, available from hamburger menu, command palette or hotkey ๐ŸŽ›๏ธ +- Leaderboard tracking now works across multiple systems and syncs level from cloud ๐Ÿ† +- Agent duplication. Pro tip: Consider a group of unused "Template" agents โœŒ๏ธ +- New setting to prevent system from going to sleep while agents are active ๐Ÿ›๏ธ +- The tab menu has a new "Publish as GitHub Gist" option ๐Ÿ“ +- The tab menu has options to move the tab to the first or last position ๐Ÿ”€ +- [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) can now contain non-markdown assets ๐Ÿ“™ +- Improved default shell detection ๐Ÿš +- Added logic to prevent overlapping TTS notifications ๐Ÿ’ฌ +- Added "Toggle Bookmark" shortcut (CTRL/CMD+SHIFT+B) โŒจ๏ธ +- Gist publishing now shows previous URLs with copy button ๐Ÿ“‹ + Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @deandebeer @shadown @breki @charles-dyfis-net @ronaldeddings @jlengrand @ksylvan ### Previous Releases in this Series @@ -136,20 +136,22 @@ Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @d ### Changes -- TAKE TWO! Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ - -### v0.13.1 Changes -- Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ -- Enhanced error handling for Auto Run batch processing ๐Ÿšจ - -### v0.13.0 Changes -- Added a global usage dashboard, data collection begins with this install ๐ŸŽ›๏ธ -- Added a Playbook Exchange for downloading pre-defined Auto Run playbooks from [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) ๐Ÿ“• -- Bundled OpenSpec commands for structured change proposals ๐Ÿ“ -- Added pre-release channel support for beta/RC updates ๐Ÿงช -- Implemented global hands-on time tracking across sessions โฑ๏ธ -- Added new keyboard shortcut for agent settings (Opt+Cmd+, | Ctrl+Alt+,) โŒจ๏ธ -- Added directory size calculation with file/folder counts in file explorer ๐Ÿ“Š +- TAKE TWO! Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ + +### v0.13.1 Changes + +- Fixed Linux ARM64 build architecture contamination issues ๐Ÿ—๏ธ +- Enhanced error handling for Auto Run batch processing ๐Ÿšจ + +### v0.13.0 Changes + +- Added a global usage dashboard, data collection begins with this install ๐ŸŽ›๏ธ +- Added a Playbook Exchange for downloading pre-defined Auto Run playbooks from [Maestro-Playbooks](https://github.com/pedramamini/Maestro-Playbooks) ๐Ÿ“• +- Bundled OpenSpec commands for structured change proposals ๐Ÿ“ +- Added pre-release channel support for beta/RC updates ๐Ÿงช +- Implemented global hands-on time tracking across sessions โฑ๏ธ +- Added new keyboard shortcut for agent settings (Opt+Cmd+, | Ctrl+Alt+,) โŒจ๏ธ +- Added directory size calculation with file/folder counts in file explorer ๐Ÿ“Š - Added sleep detection to exclude laptop sleep from time tracking โฐ ### Previous Releases in this Series @@ -163,22 +165,26 @@ Thanks for the contributions: @t1mmen @aejfager @Crumbgrabber @whglaser @b3nw @d **Latest: v0.12.3** | Released December 28, 2025 -The big changes in the v0.12.x line are the following three: - -## Show Thinking -๐Ÿค” There is now a toggle to show thinking for the agent, the default for new tabs is off, though this can be changed under Settings > General. The toggle shows next to History and Read-Only. Very similar pattern. This has been the #1 most requested feature, though personally, I don't think I'll use it as I prefer to not see the details of the work, but the results of the work. Just as we work with our colleagues. - -## GitHub Spec-Kit Integration -๐ŸŽฏ Added [GitHub Spec-Kit](https://github.com/github/spec-kit) commands into Maestro with a built in updater to grab the latest prompts from the repository. We do override `/speckit-implement` (the final step) to create Auto Run docs and guide the user through their execution, which thanks to Wortrees from v0.11.x allows us to run in parallel! - -## Context Management Tools -๐Ÿ“– Added context management options from tab right-click menu. You can now compress, merge, and transfer contexts between agents. You will received (configurable) warnings at 60% and 80% context consumption with a hint to compact. - -## Changes Specific to v0.12.3: -- We now have hosted documentation through Mintlify ๐Ÿ“š -- Export any tab conversation as self-contained themed HTML file ๐Ÿ“„ -- Publish files as private/public Gists ๐ŸŒ -- Added tab hover overlay menu with close operations and export ๐Ÿ“‹ +The big changes in the v0.12.x line are the following three: + +## Show Thinking + +๐Ÿค” There is now a toggle to show thinking for the agent, the default for new tabs is off, though this can be changed under Settings > General. The toggle shows next to History and Read-Only. Very similar pattern. This has been the #1 most requested feature, though personally, I don't think I'll use it as I prefer to not see the details of the work, but the results of the work. Just as we work with our colleagues. + +## GitHub Spec-Kit Integration + +๐ŸŽฏ Added [GitHub Spec-Kit](https://github.com/github/spec-kit) commands into Maestro with a built-in updater to grab the latest prompts from the repository. We do override `/speckit-implement` (the final step) to create Auto Run docs and guide the user through their execution, which thanks to Worktrees from v0.11.x allows us to run in parallel! + +## Context Management Tools + +๐Ÿ“– Added context management options from tab right-click menu. You can now compress, merge, and transfer contexts between agents. You will receive (configurable) warnings at 60% and 80% context consumption with a hint to compact. + +## Changes Specific to v0.12.3: + +- We now have hosted documentation through Mintlify ๐Ÿ“š +- Export any tab conversation as self-contained themed HTML file ๐Ÿ“„ +- Publish files as private/public Gists ๐ŸŒ +- Added tab hover overlay menu with close operations and export ๐Ÿ“‹ - Added social handles to achievement share images ๐Ÿ† ### Previous Releases in this Series @@ -192,12 +198,12 @@ The big changes in the v0.12.x line are the following three: **Latest: v0.11.0** | Released December 22, 2025 -๐ŸŒณ Github Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to @petersilberman. - -# Other Changes - -- @ file mentions now include documents from your Auto Run folder (which may not live in your agent working directory) ๐Ÿ—„๏ธ -- The wizard is now capable of detecting and continuing on past started projects ๐Ÿง™ +๐ŸŒณ GitHub Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to @petersilberman. + +### Other Changes + +- @ file mentions now include documents from your Auto Run folder (which may not live in your agent working directory) ๐Ÿ—„๏ธ +- The wizard is now capable of detecting and continuing on past started projects ๐Ÿง™ - Bug fixes ๐Ÿ›๐Ÿœ๐Ÿž --- @@ -208,14 +214,14 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Export group chats as self-contained HTML โฌ‡๏ธ -- Enhanced system process viewer now has details view with full process args ๐Ÿ’ป -- Update button hides until platform binaries are available in releases. โณ -- Added Auto Run stall detection at the loop level, if no documents are updated after a loop ๐Ÿ” -- Improved Codex session discovery ๐Ÿ” -- Windows compatibility fixes ๐Ÿ› -- 64-bit Linux ARM build issue fixed (thanks @LilYoopug) ๐Ÿœ -- Addressed session enumeration issues with Codex and OpenCode ๐Ÿž +- Export group chats as self-contained HTML โฌ‡๏ธ +- Enhanced system process viewer now has details view with full process args ๐Ÿ’ป +- Update button hides until platform binaries are available in releases. โณ +- Added Auto Run stall detection at the loop level, if no documents are updated after a loop ๐Ÿ” +- Improved Codex session discovery ๐Ÿ” +- Windows compatibility fixes ๐Ÿ› +- 64-bit Linux ARM build issue fixed (thanks @LilYoopug) ๐Ÿœ +- Addressed session enumeration issues with Codex and OpenCode ๐Ÿž - Addressed pathing issues around gh command (thanks @oliveiraantoniocc) ๐Ÿ ### Previous Releases in this Series @@ -231,13 +237,13 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Add Sentry crashing reporting monitoring with opt-out ๐Ÿ› -- Stability fixes on v0.9.0 along with all the changes it brought along, including... - - Major refactor to enable supporting of multiple providers ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ - - Added OpenAI Codex support ๐Ÿ‘จโ€๐Ÿ’ป - - Added OpenCode support ๐Ÿ‘ฉโ€๐Ÿ’ป - - Error handling system detects and recovers from agent failures ๐Ÿšจ - - Added option to specify CLI arguments to AI providers โœจ +- Add Sentry crashing reporting monitoring with opt-out ๐Ÿ› +- Stability fixes on v0.9.0 along with all the changes it brought along, including... + - Major refactor to enable supporting of multiple providers ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ + - Added OpenAI Codex support ๐Ÿ‘จโ€๐Ÿ’ป + - Added OpenCode support ๐Ÿ‘ฉโ€๐Ÿ’ป + - Error handling system detects and recovers from agent failures ๐Ÿšจ + - Added option to specify CLI arguments to AI providers โœจ - Bunch of other little tweaks and additions ๐Ÿ’Ž ### Previous Releases in this Series @@ -252,19 +258,19 @@ The big changes in the v0.12.x line are the following three: ### Changes -- Added "Nudge" messages. Short static copy to include with every interactive message sent, perhaps to remind the agent on how to work ๐Ÿ“Œ -- Addressed various resource consumption issues to reduce battery cost ๐Ÿ“‰ -- Implemented fuzzy file search in quick actions for instant navigation ๐Ÿ” -- Added "clear" command support to clean terminal shell logs ๐Ÿงน -- Simplified search highlighting by integrating into markdown pipeline โœจ -- Enhanced update checker to filter prerelease tags like -rc, -beta ๐Ÿš€ -- Fixed RPM package compatibility for OpenSUSE Tumbleweed ๐Ÿง (H/T @JOduMonT) -- Added libuuid1 support alongside standard libuuid dependency ๐Ÿ“ฆ -- Introduced Cmd+Shift+U shortcut for tab unread toggle โŒจ๏ธ -- Enhanced keyboard navigation for marking tabs unread ๐ŸŽฏ -- Expanded Linux distribution support with smart dependencies ๐ŸŒ -- Major underlying code re-structuring for maintainability ๐Ÿงน -- Improved stall detection to allow for individual docs to stall out while not affecting the entire playbook ๐Ÿ“– (H/T @mattjay) +- Added "Nudge" messages. Short static copy to include with every interactive message sent, perhaps to remind the agent on how to work ๐Ÿ“Œ +- Addressed various resource consumption issues to reduce battery cost ๐Ÿ“‰ +- Implemented fuzzy file search in quick actions for instant navigation ๐Ÿ” +- Added "clear" command support to clean terminal shell logs ๐Ÿงน +- Simplified search highlighting by integrating into markdown pipeline โœจ +- Enhanced update checker to filter pre-release tags like -rc, -beta ๐Ÿš€ +- Fixed RPM package compatibility for OpenSUSE Tumbleweed ๐Ÿง (H/T @JOduMonT) +- Added libuuid1 support alongside standard libuuid dependency ๐Ÿ“ฆ +- Introduced Cmd+Shift+U shortcut for tab unread toggle โŒจ๏ธ +- Enhanced keyboard navigation for marking tabs unread ๐ŸŽฏ +- Expanded Linux distribution support with smart dependencies ๐ŸŒ +- Major underlying code re-structuring for maintainability ๐Ÿงน +- Improved stall detection to allow for individual docs to stall out while not affecting the entire playbook ๐Ÿ“– (H/T @mattjay) - Added option to select a static listening port for remote control ๐ŸŽฎ (H/T @b3nw) ### Previous Releases in this Series @@ -284,35 +290,40 @@ The big changes in the v0.12.x line are the following three: **Latest: v0.7.4** | Released December 12, 2025 -Minor bugfixes on top of v0.7.3: - -# Onboarding, Wizard, and Tours -- Implemented comprehensive onboarding wizard with integrated tour system ๐Ÿš€ -- Added project-understanding confidence display to wizard UI ๐ŸŽจ -- Enhanced keyboard navigation across all wizard screens โŒจ๏ธ -- Added analytics tracking for wizard and tour completion ๐Ÿ“ˆ -- Added First Run Celebration modal with confetti animation ๐ŸŽ‰ - -# UI / UX Enhancements -- Added expand-to-fullscreen button for Auto Run interface ๐Ÿ–ฅ๏ธ -- Created dedicated modal component and improved modal priority constants for expanded Auto Run view ๐Ÿ“ -- Enhanced user experience with fullscreen editing capabilities โœจ -- Fixed tab name display to correctly show full name for active tabs ๐Ÿท๏ธ -- Added performance optimizations with throttling and caching for scrolling โšก -- Implemented drag-and-drop reordering for execution queue items ๐ŸŽฏ -- Enhanced toast context with agent name for OS notifications ๐Ÿ“ข - -# Auto Run Workflow Improvements -- Created phase document generation for Auto Run workflow ๐Ÿ“„ -- Added real-time log streaming to the LogViewer component ๐Ÿ“Š - -# Application Behavior / Core Fixes -- Added validation to prevent nested worktrees inside the main repository ๐Ÿšซ -- Fixed process manager to properly emit exit events on errors ๐Ÿ”ง -- Fixed process exit handling to ensure proper cleanup ๐Ÿงน - -# Update System -- Implemented automatic update checking on application startup ๐Ÿš€ +Minor bugfixes on top of v0.7.3: + +### Onboarding, Wizard, and Tours + +- Implemented comprehensive onboarding wizard with integrated tour system ๐Ÿš€ +- Added project-understanding confidence display to wizard UI ๐ŸŽจ +- Enhanced keyboard navigation across all wizard screens โŒจ๏ธ +- Added analytics tracking for wizard and tour completion ๐Ÿ“ˆ +- Added First Run Celebration modal with confetti animation ๐ŸŽ‰ + +### UI / UX Enhancements + +- Added expand-to-fullscreen button for Auto Run interface ๐Ÿ–ฅ๏ธ +- Created dedicated modal component and improved modal priority constants for expanded Auto Run view ๐Ÿ“ +- Enhanced user experience with fullscreen editing capabilities โœจ +- Fixed tab name display to correctly show full name for active tabs ๐Ÿท๏ธ +- Added performance optimizations with throttling and caching for scrolling โšก +- Implemented drag-and-drop reordering for execution queue items ๐ŸŽฏ +- Enhanced toast context with agent name for OS notifications ๐Ÿ“ข + +### Auto Run Workflow Improvements + +- Created phase document generation for Auto Run workflow ๐Ÿ“„ +- Added real-time log streaming to the LogViewer component ๐Ÿ“Š + +### Application Behavior / Core Fixes + +- Added validation to prevent nested worktrees inside the main repository ๐Ÿšซ +- Fixed process manager to properly emit exit events on errors ๐Ÿ”ง +- Fixed process exit handling to ensure proper cleanup ๐Ÿงน + +### Update System + +- Implemented automatic update checking on application startup ๐Ÿš€ - Added settings toggle for enabling/disabling startup update checks โš™๏ธ ### Previous Releases in this Series @@ -328,38 +339,40 @@ Minor bugfixes on top of v0.7.3: **Latest: v0.6.1** | Released December 4, 2025 -In this release... -- Added recursive subfolder support for Auto Run markdown files ๐Ÿ—‚๏ธ -- Enhanced document tree display with expandable folder navigation ๐ŸŒณ -- Enabled creating documents in subfolders with path selection ๐Ÿ“ -- Improved batch runner UI with inline progress bars and loop indicators ๐Ÿ“Š -- Fixed execution queue display bug for immediate command processing ๐Ÿ› -- Added folder icons and better visual hierarchy for document browser ๐ŸŽจ -- Implemented dynamic task re-counting for batch run loop iterations ๐Ÿ”„ -- Enhanced create document modal with location selector dropdown ๐Ÿ“ -- Improved progress tracking with per-document completion visualization ๐Ÿ“ˆ -- Added support for nested folder structures in document management ๐Ÿ—๏ธ - -Plus the pre-release ALPHA... -- Template vars now set context in default autorun prompt ๐Ÿš€ -- Added Enter key support for queued message confirmation dialog โŒจ๏ธ -- Kill process capability added to System Process Monitor ๐Ÿ’€ -- Toggle markdown rendering added to Cmd+K Quick Actions ๐Ÿ“ -- Fixed cloudflared detection in packaged app environments ๐Ÿ”ง -- Added debugging logs for process exit diagnostics ๐Ÿ› -- Tab switcher shows last activity timestamps and filters by project ๐Ÿ• -- Slash commands now fill text on Tab/Enter instead of executing โšก -- Added GitHub Actions workflow for auto-assigning issues/PRs ๐Ÿค– -- Graceful handling for playbooks with missing documents implemented โœจ -- Added multi-document batch processing for Auto Run ๐Ÿš€ -- Introduced Git worktree support for parallel execution ๐ŸŒณ -- Created playbook system for saving run configurations ๐Ÿ“š -- Implemented document reset-on-completion with loop mode ๐Ÿ”„ -- Added drag-and-drop document reordering interface ๐ŸŽฏ -- Built Auto Run folder selector with file management ๐Ÿ“ -- Enhanced progress tracking with per-document metrics ๐Ÿ“Š -- Integrated PR creation after worktree completion ๐Ÿ”€ -- Added undo/redo support in document editor โ†ฉ๏ธ +In this release... + +- Added recursive subfolder support for Auto Run markdown files ๐Ÿ—‚๏ธ +- Enhanced document tree display with expandable folder navigation ๐ŸŒณ +- Enabled creating documents in subfolders with path selection ๐Ÿ“ +- Improved batch runner UI with inline progress bars and loop indicators ๐Ÿ“Š +- Fixed execution queue display bug for immediate command processing ๐Ÿ› +- Added folder icons and better visual hierarchy for document browser ๐ŸŽจ +- Implemented dynamic task re-counting for batch run loop iterations ๐Ÿ”„ +- Enhanced create document modal with location selector dropdown ๐Ÿ“ +- Improved progress tracking with per-document completion visualization ๐Ÿ“ˆ +- Added support for nested folder structures in document management ๐Ÿ—๏ธ + +Plus the pre-release ALPHA... + +- Template vars now set context in default autorun prompt ๐Ÿš€ +- Added Enter key support for queued message confirmation dialog โŒจ๏ธ +- Kill process capability added to System Process Monitor ๐Ÿ’€ +- Toggle markdown rendering added to Cmd+K Quick Actions ๐Ÿ“ +- Fixed cloudflared detection in packaged app environments ๐Ÿ”ง +- Added debugging logs for process exit diagnostics ๐Ÿ› +- Tab switcher shows last activity timestamps and filters by project ๐Ÿ• +- Slash commands now fill text on Tab/Enter instead of executing โšก +- Added GitHub Actions workflow for auto-assigning issues/PRs ๐Ÿค– +- Graceful handling for playbooks with missing documents implemented โœจ +- Added multi-document batch processing for Auto Run ๐Ÿš€ +- Introduced Git worktree support for parallel execution ๐ŸŒณ +- Created playbook system for saving run configurations ๐Ÿ“š +- Implemented document reset-on-completion with loop mode ๐Ÿ”„ +- Added drag-and-drop document reordering interface ๐ŸŽฏ +- Built Auto Run folder selector with file management ๐Ÿ“ +- Enhanced progress tracking with per-document metrics ๐Ÿ“Š +- Integrated PR creation after worktree completion ๐Ÿ”€ +- Added undo/redo support in document editor โ†ฉ๏ธ - Implemented auto-save with 5-second debounce ๐Ÿ’พ ### Previous Releases in this Series @@ -374,15 +387,15 @@ Plus the pre-release ALPHA... ### Changes -- Added "Made with Maestro" badge to README header ๐ŸŽฏ -- Redesigned app icon with darker purple color scheme ๐ŸŽจ -- Created new SVG badge for project attribution ๐Ÿท๏ธ -- Added side-by-side image diff viewer for git changes ๐Ÿ–ผ๏ธ -- Enhanced confetti animation with realistic cannon-style bursts ๐ŸŽŠ -- Fixed z-index layering for standing ovation overlay ๐Ÿ“Š -- Improved tab switcher to show all named sessions ๐Ÿ” -- Enhanced batch synopsis prompts for cleaner summaries ๐Ÿ“ -- Added binary file detection in git diff parser ๐Ÿ”ง +- Added "Made with Maestro" badge to README header ๐ŸŽฏ +- Redesigned app icon with darker purple color scheme ๐ŸŽจ +- Created new SVG badge for project attribution ๐Ÿท๏ธ +- Added side-by-side image diff viewer for git changes ๐Ÿ–ผ๏ธ +- Enhanced confetti animation with realistic cannon-style bursts ๐ŸŽŠ +- Fixed z-index layering for standing ovation overlay ๐Ÿ“Š +- Improved tab switcher to show all named sessions ๐Ÿ” +- Enhanced batch synopsis prompts for cleaner summaries ๐Ÿ“ +- Added binary file detection in git diff parser ๐Ÿ”ง - Implemented git file reading at specific refs ๐Ÿ“ ### Previous Releases in this Series @@ -397,24 +410,24 @@ Plus the pre-release ALPHA... ### Changes -- Added Tab Switcher modal for quick navigation between AI tabs ๐Ÿš€ -- Implemented @ mention file completion for AI mode references ๐Ÿ“ -- Added navigation history with back/forward through sessions and tabs โฎ๏ธ -- Introduced tab completion filters for branches, tags, and files ๐ŸŒณ -- Added unread tab indicators and filtering for better organization ๐Ÿ“ฌ -- Implemented token counting display with human-readable formatting ๐Ÿ”ข -- Added markdown rendering toggle for AI responses in terminal ๐Ÿ“ -- Removed built-in slash commands in favor of custom AI commands ๐ŸŽฏ -- Added context menu for sessions with rename, bookmark, move options ๐Ÿ–ฑ๏ธ -- Enhanced file preview with stats showing size, tokens, timestamps ๐Ÿ“Š -- Added token counting with js-tiktoken for file preview stats bar ๐Ÿ”ข -- Implemented Tab Switcher modal for fuzzy-search navigation (Opt+Cmd+T) ๐Ÿ” -- Added Save to History toggle (Cmd+S) for automatic work synopsis tracking ๐Ÿ’พ -- Enhanced tab completion with @ mentions for file references in AI prompts ๐Ÿ“Ž -- Implemented navigation history with back/forward shortcuts (Cmd+Shift+,/.) ๐Ÿ”™ -- Added git branches and tags to intelligent tab completion system ๐ŸŒฟ -- Enhanced markdown rendering with syntax highlighting and toggle view ๐Ÿ“ -- Added right-click context menus for session management and organization ๐Ÿ–ฑ๏ธ +- Added Tab Switcher modal for quick navigation between AI tabs ๐Ÿš€ +- Implemented @ mention file completion for AI mode references ๐Ÿ“ +- Added navigation history with back/forward through sessions and tabs โฎ๏ธ +- Introduced tab completion filters for branches, tags, and files ๐ŸŒณ +- Added unread tab indicators and filtering for better organization ๐Ÿ“ฌ +- Implemented token counting display with human-readable formatting ๐Ÿ”ข +- Added markdown rendering toggle for AI responses in terminal ๐Ÿ“ +- Removed built-in slash commands in favor of custom AI commands ๐ŸŽฏ +- Added context menu for sessions with rename, bookmark, move options ๐Ÿ–ฑ๏ธ +- Enhanced file preview with stats showing size, tokens, timestamps ๐Ÿ“Š +- Added token counting with js-tiktoken for file preview stats bar ๐Ÿ”ข +- Implemented Tab Switcher modal for fuzzy-search navigation (Opt+Cmd+T) ๐Ÿ” +- Added Save to History toggle (Cmd+S) for automatic work synopsis tracking ๐Ÿ’พ +- Enhanced tab completion with @ mentions for file references in AI prompts ๐Ÿ“Ž +- Implemented navigation history with back/forward shortcuts (Cmd+Shift+,/.) ๐Ÿ”™ +- Added git branches and tags to intelligent tab completion system ๐ŸŒฟ +- Enhanced markdown rendering with syntax highlighting and toggle view ๐Ÿ“ +- Added right-click context menus for session management and organization ๐Ÿ–ฑ๏ธ - Improved mobile app with better WebSocket reconnection and status badges ๐Ÿ“ฑ ### Previous Releases in this Series @@ -429,15 +442,15 @@ Plus the pre-release ALPHA... ### Changes -- Fixed tab handling requiring explicitly selected Claude session ๐Ÿ”ง -- Added auto-scroll navigation for slash command list selection โšก -- Implemented TTS audio feedback for toast notifications speak ๐Ÿ”Š -- Fixed shortcut case sensitivity using lowercase key matching ๐Ÿ”ค -- Added Cmd+Shift+J shortcut to jump to bottom instantly โฌ‡๏ธ -- Sorted shortcuts alphabetically in help modal for discovery ๐Ÿ“‘ -- Display full commit message body in git log view ๐Ÿ“ -- Added expand/collapse all buttons to process tree header ๐ŸŒณ -- Support synopsis process type in process tree parsing ๐Ÿ” +- Fixed tab handling requiring explicitly selected Claude session ๐Ÿ”ง +- Added auto-scroll navigation for slash command list selection โšก +- Implemented TTS audio feedback for toast notifications speak ๐Ÿ”Š +- Fixed shortcut case sensitivity using lowercase key matching ๐Ÿ”ค +- Added Cmd+Shift+J shortcut to jump to bottom instantly โฌ‡๏ธ +- Sorted shortcuts alphabetically in help modal for discovery ๐Ÿ“‘ +- Display full commit message body in git log view ๐Ÿ“ +- Added expand/collapse all buttons to process tree header ๐ŸŒณ +- Support synopsis process type in process tree parsing ๐Ÿ” - Renamed "No Group" to "UNGROUPED" for better clarity โœจ ### Previous Releases in this Series @@ -450,15 +463,15 @@ Plus the pre-release ALPHA... **Latest: v0.2.3** | Released November 29, 2025 -โ€ข Enhanced mobile web interface with session sync and history panel ๐Ÿ“ฑ -โ€ข Added ThinkingStatusPill showing real-time token counts and elapsed time โฑ๏ธ -โ€ข Implemented task count badges and session deduplication for batch runner ๐Ÿ“Š -โ€ข Added TTS stop control and improved voice synthesis compatibility ๐Ÿ”Š -โ€ข Created image lightbox with navigation, clipboard, and delete features ๐Ÿ–ผ๏ธ -โ€ข Fixed UI bugs in search, auto-scroll, and sidebar interactions ๐Ÿ› -โ€ข Added global Claude stats with streaming updates across projects ๐Ÿ“ˆ -โ€ข Improved markdown checkbox styling and collapsed palette hover UX โœจ -โ€ข Enhanced scratchpad with search, image paste, and attachment support ๐Ÿ” +โ€ข Enhanced mobile web interface with session sync and history panel ๐Ÿ“ฑ +โ€ข Added ThinkingStatusPill showing real-time token counts and elapsed time โฑ๏ธ +โ€ข Implemented task count badges and session deduplication for batch runner ๐Ÿ“Š +โ€ข Added TTS stop control and improved voice synthesis compatibility ๐Ÿ”Š +โ€ข Created image lightbox with navigation, clipboard, and delete features ๐Ÿ–ผ๏ธ +โ€ข Fixed UI bugs in search, auto-scroll, and sidebar interactions ๐Ÿ› +โ€ข Added global Claude stats with streaming updates across projects ๐Ÿ“ˆ +โ€ข Improved markdown checkbox styling and collapsed palette hover UX โœจ +โ€ข Enhanced scratchpad with search, image paste, and attachment support ๐Ÿ” โ€ข Added splash screen with logo and progress bar during startup ๐ŸŽจ ### Previous Releases in this Series @@ -473,15 +486,15 @@ Plus the pre-release ALPHA... **Latest: v0.1.6** | Released November 27, 2025 -โ€ข Added template variables for dynamic AI command customization ๐ŸŽฏ -โ€ข Implemented session bookmarking with star icons and dedicated section โญ -โ€ข Enhanced Git Log Viewer with smarter date formatting ๐Ÿ“… -โ€ข Improved GitHub release workflow to handle partial failures gracefully ๐Ÿ”ง -โ€ข Added collapsible template documentation in AI Commands panel ๐Ÿ“š -โ€ข Updated default commit command with session ID traceability ๐Ÿ” -โ€ข Added tag indicators for custom-named sessions visually ๐Ÿท๏ธ -โ€ข Improved Git Log search UX with better focus handling ๐ŸŽจ -โ€ข Fixed input placeholder spacing for better readability ๐Ÿ“ +โ€ข Added template variables for dynamic AI command customization ๐ŸŽฏ +โ€ข Implemented session bookmarking with star icons and dedicated section โญ +โ€ข Enhanced Git Log Viewer with smarter date formatting ๐Ÿ“… +โ€ข Improved GitHub release workflow to handle partial failures gracefully ๐Ÿ”ง +โ€ข Added collapsible template documentation in AI Commands panel ๐Ÿ“š +โ€ข Updated default commit command with session ID traceability ๐Ÿ” +โ€ข Added tag indicators for custom-named sessions visually ๐Ÿท๏ธ +โ€ข Improved Git Log search UX with better focus handling ๐ŸŽจ +โ€ข Fixed input placeholder spacing for better readability ๐Ÿ“ โ€ข Updated documentation with new features and template references ๐Ÿ“– ### Previous Releases in this Series @@ -500,6 +513,7 @@ Plus the pre-release ALPHA... All releases are available on the [GitHub Releases page](https://github.com/RunMaestro/Maestro/releases). Maestro is available for: + - **macOS** - Apple Silicon (arm64) and Intel (x64) - **Windows** - x64 - **Linux** - x64 and arm64, AppImage, deb, and rpm packages diff --git a/dogfood-output/remote-session-mobile-20260312/.gitignore b/dogfood-output/remote-session-mobile-20260312/.gitignore new file mode 100644 index 0000000000..51d2bc501b --- /dev/null +++ b/dogfood-output/remote-session-mobile-20260312/.gitignore @@ -0,0 +1,4 @@ +desktop-runtime.json +runtime-data/ +videos/ +bg-task.md diff --git a/dogfood-output/remote-session-mobile-20260312/desktop-setup.mjs b/dogfood-output/remote-session-mobile-20260312/desktop-setup.mjs new file mode 100644 index 0000000000..ef9e772897 --- /dev/null +++ b/dogfood-output/remote-session-mobile-20260312/desktop-setup.mjs @@ -0,0 +1,214 @@ +import { _electron as electron } from '@playwright/test'; +import fs from 'fs/promises'; +import path from 'path'; + +const repoRoot = process.cwd(); +const outputDir = path.join(repoRoot, 'dogfood-output/remote-session-mobile-20260312'); +const screenshotDir = path.join(outputDir, 'screenshots'); +const runtimeDataDir = path.join(outputDir, 'runtime-data'); +const statePath = path.join(outputDir, 'desktop-runtime.json'); +const screenshotPath = path.join(screenshotDir, 'desktop-live-overlay.png'); +const appPath = path.join(repoRoot, 'dist/main/index.js'); +const workingDir = process.env.MAESTRO_WORKDIR || repoRoot; +const agentName = process.env.MAESTRO_AGENT_NAME || `Remote UX Audit ${Date.now()}`; + +let app; + +async function ensureDirs() { + await fs.mkdir(screenshotDir, { recursive: true }); + await fs.mkdir(runtimeDataDir, { recursive: true }); +} + +async function writeState(patch) { + let current = {}; + try { + current = JSON.parse(await fs.readFile(statePath, 'utf8')); + } catch { + current = {}; + } + + const next = { + ...current, + ...patch, + updatedAt: new Date().toISOString(), + }; + + await fs.writeFile(statePath, `${JSON.stringify(next, null, 2)}\n`, 'utf8'); +} + +async function closeInterstitials(page) { + const dismissButtons = [ + page.getByRole('button', { name: /^Skip$/i }), + page.getByRole('button', { name: /^Close$/i }), + page.getByRole('button', { name: /^Got it$/i }), + ]; + + for (const button of dismissButtons) { + if (await button.isVisible().catch(() => false)) { + await button.click().catch(() => {}); + } + } +} + +async function launchApp() { + app = await electron.launch({ + args: [appPath], + env: { + ...process.env, + MAESTRO_DATA_DIR: runtimeDataDir, + ELECTRON_DISABLE_GPU: '1', + }, + timeout: 60000, + }); + + const page = await app.firstWindow(); + await page.waitForLoadState('domcontentloaded'); + await page.waitForTimeout(2000); + await closeInterstitials(page); + + return page; +} + +async function openNewAgentModal(page) { + await page.keyboard.press('Meta+N'); + const heading = page.getByText('Create New Agent', { exact: true }); + await heading.waitFor({ state: 'visible', timeout: 15000 }); + return heading; +} + +async function createCodexAgent(page) { + await openNewAgentModal(page); + + const nameInput = page.getByLabel('Agent Name'); + await nameInput.fill(agentName); + + const codexOption = page.getByRole('option', { name: /Codex/i }).first(); + await codexOption.waitFor({ state: 'visible', timeout: 15000 }); + await codexOption.click(); + + const dirInput = page.getByLabel('Working Directory'); + await dirInput.fill(workingDir); + + const riskCheckbox = page.getByLabel('I understand the risk and want to proceed'); + if (await riskCheckbox.isVisible().catch(() => false)) { + await riskCheckbox.check(); + } + + const createButton = page.getByRole('button', { name: 'Create Agent' }).last(); + await createButton.waitFor({ state: 'visible', timeout: 15000 }); + await createButton.click(); + + await page.getByText('Create New Agent', { exact: true }).waitFor({ + state: 'hidden', + timeout: 30000, + }); + + await page.getByText(agentName, { exact: false }).first().waitFor({ + state: 'visible', + timeout: 30000, + }); +} + +async function openLiveOverlay(page) { + const toggle = page.getByRole('button', { name: /^(LIVE|OFFLINE)$/i }).first(); + await toggle.waitFor({ state: 'visible', timeout: 20000 }); + await toggle.click(); + await page.getByText('Remote Control', { exact: true }).waitFor({ + state: 'visible', + timeout: 20000, + }); +} + +async function enableRemoteControl(page) { + const remoteToggle = page.locator( + 'button[title="Enable remote control"], button[title="Disable remote control"]' + ); + await remoteToggle.first().waitFor({ state: 'visible', timeout: 20000 }); + const title = await remoteToggle.first().getAttribute('title'); + if (title === 'Enable remote control') { + await remoteToggle.first().click(); + } + + // Required by the user: wait 30 seconds after enabling remote access. + await page.waitForTimeout(30000); + + await page + .locator('[title*="trycloudflare.com"]') + .first() + .waitFor({ state: 'visible', timeout: 30000 }); +} + +async function readUrls(page) { + const localUrl = await page.evaluate(() => window.maestro.live.getDashboardUrl()); + const remoteUrl = await page + .locator('[title*="trycloudflare.com"]') + .first() + .getAttribute('title'); + return { localUrl, remoteUrl }; +} + +async function heartbeat(page) { + while (true) { + const url = page.url(); + await writeState({ heartbeatUrl: url, heartbeatAt: new Date().toISOString() }); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } +} + +async function main() { + await ensureDirs(); + await writeState({ + status: 'starting', + agentName, + workingDir, + appPath, + runtimeDataDir, + }); + + const page = await launchApp(); + await writeState({ status: 'app-launched' }); + + await createCodexAgent(page); + await writeState({ status: 'agent-created' }); + + await openLiveOverlay(page); + await writeState({ status: 'live-overlay-open' }); + + await enableRemoteControl(page); + const { localUrl, remoteUrl } = await readUrls(page); + + await page.screenshot({ path: screenshotPath, fullPage: true }); + + await writeState({ + status: 'ready', + localUrl, + remoteUrl, + screenshotPath, + }); + + console.log(`[desktop-setup] agentName=${agentName}`); + console.log(`[desktop-setup] localUrl=${localUrl}`); + console.log(`[desktop-setup] remoteUrl=${remoteUrl}`); + console.log(`[desktop-setup] screenshot=${screenshotPath}`); + + await heartbeat(page); +} + +async function cleanup(exitCode = 0) { + if (app) { + await app.close().catch(() => {}); + } + process.exit(exitCode); +} + +process.on('SIGINT', () => void cleanup(0)); +process.on('SIGTERM', () => void cleanup(0)); + +main().catch(async (error) => { + await writeState({ + status: 'error', + error: error instanceof Error ? error.stack || error.message : String(error), + }); + console.error('[desktop-setup] failed', error); + await cleanup(1); +}); diff --git a/dogfood-output/remote-session-mobile-20260312/report.md b/dogfood-output/remote-session-mobile-20260312/report.md new file mode 100644 index 0000000000..af4c51f025 --- /dev/null +++ b/dogfood-output/remote-session-mobile-20260312/report.md @@ -0,0 +1,160 @@ +# Dogfood Report: Maestro Remote Control + +| Field | Value | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| **Date** | 2026-03-12 | +| **App URL** | [REDACTED_TUNNEL_URL] | +| **Session** | Remote UX Audit 1773336790326 | +| **Scope** | Mobile-first audit of remote session start, AI/CLI switching, response visibility, text input ergonomics, and tablet responsive behavior | + +## Summary + +### Original Findings + +| Severity | Count | +| --------- | ----- | +| Critical | 0 | +| High | 3 | +| Medium | 0 | +| Low | 0 | +| **Total** | **3** | + +### Current Retest Status On `7cfaded1` + +| Status | Count | +| ------------------------------------------------ | ----- | +| Open High Issues | 1 | +| Previously Reported Issues No Longer Reproducing | 2 | +| **Open Total** | **1** | + +### Retest Coverage + +- Phone: reproduced long-draft composer clipping; confirmed AI-to-CLI isolates buffers; confirmed AI draft restores after CLI-to-AI; confirmed session picker no longer leaks draft into another session. +- Tablet/iPad size: long draft remained readable and contained. Evidence: `screenshots/tablet-audit-long-draft-current.png` +- Wide desktop-like size: long draft remained readable and contained. Evidence: `screenshots/wide-audit-long-draft-current-2.png` + +### Post-Fix Verification + +| Status | Count | +| -------------------------------- | ----- | +| Open Issues In Retested Scope | 0 | +| Verified Fixed Issues | 1 | +| Verified Previously Fixed Issues | 2 | +| **Open Total After Fixes** | **0** | + +- Rebuilt the web bundle, restarted the PM2-backed Maestro app, and re-ran the LIVE overlay remote flow until `desktop-runtime.json` returned `status: "ready"` with the fresh Cloudflare tunnel. +- Phone: + - Long draft is now readable in a full-width stacked composer. Evidence: `screenshots/phone-verify-expanded-draft-postfix.png` + - AI-to-CLI switch presents a clean shell input. Evidence: `screenshots/phone-verify-cli-after-toggle-final.png` + - CLI `pwd` output is visible in the remote UI. Evidence: `screenshots/phone-verify-cli-pwd-output-final.png` + - CLI-to-AI restores the pending AI draft. Evidence: `screenshots/phone-verify-back-to-ai-restored-final.png` + - Real AI factoid response arrived: `What is the capital of Peru?` -> `Lima`. Evidence: `screenshots/phone-verify-ai-factoid-response-final.png` +- Tablet/iPad size remained readable after the phone-only fix. Evidence: `screenshots/tablet-verify-long-draft-final.png` +- Wide desktop-like size remained readable after the phone-only fix. Evidence: `screenshots/wide-verify-long-draft-final.png` + +## Issues + + + +### ISSUE-001: Phone AI composer collapses into a clipped vertical strip for normal-length drafts + +| Field | Value | +| ------------------------------- | --------------------- | +| **Severity** | high | +| **Category** | ux | +| **URL** | [REDACTED_TUNNEL_URL] | +| **Repro Video** | N/A | +| **Retest Status On `7cfaded1`** | still reproducible | + +**Description** + +On iPhone-sized viewports, the fixed bottom AI composer becomes too narrow once the user types a realistic prompt. The text wraps inside words, the top of the field is clipped out of view, and the user can only see the bottom portion of the draft. Expected: the main input stays readable and fully contained while growing. Actual: the input collapses into a thin, partially hidden column that makes phone prompting impractical. + +**Repro Steps** + +1. Open the remote dashboard on an iPhone-sized viewport and select an AI session. + ![Step 1](screenshots/phone-ai-response.png) + +2. Type a normal multi-clause prompt into the AI composer. + ![Result](screenshots/issue-001-phone-composer-overflow.png) + +**Current Retest Notes** + +- The exact class of bug still reproduces on the current branch when the phone composer contains a realistic unsent draft. The field no longer collapses into the original razor-thin strip, but it still anchors too low and clips the visible draft so only the lower portion is readable. +- Current evidence: + - `screenshots/phone-audit-long-draft-current.png` + - `screenshots/phone-audit-back-to-ai-current.png` + +**Post-Fix Verification** + +- No longer reproducible in the working tree after switching the phone layout to a width-based stacked composer path. +- Current evidence: + - `screenshots/phone-verify-expanded-draft-postfix.png` + - `screenshots/phone-verify-back-to-ai-restored-final.png` + +--- + +### ISSUE-002: Switching from AI-to-CLI reuses the unsent AI draft as a shell command + +| Field | Value | +| ------------------------------- | --------------------- | +| **Severity** | high | +| **Category** | functional | +| **URL** | [REDACTED_TUNNEL_URL] | +| **Repro Video** | N/A | +| **Retest Status On `7cfaded1`** | not reproducible | + +**Description** + +The mobile mode toggle carries the exact unsent AI draft into terminal mode instead of isolating AI and CLI buffers. Expected: switching to CLI should present an empty shell prompt or a terminal-specific draft buffer. Actual: the prior AI prompt instantly becomes terminal input, which creates a real risk of accidentally executing natural-language text as a shell command. + +**Repro Steps** + +1. In AI mode, type an unsent natural-language draft. + ![Step 1](screenshots/phone-long-draft.png) + +2. Tap the AI/CLI mode toggle. + ![Result](screenshots/phone-terminal-after-switch.png) + +**Current Retest Notes** + +- On the current branch, switching from AI-to-CLI presents a clean shell input instead of reusing the unsent AI draft. +- Switching back from CLI-to-AI restores the original AI draft for that session. +- Current evidence: + - `screenshots/phone-audit-after-cli-switch-current.png` + - `screenshots/phone-audit-back-to-ai-current.png` + +--- + +### ISSUE-003: Switching sessions carries the unsent draft into the newly selected session + +| Field | Value | +| ------------------------------- | ------------------------------------------------ | +| **Severity** | high | +| **Category** | functional | +| **URL** | [REDACTED_TUNNEL_URL] | +| **Repro Video** | N/A | +| **Retest Status On `7cfaded1`** | not reproducible via current session picker flow | + +**Description** + +The mobile composer state is not scoped to the selected session. Expected: when the user switches to a different session, they should see that session's own draft state or an empty composer. Actual: the previous session's unsent draft appears unchanged in the next session, making it easy to send the wrong prompt to the wrong agent. + +**Repro Steps** + +1. In one AI session, type an unsent draft. + ![Step 1](screenshots/phone-long-draft.png) + +2. Tap another session in the session strip. + ![Result](screenshots/phone-switch-other-session-with-draft.png) + +**Current Retest Notes** + +- Using the current `All Agents` picker, switching to another session no longer carries the unsent draft into the newly selected session. +- Returning to the original session restores the original draft. +- Current evidence: + - `screenshots/phone-audit-search-menu-current.png` + - `screenshots/phone-audit-after-picker-switch-current.png` + - `screenshots/phone-audit-draft-restored-after-picker-current.png` + +--- diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/desktop-live-overlay.png b/dogfood-output/remote-session-mobile-20260312/screenshots/desktop-live-overlay.png new file mode 100644 index 0000000000..4999661b56 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/desktop-live-overlay.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/issue-001-phone-composer-overflow.png b/dogfood-output/remote-session-mobile-20260312/screenshots/issue-001-phone-composer-overflow.png new file mode 100644 index 0000000000..ba7c1d560e Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/issue-001-phone-composer-overflow.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-ai-response.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-ai-response.png new file mode 100644 index 0000000000..5eeff65a2b Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-ai-response.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-cli-switch-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-cli-switch-current.png new file mode 100644 index 0000000000..db53d0360c Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-cli-switch-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-picker-switch-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-picker-switch-current.png new file mode 100644 index 0000000000..8f81255d27 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-after-picker-switch-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-back-to-ai-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-back-to-ai-current.png new file mode 100644 index 0000000000..e43f789040 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-back-to-ai-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-current-initial.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-current-initial.png new file mode 100644 index 0000000000..9a438fbbbe Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-current-initial.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-draft-restored-after-picker-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-draft-restored-after-picker-current.png new file mode 100644 index 0000000000..37f71f2281 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-draft-restored-after-picker-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-long-draft-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-long-draft-current.png new file mode 100644 index 0000000000..bef87c8239 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-long-draft-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-offline-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-offline-current.png new file mode 100644 index 0000000000..335af82da4 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-offline-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-reconnected-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-reconnected-current.png new file mode 100644 index 0000000000..335af82da4 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-reconnected-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-search-menu-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-search-menu-current.png new file mode 100644 index 0000000000..39b5d7986a Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-search-menu-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-switch-session-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-switch-session-current.png new file mode 100644 index 0000000000..37f71f2281 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-switch-session-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-focus-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-focus-current.png new file mode 100644 index 0000000000..0dd7e7342b Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-focus-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-then-fill-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-then-fill-current.png new file mode 100644 index 0000000000..d6cc57b94a Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-audit-tap-then-fill-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-back-to-ai-after-cli.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-back-to-ai-after-cli.png new file mode 100644 index 0000000000..6e5f506237 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-back-to-ai-after-cli.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-cli-pwd.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-cli-pwd.png new file mode 100644 index 0000000000..10a76a7fa5 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-cli-pwd.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-session-back-to-ai.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-session-back-to-ai.png new file mode 100644 index 0000000000..7141729f4d Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-clean-session-back-to-ai.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-output-settled.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-output-settled.png new file mode 100644 index 0000000000..c141ae913d Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-output-settled.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-pwd-output.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-pwd-output.png new file mode 100644 index 0000000000..c76ea72a1b Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-cli-pwd-output.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-after-wait.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-after-wait.png new file mode 100644 index 0000000000..20a1fb31bc Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-after-wait.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-ai-factoid-response.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-ai-factoid-response.png new file mode 100644 index 0000000000..e72687a9a4 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-ai-factoid-response.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-back-to-ai-with-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-back-to-ai-with-draft.png new file mode 100644 index 0000000000..66ac7a923c Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-back-to-ai-with-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-cli-pwd-output.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-cli-pwd-output.png new file mode 100644 index 0000000000..0b894d788c Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-cli-pwd-output.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored-via-picker.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored-via-picker.png new file mode 100644 index 0000000000..62192f12ae Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored-via-picker.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored.png new file mode 100644 index 0000000000..e1db211f04 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-draft-restored.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-initial.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-initial.png new file mode 100644 index 0000000000..ef1db86ca6 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-initial.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-390.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-390.png new file mode 100644 index 0000000000..d536746559 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-390.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-device.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-device.png new file mode 100644 index 0000000000..eb5c9c3ff9 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-device.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-touch.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-touch.png new file mode 100644 index 0000000000..d21196f3a0 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft-touch.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft.png new file mode 100644 index 0000000000..a91fef0a99 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-long-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-no-leak.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-no-leak.png new file mode 100644 index 0000000000..84d6e78150 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-no-leak.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-via-picker.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-via-picker.png new file mode 100644 index 0000000000..e1db211f04 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-switch-session-via-picker.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-terminal-after-switch.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-terminal-after-switch.png new file mode 100644 index 0000000000..e4261c080b Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-terminal-after-switch.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-verification-start.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-verification-start.png new file mode 100644 index 0000000000..958c402805 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-fixed-verification-start.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-history-open.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-history-open.png new file mode 100644 index 0000000000..84d6e78150 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-history-open.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-initial.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-initial.png new file mode 100644 index 0000000000..1e315febd1 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-initial.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-long-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-long-draft.png new file mode 100644 index 0000000000..afddbe35c1 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-long-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-offline-banner.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-offline-banner.png new file mode 100644 index 0000000000..5779007b8a Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-offline-banner.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-reconnected.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-reconnected.png new file mode 100644 index 0000000000..f42f39dcaf Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-reconnected.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-switch-other-session-with-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-switch-other-session-with-draft.png new file mode 100644 index 0000000000..0bc51c494e Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-switch-other-session-with-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-terminal-after-switch.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-terminal-after-switch.png new file mode 100644 index 0000000000..9840145f92 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-terminal-after-switch.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-blur-eval-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-blur-eval-postfix.png new file mode 100644 index 0000000000..14f1a1234f Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-blur-eval-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-short-draft-refresh-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-short-draft-refresh-postfix.png new file mode 100644 index 0000000000..b9cf467d51 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-short-draft-refresh-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-wait-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-wait-postfix.png new file mode 100644 index 0000000000..9a438fbbbe Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-after-wait-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-ai-factoid-response-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-ai-factoid-response-final.png new file mode 100644 index 0000000000..7edc114238 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-ai-factoid-response-final.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-back-to-ai-restored-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-back-to-ai-restored-final.png new file mode 100644 index 0000000000..04069e5adf Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-back-to-ai-restored-final.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-js-toggle-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-js-toggle-postfix.png new file mode 100644 index 0000000000..79eb55358e Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-js-toggle-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-switch-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-switch-postfix.png new file mode 100644 index 0000000000..14f1a1234f Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-switch-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-eval-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-eval-postfix.png new file mode 100644 index 0000000000..b9cf467d51 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-eval-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-final.png new file mode 100644 index 0000000000..adbd9bd30a Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-after-toggle-final.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-empty-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-empty-postfix.png new file mode 100644 index 0000000000..b9cf467d51 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-empty-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-pwd-output-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-pwd-output-final.png new file mode 100644 index 0000000000..77314f6ca0 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-pwd-output-final.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-via-eval-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-via-eval-postfix.png new file mode 100644 index 0000000000..b9cf467d51 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-cli-via-eval-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-collapsed-after-blur-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-collapsed-after-blur-postfix.png new file mode 100644 index 0000000000..6c920c159e Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-collapsed-after-blur-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-expanded-draft-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-expanded-draft-postfix.png new file mode 100644 index 0000000000..93697c1082 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-expanded-draft-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-initial-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-initial-postfix.png new file mode 100644 index 0000000000..f6903e4d74 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-initial-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix-2.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix-2.png new file mode 100644 index 0000000000..d84a2bc1a8 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix-2.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix.png new file mode 100644 index 0000000000..bef87c8239 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-long-draft-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-postfix.png new file mode 100644 index 0000000000..b0e2549833 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-short-draft-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-short-draft-postfix.png new file mode 100644 index 0000000000..4b776e2fab Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-search-open-short-draft-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-short-draft-before-cli-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-short-draft-before-cli-postfix.png new file mode 100644 index 0000000000..b9cf467d51 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-short-draft-before-cli-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-terminal-mode-postfix.png b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-terminal-mode-postfix.png new file mode 100644 index 0000000000..14f1a1234f Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/phone-verify-terminal-mode-postfix.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-current-initial.png b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-current-initial.png new file mode 100644 index 0000000000..1df42e2a31 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-current-initial.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-long-draft-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-long-draft-current.png new file mode 100644 index 0000000000..5a68505e03 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-audit-long-draft-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-initial.png b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-initial.png new file mode 100644 index 0000000000..eba39f01bf Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-initial.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-long-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-long-draft.png new file mode 100644 index 0000000000..e245d15c32 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-long-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-verify-long-draft-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-verify-long-draft-final.png new file mode 100644 index 0000000000..5a68505e03 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/tablet-verify-long-draft-final.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current-2.png b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current-2.png new file mode 100644 index 0000000000..05815846c5 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current-2.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current.png b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current.png new file mode 100644 index 0000000000..015f329406 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-audit-long-draft-current.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/wide-long-draft.png b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-long-draft.png new file mode 100644 index 0000000000..9c7a1e2ee1 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-long-draft.png differ diff --git a/dogfood-output/remote-session-mobile-20260312/screenshots/wide-verify-long-draft-final.png b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-verify-long-draft-final.png new file mode 100644 index 0000000000..cff1454c00 Binary files /dev/null and b/dogfood-output/remote-session-mobile-20260312/screenshots/wide-verify-long-draft-final.png differ diff --git a/src/__tests__/main/process-manager/runners/LocalCommandRunner.test.ts b/src/__tests__/main/process-manager/runners/LocalCommandRunner.test.ts new file mode 100644 index 0000000000..5ddc2ff4d3 --- /dev/null +++ b/src/__tests__/main/process-manager/runners/LocalCommandRunner.test.ts @@ -0,0 +1,72 @@ +import { EventEmitter } from 'events'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const { mockPtySpawn, mockResolveShellPath, mockBuildInteractiveShellArgs, mockBuildUnixBasePath } = + vi.hoisted(() => ({ + mockPtySpawn: vi.fn(), + mockResolveShellPath: vi.fn(), + mockBuildInteractiveShellArgs: vi.fn(), + mockBuildUnixBasePath: vi.fn(), + })); + +vi.mock('node-pty', () => ({ + spawn: mockPtySpawn, +})); + +vi.mock('../../../../main/utils/logger', () => ({ + logger: { + debug: vi.fn(), + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + }, +})); + +vi.mock('../../../../main/process-manager/utils/pathResolver', () => ({ + resolveShellPath: mockResolveShellPath, + buildInteractiveShellArgs: mockBuildInteractiveShellArgs, + buildWrappedCommand: vi.fn(), +})); + +vi.mock('../../../../main/process-manager/utils/envBuilder', () => ({ + buildUnixBasePath: mockBuildUnixBasePath, +})); + +vi.mock('../../../../shared/platformDetection', () => ({ + isWindows: vi.fn(() => false), +})); + +import { LocalCommandRunner } from '../../../../main/process-manager/runners/LocalCommandRunner'; + +describe('LocalCommandRunner', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockResolveShellPath.mockReturnValue('/bin/zsh'); + mockBuildInteractiveShellArgs.mockReturnValue(['-l', '-i', '-c', 'ls']); + mockBuildUnixBasePath.mockReturnValue('/usr/bin:/bin'); + }); + + it('resolves and emits stderr when PTY spawn throws', async () => { + mockPtySpawn.mockImplementation(() => { + throw new Error('permission denied'); + }); + + const emitter = new EventEmitter(); + const runner = new LocalCommandRunner(emitter); + const stderrEvents: string[] = []; + const exitEvents: number[] = []; + + emitter.on('stderr', (_sessionId: string, data: string) => { + stderrEvents.push(data); + }); + emitter.on('command-exit', (_sessionId: string, code: number) => { + exitEvents.push(code); + }); + + const result = await runner.run('session-1', 'ls', '/tmp'); + + expect(result).toEqual({ exitCode: 1 }); + expect(stderrEvents).toEqual(['Error: permission denied']); + expect(exitEvents).toEqual([1]); + }); +}); diff --git a/src/__tests__/main/process-manager/utils/pathResolver.test.ts b/src/__tests__/main/process-manager/utils/pathResolver.test.ts new file mode 100644 index 0000000000..6a930fd871 --- /dev/null +++ b/src/__tests__/main/process-manager/utils/pathResolver.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; +import { buildInteractiveShellArgs } from '../../../../main/process-manager/utils/pathResolver'; + +describe('pathResolver', () => { + describe('buildInteractiveShellArgs', () => { + it('uses login + interactive flags for zsh commands', () => { + expect(buildInteractiveShellArgs('ls', 'zsh')).toEqual(['-l', '-i', '-c', 'ls']); + }); + + it('passes the command as a dedicated shell argument without manual quoting', () => { + expect(buildInteractiveShellArgs("printf 'hi'", 'zsh')).toEqual([ + '-l', + '-i', + '-c', + "printf 'hi'", + ]); + }); + + it('uses login + interactive flags for bash commands', () => { + expect(buildInteractiveShellArgs('ls', 'bash')).toEqual(['-l', '-i', '-c', 'ls']); + }); + }); +}); diff --git a/src/__tests__/main/utils/terminalFilter.test.ts b/src/__tests__/main/utils/terminalFilter.test.ts index d15607624a..6396cbf09e 100644 --- a/src/__tests__/main/utils/terminalFilter.test.ts +++ b/src/__tests__/main/utils/terminalFilter.test.ts @@ -224,6 +224,12 @@ describe('terminalFilter', () => { expect(result).toBe('textmore'); }); + it('should remove transient PTY wrapper control bytes before visible output', () => { + const input = '\x04\x08\x08file1.txt\nfile2.txt'; + const result = stripControlSequences(input); + expect(result).toBe('file1.txt\nfile2.txt'); + }); + it('should preserve newlines', () => { const input = 'line1\nline2'; const result = stripControlSequences(input); diff --git a/src/__tests__/main/web-server/routes/staticRoutes.test.ts b/src/__tests__/main/web-server/routes/staticRoutes.test.ts index 642f2c7931..01c4b78335 100644 --- a/src/__tests__/main/web-server/routes/staticRoutes.test.ts +++ b/src/__tests__/main/web-server/routes/staticRoutes.test.ts @@ -14,6 +14,9 @@ */ import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs'; +import { tmpdir } from 'os'; +import path from 'path'; import { StaticRoutes } from '../../../../main/web-server/routes/staticRoutes'; // Mock the logger @@ -171,6 +174,52 @@ describe('StaticRoutes', () => { }); }); + describe('Index HTML freshness', () => { + it('should serve updated index.html content after the file changes on disk', async () => { + const tempRoot = mkdtempSync(path.join(tmpdir(), 'maestro-static-routes-')); + const tempAssetsPath = path.join(tempRoot, 'web'); + const tempIndexPath = path.join(tempAssetsPath, 'index.html'); + + mkdirSync(tempAssetsPath, { recursive: true }); + + try { + writeFileSync( + tempIndexPath, + '', + 'utf8' + ); + + const freshRoutes = new StaticRoutes(securityToken, tempAssetsPath); + const freshFastify = createMockFastify(); + freshRoutes.registerRoutes(freshFastify as any); + + const route = freshFastify.getRoute('GET', `/${securityToken}`); + const firstReply = createMockReply(); + await route!.handler({}, firstReply); + + expect(firstReply.type).toHaveBeenCalledWith('text/html'); + expect(firstReply.send).toHaveBeenCalledWith( + expect.stringContaining(`/${securityToken}/assets/main-old.js`) + ); + + writeFileSync( + tempIndexPath, + '', + 'utf8' + ); + + const secondReply = createMockReply(); + await route!.handler({}, secondReply); + + expect(secondReply.send).toHaveBeenCalledWith( + expect.stringContaining(`/${securityToken}/assets/main-new.js`) + ); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + }); + describe('XSS Sanitization (sanitizeId)', () => { // Access private method via type casting for testing const getSanitizeId = (routes: StaticRoutes) => { diff --git a/src/__tests__/web/mobile/App.test.tsx b/src/__tests__/web/mobile/App.test.tsx index 1ec3cafba4..79e11454af 100644 --- a/src/__tests__/web/mobile/App.test.tsx +++ b/src/__tests__/web/mobile/App.test.tsx @@ -1116,6 +1116,106 @@ describe('MobileApp', () => { mode: 'terminal', }); }); + + it('keeps separate drafts for AI and terminal mode', async () => { + render(); + + await act(async () => { + mockHandlers.onSessionsUpdate?.([ + createMockSession({ + id: 'session-1', + inputMode: 'ai', + aiTabs: [{ id: 'tab-1', name: 'Main', state: 'idle', inputValue: '' }], + activeTabId: 'tab-1', + }), + ]); + }); + + const input = screen.getByTestId('command-input'); + fireEvent.change(input, { target: { value: 'Explain the repo status' } }); + + fireEvent.click(screen.getByTestId('mode-toggle')); + + expect(screen.getByTestId('input-mode')).toHaveTextContent('terminal'); + expect(screen.getByTestId('command-input')).toHaveValue(''); + + fireEvent.change(screen.getByTestId('command-input'), { target: { value: 'pwd' } }); + fireEvent.click(screen.getByTestId('mode-toggle')); + + expect(screen.getByTestId('input-mode')).toHaveTextContent('ai'); + expect(screen.getByTestId('command-input')).toHaveValue('Explain the repo status'); + }); + }); + + describe('draft scoping', () => { + it('keeps drafts scoped to the selected session', async () => { + render(); + + await act(async () => { + mockHandlers.onSessionsUpdate?.([ + createMockSession({ + id: 'session-1', + name: 'Session 1', + inputMode: 'ai', + aiTabs: [{ id: 'tab-1', name: 'Main', state: 'idle', inputValue: '' }], + activeTabId: 'tab-1', + }), + createMockSession({ + id: 'session-2', + name: 'Session 2', + inputMode: 'ai', + aiTabs: [{ id: 'tab-2', name: 'Main', state: 'idle', inputValue: '' }], + activeTabId: 'tab-2', + }), + ]); + }); + + fireEvent.change(screen.getByTestId('command-input'), { + target: { value: 'draft for session one' }, + }); + + fireEvent.click(screen.getByTestId('session-session-2')); + expect(screen.getByTestId('command-input')).toHaveValue(''); + + fireEvent.change(screen.getByTestId('command-input'), { + target: { value: 'draft for session two' }, + }); + + fireEvent.click(screen.getByTestId('session-session-1')); + expect(screen.getByTestId('command-input')).toHaveValue('draft for session one'); + }); + + it('falls back to desktop AI draft after submit clears the local override', async () => { + render(); + + await act(async () => { + mockHandlers.onSessionsUpdate?.([ + createMockSession({ + id: 'session-1', + inputMode: 'ai', + aiTabs: [{ id: 'tab-1', name: 'Main', state: 'idle', inputValue: '' }], + activeTabId: 'tab-1', + }), + ]); + }); + + fireEvent.change(screen.getByTestId('command-input'), { + target: { value: 'temporary local draft' }, + }); + + fireEvent.click(screen.getByTestId('submit-command')); + expect(screen.getByTestId('command-input')).toHaveValue(''); + + await act(async () => { + mockHandlers.onTabsChanged?.( + 'session-1', + [{ id: 'tab-1', name: 'Main', state: 'idle', inputValue: 'desktop restored draft' }], + 'tab-1' + ); + }); + + expect(screen.getByTestId('command-input')).toHaveValue('desktop restored draft'); + }); }); describe('interrupt handling', () => { diff --git a/src/__tests__/web/mobile/CommandInputBar.test.tsx b/src/__tests__/web/mobile/CommandInputBar.test.tsx index 85c5bc90b3..2bf93f2474 100644 --- a/src/__tests__/web/mobile/CommandInputBar.test.tsx +++ b/src/__tests__/web/mobile/CommandInputBar.test.tsx @@ -596,6 +596,34 @@ describe('CommandInputBar', () => { expect(screen.getByTestId('slash-autocomplete')).toBeInTheDocument(); }); + + it('stacks phone AI drafts into a full-width preview when they exceed the compact height', () => { + Object.defineProperty(window, 'innerWidth', { value: 400, writable: true }); + + const scrollHeightSpy = vi + .spyOn(HTMLTextAreaElement.prototype, 'scrollHeight', 'get') + .mockReturnValue(160); + + renderComponent({ + inputMode: 'ai', + value: + 'Summarize the working directory status, current branch, and whether there are uncommitted changes in this project.', + }); + + const textarea = screen.getByRole('textbox'); + const form = textarea.closest('form'); + expect( + screen.queryByRole('button', { name: /open slash commands/i }) + ).not.toBeInTheDocument(); + expect(form).toHaveStyle({ flexDirection: 'column' }); + expect(Number.parseInt((textarea as HTMLTextAreaElement).style.height, 10)).toBeGreaterThan( + 48 + ); + expect(screen.getByRole('button', { name: /switch to terminal mode/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /send command/i })).toBeInTheDocument(); + + scrollHeightSpy.mockRestore(); + }); }); describe('Swipe Up Handle', () => { @@ -1041,42 +1069,55 @@ describe('useIsMobilePhone hook', () => { Object.defineProperty(navigator, 'maxTouchPoints', { value: 0, writable: true }); }); - it('detects mobile phone when touch and small screen', () => { + it('treats narrow screens as phone layouts even without touch capability', () => { Object.defineProperty(window, 'innerWidth', { value: 400, writable: true }); - Object.defineProperty(window, 'ontouchstart', { value: () => {}, writable: true }); - renderComponent({ inputMode: 'ai' }); + const scrollHeightSpy = vi + .spyOn(HTMLTextAreaElement.prototype, 'scrollHeight', 'get') + .mockReturnValue(160); - // On mobile phone in AI mode, clicking textarea should expand - // We can verify the hook is working through behavior + renderComponent({ + inputMode: 'ai', + value: + 'Summarize the working directory status, current branch, and whether there are uncommitted changes in this project.', + }); + + expect(screen.getByRole('textbox').closest('form')).toHaveStyle({ flexDirection: 'column' }); + + scrollHeightSpy.mockRestore(); }); - it('does not detect mobile on large screens', () => { + it('keeps large screens on the desktop/tablet layout', () => { Object.defineProperty(window, 'innerWidth', { value: 1024, writable: true }); - Object.defineProperty(window, 'ontouchstart', { value: () => {}, writable: true }); - renderComponent({ inputMode: 'ai' }); + const scrollHeightSpy = vi + .spyOn(HTMLTextAreaElement.prototype, 'scrollHeight', 'get') + .mockReturnValue(160); - // Large screen = not mobile, even with touch - }); - - it('does not detect mobile without touch capability', () => { - Object.defineProperty(window, 'innerWidth', { value: 400, writable: true }); - Object.defineProperty(window, 'ontouchstart', { value: undefined, writable: true }); - Object.defineProperty(navigator, 'maxTouchPoints', { value: 0, writable: true }); + renderComponent({ + inputMode: 'ai', + value: + 'Summarize the working directory status, current branch, and whether there are uncommitted changes in this project.', + }); - renderComponent({ inputMode: 'ai' }); + expect(screen.getByRole('textbox').closest('form')).toHaveStyle({ flexDirection: 'row' }); - // Small screen but no touch = not mobile phone + scrollHeightSpy.mockRestore(); }); it('responds to resize events', () => { Object.defineProperty(window, 'innerWidth', { value: 800, writable: true }); - Object.defineProperty(window, 'ontouchstart', { value: () => {}, writable: true }); + const scrollHeightSpy = vi + .spyOn(HTMLTextAreaElement.prototype, 'scrollHeight', 'get') + .mockReturnValue(160); - renderComponent({ inputMode: 'ai' }); + renderComponent({ + inputMode: 'ai', + value: + 'Summarize the working directory status, current branch, and whether there are uncommitted changes in this project.', + }); - // Initially not mobile (800px wide) + expect(screen.getByRole('textbox').closest('form')).toHaveStyle({ flexDirection: 'row' }); // Simulate resize to mobile width Object.defineProperty(window, 'innerWidth', { value: 400, writable: true }); @@ -1085,7 +1126,9 @@ describe('useIsMobilePhone hook', () => { fireEvent(window, new Event('resize')); }); - // The resize handler runs synchronously, state change will be reflected + expect(screen.getByRole('textbox').closest('form')).toHaveStyle({ flexDirection: 'column' }); + + scrollHeightSpy.mockRestore(); }); }); diff --git a/src/main/process-manager/runners/LocalCommandRunner.ts b/src/main/process-manager/runners/LocalCommandRunner.ts index 5d8452e9d1..1201af2e60 100644 --- a/src/main/process-manager/runners/LocalCommandRunner.ts +++ b/src/main/process-manager/runners/LocalCommandRunner.ts @@ -4,19 +4,38 @@ import { spawn } from 'child_process'; import * as path from 'path'; import * as os from 'os'; import { EventEmitter } from 'events'; +import * as pty from 'node-pty'; import { logger } from '../../utils/logger'; import type { CommandResult } from '../types'; import { buildUnixBasePath } from '../utils/envBuilder'; -import { resolveShellPath, buildWrappedCommand } from '../utils/pathResolver'; +import { + resolveShellPath, + buildInteractiveShellArgs, + buildWrappedCommand, +} from '../utils/pathResolver'; import { isWindows } from '../../../shared/platformDetection'; +import { captureException } from '../../utils/sentry'; +import { stripControlSequences } from '../../utils/terminalFilter'; /** * Runs single commands locally and captures stdout/stderr cleanly. - * Does NOT use PTY - spawns commands directly via shell -c. + * On Unix, uses a transient PTY so interactive shell aliases behave correctly. */ export class LocalCommandRunner { constructor(private emitter: EventEmitter) {} + private isRecoverablePtySpawnError(error: unknown): boolean { + const errorCode = + typeof error === 'object' && error !== null && 'code' in error ? String(error.code) : ''; + const message = error instanceof Error ? error.message : String(error); + + if (['ENOENT', 'EACCES', 'ENOTDIR'].includes(errorCode)) { + return true; + } + + return /no such file|not found|not a directory|permission denied|cwd/i.test(message); + } + /** * Run a single command and capture stdout/stderr cleanly */ @@ -47,7 +66,7 @@ export class LocalCommandRunner { .pop() ?.replace(/\.exe$/i, '') || shellToUse; - const wrappedCommand = buildWrappedCommand(command, shellName); + const shellPath = resolveShellPath(shellToUse); // Build environment for command execution let env: NodeJS.ProcessEnv; @@ -86,8 +105,78 @@ export class LocalCommandRunner { ); } - // Resolve shell to full path - const shellPath = resolveShellPath(shellToUse); + if (!isWindows()) { + const ptyArgs = buildInteractiveShellArgs(command, shellName); + + logger.debug('[ProcessManager] runCommand spawning PTY', 'ProcessManager', { + shell: shellToUse, + shellPath, + ptyArgs, + cwd, + PATH: env.PATH?.substring(0, 100), + }); + + let ptyProcess: pty.IPty; + try { + ptyProcess = pty.spawn(shellPath, ptyArgs, { + name: 'xterm-256color', + cols: 120, + rows: 40, + cwd, + env: env as Record, + }); + } catch (error) { + if (!this.isRecoverablePtySpawnError(error)) { + captureException(error, { + operation: 'process-runner:pty-spawn', + sessionId, + shell: shellToUse, + shellPath, + cwd, + }); + throw error; + } + + const message = error instanceof Error ? error.message : String(error); + logger.error('[ProcessManager] runCommand PTY spawn error', 'ProcessManager', { + sessionId, + error: message, + shell: shellToUse, + shellPath, + }); + this.emitter.emit('stderr', sessionId, `Error: ${message}`); + this.emitter.emit('command-exit', sessionId, 1); + resolve({ exitCode: 1 }); + return; + } + + ptyProcess.onData((data) => { + const output = stripControlSequences(data, command, true); + logger.debug('[ProcessManager] runCommand PTY stdout FILTERED', 'ProcessManager', { + sessionId, + filteredLength: output.length, + filteredPreview: output.substring(0, 200), + trimmedEmpty: !output.trim(), + }); + + if (output.trim()) { + this.emitter.emit('data', sessionId, output); + } + }); + + ptyProcess.onExit(({ exitCode }) => { + logger.debug('[ProcessManager] runCommand PTY exit', 'ProcessManager', { + sessionId, + exitCode, + }); + this.emitter.emit('command-exit', sessionId, exitCode); + resolve({ exitCode }); + }); + + return; + } + + const wrappedCommand = buildWrappedCommand(command, shellName); logger.debug('[ProcessManager] runCommand spawning', 'ProcessManager', { shell: shellToUse, @@ -117,6 +206,7 @@ export class LocalCommandRunner { output = output.replace(/\x1b?\]133;[^\x07\x1b\n]*(\x07|\x1b\\)?/g, ''); output = output.replace(/\x1b?\]7;[^\x07\x1b\n]*(\x07|\x1b\\)?/g, ''); output = output.replace(/\x1b?\][0-9];[^\x07\x1b\n]*(\x07|\x1b\\)?/g, ''); + output = stripControlSequences(output, command, true); logger.debug('[ProcessManager] runCommand stdout FILTERED', 'ProcessManager', { sessionId, diff --git a/src/main/process-manager/utils/pathResolver.ts b/src/main/process-manager/utils/pathResolver.ts index 7ffc92d102..e5025fffce 100644 --- a/src/main/process-manager/utils/pathResolver.ts +++ b/src/main/process-manager/utils/pathResolver.ts @@ -78,6 +78,26 @@ export function buildWrappedCommand(command: string, shellName: string): string return command; } +/** + * Build shell arguments for interactive one-off PTY commands. + * Terminal aliases like `ls` -> `eza` only render correctly when the shell has a real PTY. + */ +export function buildInteractiveShellArgs(command: string, shellName: string): string[] { + if (isWindows()) { + return [command]; + } + + if (shellName === 'zsh' || shellName === 'bash') { + return ['-l', '-i', '-c', command]; + } + + if (shellName === 'fish') { + return ['-i', '-c', command]; + } + + return ['-c', command]; +} + /** * Clear the shell path cache (useful for testing) */ diff --git a/src/main/web-server/routes/staticRoutes.ts b/src/main/web-server/routes/staticRoutes.ts index bc2e047f20..9e88185c2d 100644 --- a/src/main/web-server/routes/staticRoutes.ts +++ b/src/main/web-server/routes/staticRoutes.ts @@ -114,8 +114,7 @@ export class StaticRoutes { } const indexPath = path.join(this.webAssetsPath, 'index.html'); - const cachedHtml = getCachedFile(indexPath); - if (cachedHtml === null) { + if (!existsSync(indexPath)) { reply.code(404).send({ error: 'Not Found', message: 'Web interface index.html not found.', @@ -124,8 +123,8 @@ export class StaticRoutes { } try { - // Use cached HTML and transform asset paths - let html = cachedHtml; + // Read index.html fresh so rebuilt asset hashes are reflected immediately. + let html = readFileSync(indexPath, 'utf-8'); // Transform relative paths to use the token-prefixed absolute paths html = html.replace(/\.\/assets\//g, `/${this.securityToken}/assets/`); diff --git a/src/web/mobile/App.tsx b/src/web/mobile/App.tsx index 00f5d23faf..f6a8bb87e9 100644 --- a/src/web/mobile/App.tsx +++ b/src/web/mobile/App.tsx @@ -45,6 +45,21 @@ import { useMobileKeyboardHandler } from '../hooks/useMobileKeyboardHandler'; import { useMobileViewState } from '../hooks/useMobileViewState'; import { useMobileAutoReconnect } from '../hooks/useMobileAutoReconnect'; +interface SessionCommandDrafts { + aiByTab: Record; + terminal: string; +} + +type CommandDraftStore = Record; +const SESSION_LEVEL_AI_DRAFT_KEY = '__session__'; + +function getEmptyDrafts(): SessionCommandDrafts { + return { + aiByTab: {}, + terminal: '', + }; +} + /** * Get the active tab from a session */ @@ -292,7 +307,7 @@ export default function MobileApp() { const [showAllSessions, setShowAllSessions] = useState(savedState.showAllSessions); const [showHistoryPanel, setShowHistoryPanel] = useState(savedState.showHistoryPanel); const [showTabSearch, setShowTabSearch] = useState(savedState.showTabSearch); - const [commandInput, setCommandInput] = useState(''); + const [commandDrafts, setCommandDrafts] = useState({}); const [showResponseViewer, setShowResponseViewer] = useState(false); const [selectedResponse, setSelectedResponse] = useState(null); const [responseIndex, setResponseIndex] = useState(0); @@ -612,6 +627,144 @@ export default function MobileApp() { connect(); }, [connect]); + const currentInputMode = ((activeSession?.inputMode as InputMode | undefined) || + 'ai') as InputMode; + const activeAiTabId = activeSession?.activeTabId || activeTabId || null; + const activeAiTab = activeSession?.aiTabs?.find((tab) => tab.id === activeAiTabId); + const activeAiDraftKey = activeAiTabId || SESSION_LEVEL_AI_DRAFT_KEY; + + const commandInput = useMemo(() => { + if (!activeSessionId || !activeSession) return ''; + + const draftsForSession = commandDrafts[activeSessionId] || getEmptyDrafts(); + + if (currentInputMode === 'terminal') { + return draftsForSession.terminal; + } + + return draftsForSession.aiByTab[activeAiDraftKey] ?? activeAiTab?.inputValue ?? ''; + }, [ + activeAiDraftKey, + activeAiTab, + activeSession, + activeSessionId, + commandDrafts, + currentInputMode, + ]); + + const updateCommandDraft = useCallback( + (nextValue: string, mode: InputMode = currentInputMode) => { + if (!activeSessionId) return; + + setCommandDrafts((prev) => { + const currentDrafts = prev[activeSessionId] || getEmptyDrafts(); + + if (mode === 'terminal') { + if (currentDrafts.terminal === nextValue) { + return prev; + } + + return { + ...prev, + [activeSessionId]: { + ...currentDrafts, + terminal: nextValue, + }, + }; + } + + if (currentDrafts.aiByTab[activeAiDraftKey] === nextValue) { + return prev; + } + + return { + ...prev, + [activeSessionId]: { + ...currentDrafts, + aiByTab: { + ...currentDrafts.aiByTab, + [activeAiDraftKey]: nextValue, + }, + }, + }; + }); + }, + [activeAiDraftKey, activeSessionId, currentInputMode] + ); + + const clearCommandDraft = useCallback( + (mode: InputMode = currentInputMode) => { + if (!activeSessionId) return; + + setCommandDrafts((prev) => { + const currentDrafts = prev[activeSessionId] || getEmptyDrafts(); + + if (mode === 'terminal') { + if (currentDrafts.terminal === '') { + return prev; + } + + return { + ...prev, + [activeSessionId]: { + ...currentDrafts, + terminal: '', + }, + }; + } + + if (!(activeAiDraftKey in currentDrafts.aiByTab)) { + return prev; + } + + const nextAiByTab = { ...currentDrafts.aiByTab }; + delete nextAiByTab[activeAiDraftKey]; + + return { + ...prev, + [activeSessionId]: { + ...currentDrafts, + aiByTab: nextAiByTab, + }, + }; + }); + }, + [activeAiDraftKey, activeSessionId, currentInputMode] + ); + + useEffect(() => { + setCommandDrafts((prev) => { + const validSessionIds = new Set(sessions.map((session) => session.id)); + let changed = false; + const nextDrafts: CommandDraftStore = {}; + + for (const [sessionId, drafts] of Object.entries(prev)) { + if (!validSessionIds.has(sessionId)) { + changed = true; + continue; + } + + const session = sessions.find((item) => item.id === sessionId); + const validTabIds = new Set(session?.aiTabs?.map((tab) => tab.id) || []); + validTabIds.add(SESSION_LEVEL_AI_DRAFT_KEY); + const aiByTab = Object.fromEntries( + Object.entries(drafts.aiByTab).filter(([tabId]) => validTabIds.has(tabId)) + ); + + if (Object.keys(aiByTab).length !== Object.keys(drafts.aiByTab).length) { + changed = true; + } + + nextDrafts[sessionId] = { + aiByTab, + terminal: drafts.terminal, + }; + } + + return changed ? nextDrafts : prev; + }); + }, [sessions]); + // Auto-reconnect with countdown timer (extracted to hook) const { reconnectCountdown } = useMobileAutoReconnect({ connectionState, @@ -658,7 +811,7 @@ export default function MobileApp() { if (!activeSessionId) return; // Find the active session to get input mode - const currentMode = (activeSession?.inputMode as InputMode) || 'ai'; + const currentMode = currentInputMode; // Provide haptic feedback on send triggerHaptic(HAPTIC_PATTERNS.send); @@ -692,11 +845,12 @@ export default function MobileApp() { } // Clear the input - setCommandInput(''); + clearCommandDraft(currentMode); }, [ activeSessionId, - activeSession, + clearCommandDraft, + currentInputMode, send, isOffline, isActuallyConnected, @@ -706,9 +860,12 @@ export default function MobileApp() { ); // Handle command input change - const handleCommandChange = useCallback((value: string) => { - setCommandInput(value); - }, []); + const handleCommandChange = useCallback( + (value: string) => { + updateCommandDraft(value); + }, + [updateCommandDraft] + ); // Handle mode toggle between AI and Terminal const handleModeToggle = useCallback( @@ -1143,7 +1300,7 @@ export default function MobileApp() { ? 'Select a session first...' : activeSession?.inputMode === 'ai' ? isSmallScreen - ? 'Query AI...' + ? 'Ask AI...' : `Ask ${activeSession?.toolType === 'claude-code' ? 'Claude' : activeSession?.toolType || 'AI'} about ${activeSession?.name || 'this session'}...` : 'Run shell command...' } diff --git a/src/web/mobile/CommandInputBar.tsx b/src/web/mobile/CommandInputBar.tsx index 6bdcdd2e74..511e2a5285 100644 --- a/src/web/mobile/CommandInputBar.tsx +++ b/src/web/mobile/CommandInputBar.tsx @@ -62,6 +62,9 @@ const TEXTAREA_VERTICAL_PADDING = 28; // 14px top + 14px bottom /** Maximum height for textarea based on max lines */ const MAX_TEXTAREA_HEIGHT = LINE_HEIGHT * MAX_LINES + TEXTAREA_VERTICAL_PADDING; +/** Maximum collapsed height for phone AI drafts before the full editor is needed */ +const MOBILE_COLLAPSED_MAX_HEIGHT = LINE_HEIGHT * 3 + TEXTAREA_VERTICAL_PADDING; + /** Mobile breakpoint - phones only, not tablets */ const MOBILE_MAX_WIDTH = 480; @@ -70,16 +73,15 @@ const MOBILE_EXPANDED_HEIGHT_VH = 50; /** * Detect if the device is a mobile phone (not tablet/desktop) - * Based on screen width and touch capability + * Based on screen width so narrow remote/mobile layouts get the phone treatment + * even when touch capability is not exposed to the browser. */ function useIsMobilePhone(): boolean { const [isMobile, setIsMobile] = useState(false); useEffect(() => { const checkMobile = () => { - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const isSmallScreen = window.innerWidth <= MOBILE_MAX_WIDTH; - setIsMobile(isTouchDevice && isSmallScreen); + setIsMobile(window.innerWidth <= MOBILE_MAX_WIDTH); }; checkMobile(); @@ -477,6 +479,12 @@ export function CommandInputBar({ ); // Calculate textarea height for mobile expanded mode + const shouldCompressPhoneActions = isMobilePhone && inputMode === 'ai' && value.trim().length > 0; + const collapsedMobileTextareaHeight = Math.min(textareaHeight, MOBILE_COLLAPSED_MAX_HEIGHT); + const shouldStackPhoneComposer = + isMobilePhone && + inputMode === 'ai' && + collapsedMobileTextareaHeight >= MOBILE_COLLAPSED_MAX_HEIGHT; const mobileExpandedHeight = isMobilePhone && inputMode === 'ai' && isExpanded ? `${MOBILE_EXPANDED_HEIGHT_VH}vh` @@ -637,8 +645,9 @@ export function CommandInputBar({ onSubmit={handleMobileSubmit} style={{ display: 'flex', + flexDirection: shouldStackPhoneComposer ? 'column' : 'row', gap: '8px', - alignItems: 'flex-end', // Align to bottom for multi-line textarea + alignItems: shouldStackPhoneComposer ? 'stretch' : 'flex-end', paddingLeft: '16px', paddingRight: '16px', // Ensure form doesn't overflow screen width @@ -646,195 +655,253 @@ export function CommandInputBar({ overflow: 'hidden', }} > - {/* Mode toggle button - AI / Terminal */} - {/* NOTE: Mode toggle is NOT disabled when session is busy - user should always be able to switch modes */} - - - {/* Voice input button - only shown if speech recognition is supported */} - {voiceSupported && ( - - )} - - {/* Slash command button - only shown in AI mode */} - {inputMode === 'ai' && ( - - )} - {/* Terminal mode: $ prefix + input in a container - single line, tight height */} {inputMode === 'terminal' ? ( -
- {/* $ prompt */} - + {/* Mode toggle button - AI / Terminal */} + {/* NOTE: Mode toggle is NOT disabled when session is busy - user should always be able to switch modes */} + +
- $ - - } - type="text" - value={value} - onChange={(e) => - handleChange(e as unknown as React.ChangeEvent) - } - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - handleSubmit(e as unknown as React.FormEvent); + {/* $ prompt */} + + $ + + } + type="text" + value={value} + onChange={(e) => + handleChange(e as unknown as React.ChangeEvent) } - }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSubmit(e as unknown as React.FormEvent); + } + }} + placeholder={getPlaceholder()} + disabled={isDisabled} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck={false} + enterKeyHint="send" + style={{ + flex: 1, + padding: 0, + border: 'none', + backgroundColor: 'transparent', + color: isDisabled ? colors.textDim : colors.textMain, + fontSize: '17px', + fontFamily: 'ui-monospace, monospace', + outline: 'none', + width: '100%', + }} + onFocus={(e) => { + const container = e.currentTarget.parentElement; + if (container) container.style.borderColor = colors.accent; + onInputFocus?.(); + }} + onBlur={(e) => { + const container = e.currentTarget.parentElement; + if (container) container.style.borderColor = colors.border; + onInputBlur?.(); + }} + aria-label="Shell command input" + /> +
+ + + ) : ( + <> + {!shouldStackPhoneComposer && ( + <> + {/* Mode toggle button - AI / Terminal */} + {/* NOTE: Mode toggle is NOT disabled when session is busy - user should always be able to switch modes */} + + + {/* Voice input button - only shown if speech recognition is supported */} + {voiceSupported && !shouldCompressPhoneActions && ( + + )} + + {/* Slash command button - only shown in AI mode */} + {!shouldCompressPhoneActions && ( + + )} + + )} + + {/* AI mode: regular textarea - on mobile phone, focus triggers expanded mode */} + {/* On mobile, collapsed state shows single-line height matching buttons */} +