Unify mobile and desktop UI into single responsive app#1041
Open
Donach wants to merge 13 commits into
Open
Conversation
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.
Member
|
Holly molly that's a large PR. Let me pull it and try it. |
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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
MobileAppcomponent; the mainApp.tsxnow handles all devicesSessionPage,MobileCommentsPage,MobileNavTree,MobileHeader,MobilePromptInput)useIsCompactViewport()hook to detect phones/small tablets via media query and pointer typeMobileBoardViewcomponent that renders worktrees, sessions, and comments as scrollable lists with a Segmented tab switcherMobileLegacyRedirectto handle old/m/*bookmarks and deep links, redirecting to canonical desktop routesPWA Support
sw.js) with network-first navigation caching and stale-while-revalidate for static assetsmanifest.webmanifestfor installable app behavior on iOS, Android, macOS, Windows, and LinuxPWAShellcomponent exposing install prompts, update notifications, and offline bannersusePWAInstall()andusePWAUpdate()hooks for install/update lifecycle managementindex.htmlwith PWA metadata (apple-touch-icon, theme-color, manifest link)Session Panel & Comments
Drawerinstead of a side panelMCP & Daemon Updates
agor_sessions_list,agor_worktrees_get, andagor_artifacts_publishtool schemas with shorter descriptionsbuildContentBlocksFromParts()method in OpenCodeTool to handle reasoning-only responses as user-visible textInfrastructure
flake.nixfor Nix-based build/run/publish workflowsagor-livepackage publishingagor-livebuild script to prioritize daemon over CLI/ui/sub-pathsCleanup
App.tsx(drafts now handled per-session)DeviceRoutercomponent that previously redirected between/and/mroutesNotable Implementation Details
max-width: 767.5pxor(pointer: coarse) and (max-width: 1023.5px)triggers compact modeDrawerwithplacement="right"andwidth="100%"for full-screen mobile experienceself.registration.scopeto support both root (/) and sub-path (/ui/) deployments/m/*URLs are transparently redirected to their desktop equivalentsThis 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