Skip to content

Unify mobile and desktop UI into single responsive app#1041

Open
Donach wants to merge 13 commits into
preset-io:mainfrom
Code-Fixxers:claude/optimize-agor-mcp-tokens-TaNnK
Open

Unify mobile and desktop UI into single responsive app#1041
Donach wants to merge 13 commits into
preset-io:mainfrom
Code-Fixxers:claude/optimize-agor-mcp-tokens-TaNnK

Conversation

@Donach
Copy link
Copy Markdown

@Donach Donach commented Apr 19, 2026

Summary

This PR consolidates the separate mobile (/m) and desktop routes into a single unified Agor UI that adapts responsively to all device sizes. The desktop React Flow canvas is replaced with a touch-friendly list/drawer layout on compact viewports (phones and small tablets), while maintaining the full canvas experience on larger screens.

Key Changes

Mobile/Desktop Unification

  • Removed separate mobile routing and MobileApp component; the main App.tsx now handles all devices
  • Removed legacy mobile-only pages (SessionPage, MobileCommentsPage, MobileNavTree, MobileHeader, MobilePromptInput)
  • Added useIsCompactViewport() hook to detect phones/small tablets via media query and pointer type
  • Added MobileBoardView component that renders worktrees, sessions, and comments as scrollable lists with a Segmented tab switcher
  • Added MobileLegacyRedirect to handle old /m/* bookmarks and deep links, redirecting to canonical desktop routes

PWA Support

  • Added service worker (sw.js) with network-first navigation caching and stale-while-revalidate for static assets
  • Added manifest.webmanifest for installable app behavior on iOS, Android, macOS, Windows, and Linux
  • Added PWAShell component exposing install prompts, update notifications, and offline banners
  • Added usePWAInstall() and usePWAUpdate() hooks for install/update lifecycle management
  • Updated index.html with PWA metadata (apple-touch-icon, theme-color, manifest link)
  • Added responsive CSS overrides for compact layouts (tighter headers, full-width drawers)

Session Panel & Comments

  • On compact viewports, the selected session now opens in a full-screen Drawer instead of a side panel
  • Comments panel is accessible via a drawer on mobile; desktop retains the resizable side panel
  • Session selection and comment interactions work identically across all device sizes

MCP & Daemon Updates

  • Simplified MCP server instructions and tool descriptions for clarity
  • Updated agor_sessions_list, agor_worktrees_get, and agor_artifacts_publish tool schemas with shorter descriptions
  • Added buildContentBlocksFromParts() method in OpenCodeTool to handle reasoning-only responses as user-visible text
  • Improved response formatting to avoid JSON pretty-printing in MCP results

Infrastructure

  • Added flake.nix for Nix-based build/run/publish workflows
  • Added GitHub Actions workflow for manual agor-live package publishing
  • Updated agor-live build script to prioritize daemon over CLI
  • Added PWA base-path compatibility tests to ensure manifest and SW work under /ui/ sub-paths

Cleanup

  • Removed prompt draft state management from main App.tsx (drafts now handled per-session)
  • Removed DeviceRouter component that previously redirected between / and /m routes
  • Updated daemon config to detect sub-path hosting and register service worker accordingly

Notable Implementation Details

  • Responsive breakpoint: max-width: 767.5px or (pointer: coarse) and (max-width: 1023.5px) triggers compact mode
  • Session drawer: Uses Drawer with placement="right" and width="100%" for full-screen mobile experience
  • Service worker scope: Dynamically derived from self.registration.scope to support both root (/) and sub-path (/ui/) deployments
  • Backward compatibility: Legacy /m/* URLs are transparently redirected to their desktop equivalents
  • Canvas fallback: Desktop users with React Flow canvas; mobile users get list-based navigation with drawer panels

This change enables a single codebase to deliver native-app-like experiences across all platforms while maintaining feature parity with the previous desktop UI.

https://claude.ai/code/session_0175z9ax55y9qoY1bWCn2dMJ

Donach and others added 13 commits April 18, 2026 00:35
Personal API keys (agor_sk_*) exist so external orchestrators (Hermes,
etc.) can call Agor MCP without binding to a specific session. The
recently-added session-context gate forced every API-key request to
supply ?sessionId=/X-Agor-Session-Id, which broke tool discovery and
any sessionless workflow.

Drop the hard gate; tools that genuinely need a current session will
surface their own error when ctx.sessionId is undefined.

Also fix the agor-live build ordering: daemon must build before the
CLI because apps/agor-cli dynamically imports @agor/daemon, which
tsup DTS cannot resolve otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): full-feature PWA with phone-friendly drawer layout

Restores phone usability and delivers desktop feature parity on mobile,
and fixes PWA behavior when the UI is hosted under /ui.

Routing parity
--------------
* Remove the device-detection redirect that forced phones onto the
  trimmed-down /m shell. The full Agor UI now renders on every device.
* /m/* is preserved as a legacy redirect (MobileLegacyRedirect) so
  existing PWA installs and bookmarks resolve to canonical
  /b/:board/:session/ URLs, preserving session context when available.

Feature parity on phone
-----------------------
* useIsCompactViewport (matchMedia; max-width <768 OR coarse-pointer
  tablet) drives a layout switch in components/App/App.tsx.
* On compact viewports the React Flow canvas is replaced by
  MobileBoardView — a Segmented control over Worktrees/Sessions/Comments
  lists with a floating action button for new sessions.
* SessionPanel, CommentsPanel, and EventStreamPanel open as full-screen
  Drawers on mobile instead of side panels. All desktop modals, dialogs,
  and tool flows remain available.

/ui PWA correctness
-------------------
* manifest.webmanifest uses relative start_url/scope/icons ("./") so it
  resolves correctly whether hosted at / or /ui/.
* index.html manifest link uses Vite's %BASE_URL% so deep SPA cold loads
  still resolve the manifest to an absolute path.
* Service worker rewritten: derives basePath from self.registration.scope,
  network-first for navigation (no more stale shells), stale-while-
  revalidate for static assets, hard bypass for API/socket.io/MCP/
  EventSource/WebSocket traffic, and a SKIP_WAITING channel so users
  reload into new deploys without clearing caches.
* usePWAUpdate hook + <PWAShell /> banner surface install/update/offline
  state on every route.
* getDaemonUrl() now uses window.location.origin in production, fixing
  cross-origin WSS blocks on mobile networks/Brave/Firefox when served
  through an HTTPS reverse proxy at / or /ui/.

Viewport/keyboard usability
---------------------------
* index.html ships apple-touch-icon, mask-icon, mobile-web-app-capable,
  safe-area padding (env(safe-area-inset-*)), 100dvh root, dynamic
  viewport, overscroll-behavior:none, and format-detection.
* Responsive CSS overrides shrink the antd header, full-bleed modals,
  hide unusable react-resizable-panels handles, enlarge tap targets,
  and respect iOS safe-area insets in standalone display mode.

Device detection hardening
--------------------------
* isMobileDevice/isCompactViewport documented as UX-affordance only --
  must never control route ownership.
* isOnMobileRoute renamed to isCompactViewport (width-based) since the
  old pathname check is now meaningless.

Cleanup
-------
* Delete dead /m-only shell: MobileApp, MobileCommentsPage, MobileHeader,
  MobileNavTree, MobilePromptInput, SessionPage.
* Drop write-only promptDrafts state in App.tsx -- SessionPanel already
  persists per-session drafts via localStorage.

Tests
-----
* pwa-base-path.test.ts: manifest relative paths, %BASE_URL% link,
  service worker scope/basePath/NO_CACHE_PREFIXES/SKIP_WAITING.
* MobileLegacyRedirect.test.tsx: /m, /m/comments/:boardId,
  /m/session/:id with known and missing session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(api-keys): register patch/remove on base service so revoke works

Feathers dispatches methods on the service name, so calling
`client.service('api/v1/user/api-keys').remove(id)` requires the
`remove` method to exist on that exact service. The previous
registration split the CRUD methods across two services
(`api-keys` for find/create and `api-keys/:id` for patch/remove),
causing "Method 'remove' not allowed on service
'api/v1/user/api-keys'" when users tried to revoke a key from the
Personal API Keys settings tab.

Consolidate all four methods on the single base service. Feathers'
REST provider maps `DELETE /api-keys/:id` to `service.remove(id, params)`
and `PATCH /api-keys/:id` to `service.patch(id, data, params)`
automatically, so no separate `/:id` route is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
External orchestrators (Hermes, etc.) were triggering rapid context
compaction because the MCP surface was bloated at multiple layers:

- Tool responses were pretty-printed with 2-space indentation
  (JSON.stringify(_, null, 2)), inflating every payload 30-40%.
- SERVER_INSTRUCTIONS carried ~1.8 KB of domain lists and workflow
  examples on every initialize.
- agor_artifacts_publish embedded ~1.6 KB of CONFIG CONVENTION docs
  in its description; useLocalBundler and artifacts_status carried
  hundreds more chars each.
- Session tools had prose-heavy enum/parameter descriptions
  (sessionType parentheticals, spawn/prompt narratives,
  get_current_context field list).
- Worktree tools defaulted _include_sessions:true on every get,
  scaling response size with session count, and othersCan/
  othersFsAccess fields repeated tier documentation.
- Search/execute meta-tools had long self-descriptions and returned
  unused hint + domains blobs on every call.

Trim every layer. No tool names or schemas removed — only description
length, default includes, and response formatting. DOMAIN_DESCRIPTIONS
collapsed. agor_worktrees_get now takes an opt-in includeSessions
flag (default: false).

Expected ≥50% reduction in per-call token cost for typical workflows.
Two related bugs found while reviewing the MCP surface:

1. Pagination was unbounded. `PAGINATION.DEFAULT_LIMIT = 10_000` in
   packages/core — every MCP list tool whose description claimed
   "default: 50" actually returned up to 10,000 rows because nothing
   set $limit when the agent omitted it. Affected: sessions, worktrees,
   boards, tasks, repos, users. Set an explicit default of 50 in each
   tool handler so the schema's stated default is actually enforced.

2. Archived items leaked into several tools by default:
   - agor_cards_list: three code paths (findByZoneId, findByCardTypeId,
     unfiltered find) never passed archived=false. Unified on the
     includeArchived/archived opt-in pattern (same as sessions/boards)
     and added post-filter for the paths where the repo doesn't accept
     an archived arg.
   - agor_tasks_list: tasks have no archived column and the tool didn't
     join to sessions, so tasks from archived sessions were returned.
     When called without sessionId, scope to active-session IDs. Added
     an includeArchived opt-in.
   - agor_messages_list: unscoped search bled into archived sessions.
     When no sessionId/taskId is given, restrict via inArray on active
     session IDs. Added an includeArchived opt-in.

These are semantic (correctness) fixes as much as token fixes — once a
user archives something, it should stay out of agent context by
default.
Review feedback: the previous trim dropped the workflow recipes which
genuinely save agents discovery round-trips. Keep the three common
workflows (orient, create-and-start, delegate, continue/fork) and a
one-line product description — agents still get the domain listing
from agor_search_tools itself, so that part stays out.

~900 bytes vs the original ~1,800 and the over-trimmed ~300.
@mistercrunch
Copy link
Copy Markdown
Member

Holly molly that's a large PR. Let me pull it and try it.

@mistercrunch
Copy link
Copy Markdown
Member

Did a quick visual review and looks pretty rough. Maybe it's not reasonable to try and bring everything to mobile, or might take much more TLC to get it all responsive. For instance navbar doesn't fully fit-in, and opening the Settings modal is a bit of a disaster.

Screenshot 2026-04-20 at 3 52 00 PM

The original intent with the responsive mode was just to be able to prompt agent/sessions on-the-go. I think it probably makes sense to focus on just that, at least at first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants