Skip to content

[feat] Route phones to /m behind an off-by-default flag (5/12) - #5684

Draft
ardaerzin wants to merge 13 commits into
feat/mobile-image-and-cifrom
feat/mobile-device-gate
Draft

[feat] Route phones to /m behind an off-by-default flag (5/12)#5684
ardaerzin wants to merge 13 commits into
feat/mobile-image-and-cifrom
feat/mobile-device-gate

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

With a mobile app at /m, a phone that lands on the desktop app should be able to get to it, and a laptop that lands on /m should be able to get back. Neither should happen without the deployment asking for it: self-hosters who do not want a mobile surface must be unaffected.

Changes

The decision is a pure function in @agenta/shared (mobileGate), with three thin adapters: OSS middleware, EE middleware, and the mobile app's reverse gate. Keeping the decision separate from the adapters is what makes it testable without a request.

It is off by default. The middleware returns immediately unless the deployment sets AGENTA_MOBILE_GATE=true, read at request time so it can be flipped on a running standalone server. An ?view=desktop / ?view=mobile escape hatch sets a long-lived cookie either way, and an OAuth callback is never bounced mid-flow (that would drop the one-time code).

Tests / notes

  • Unit tests for the decision core, plus a UA-emulation Playwright smoke for the middleware.
  • Two live-QA defects are fixed here and recorded in the plan: the basePath root (/m with no trailing slash) needed an explicit "/" matcher entry, and the flag needed mirroring into a dev env file for the middleware sandbox.
  • The gate never hard-fails: every path is wrapped so a decision error falls through to "do nothing".

What to QA

  • With AGENTA_MOBILE_GATE unset, open / on a phone. Nothing redirects. This is the default and the one that matters for self-hosters.
  • Set it to true, then open / on a phone. You land on /m. Tap "View desktop site" and you stay on desktop across reloads.
  • Regression: sign in with a social provider on desktop with the flag on. The OAuth callback completes rather than bouncing.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 3, 2026 10:30pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da80586b-e570-4457-921d-09e8f4cbb7ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional device-aware routing between desktop and mobile experiences.
    • Mobile visitors can remain on mobile, while desktop visitors are redirected appropriately.
    • Added a “View desktop site” escape link with persistent opt-in/opt-out behavior.
    • Mobile development services can be enabled explicitly across supported environments.
  • Documentation

    • Added mobile design specifications, rollout guidance, deployment instructions, and implementation plans.
    • Documented the proposed headless chat architecture and mobile session-query work.
  • Tests

    • Added coverage for device detection, routing, cookies, redirects, and middleware behavior.

Walkthrough

The PR documents the Agenta Mobile architecture and execution plans. It adds a shared mobile-gate decision core, desktop and mobile middleware, opt-in runtime configuration, mobile escape navigation, Compose wiring, and unit and Playwright coverage.

Changes

Mobile platform and device gate

Layer / File(s) Summary
Mobile architecture and headless contracts
docs/design/agenta-mobile/design.md, docs/design/agenta-mobile/chat-headless-contract.md, docs/design/agenta-mobile/plans/2026-07-12-wp0-sessions-query-and-stamping.md
The documents define mobile routes, sessions requirements, shared chat behavior, headless hooks, neutral skin contracts, and residual session-query work.
Mobile foundation and infrastructure plans
docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md, docs/design/agenta-mobile/plans/2026-07-25-wp1-infra-tail.md, docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md
The plans specify the /m Next.js app, styling, lint rules, runtime configuration, @agenta/chat, container builds, CI, Compose services, startup commands, and rollout validation.
Device-gate decision core and middleware plan
docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md, docs/design/agenta-mobile/README.md
The WP5 plan and handoff record document shared gate decisions, middleware, cookies, route mapping, verification, runtime flag handling, and deferred wrapper retirement.
Device-gate wiring and validation
web/packages/agenta-shared/src/utils/mobileGate/index.ts, web/oss/src/middleware.ts, web/ee/src/middleware.ts, web/mobile/src/middleware.ts, hosting/docker-compose/*, web/mobile/tests/*, web/oss/tests/playwright/*
The implementation adds flag-gated forward and reverse routing, runtime flag propagation, mobile desktop-site navigation, and unit and Playwright tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant DesktopMiddleware
  participant MobileGate
  participant MobileMiddleware
  Browser->>DesktopMiddleware: Request a document route
  DesktopMiddleware->>MobileGate: Evaluate desktop gate
  MobileGate-->>DesktopMiddleware: Return redirect or continue
  DesktopMiddleware-->>Browser: Return 307 or next response
  Browser->>MobileMiddleware: Request a mobile route
  MobileMiddleware->>MobileGate: Evaluate reverse gate
  MobileGate-->>MobileMiddleware: Return redirect or continue
  MobileMiddleware-->>Browser: Return 307 or mobile response
Loading

Possibly related PRs

  • Agenta-AI/agenta#5569: Refactors AgentChatSlice and AgentConversation into reusable chat hooks and presentation components.
  • Agenta-AI/agenta#5679: Adds the web/mobile app and Compose setup extended here with device-gate routing.
  • Agenta-AI/agenta#5008: Concerns AgentChatSlice behavior and the useAgentChatQueue hook targeted for extraction.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the off-by-default mobile routing feature implemented by the pull request.
Description check ✅ Passed The description directly explains the mobile gate, routing behavior, flag, cookies, error handling, tests, and QA steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mobile-device-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (4)
docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md (2)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the GitButler workflow when workspace mode is active.

This instruction runs raw git branch --show-current before it detects GitButler workspace mode. Use but commands for workspace detection and commits. Add the required lane assignment and post-commit file-scope verification.

As per coding guidelines, use the but CLI instead of raw git branch or git commit when GitButler workspace mode is active; assign exactly one lane's files and verify each commit with git show --stat --name-only.

Source: Coding guidelines


463-468: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Run the required frontend lint fixer before each web commit.

The listed task sequences run tests, builds, or checks, but they omit pnpm lint-fix from web. Add it before each commit and fix all reported errors.

  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md#L463-L468: run cd web && pnpm lint-fix before committing the shared package changes.
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md#L541-L546: run cd web && pnpm lint-fix before committing the OSS and EE middleware changes.
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md#L819-L824: run cd web && pnpm lint-fix before committing the mobile middleware and UI changes.
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md#L988-L993: run cd web && pnpm lint-fix before committing the Playwright TypeScript changes.

As per coding guidelines, run pnpm lint-fix from web before committing web changes.

Source: Coding guidelines

web/oss/src/middleware.ts (1)

21-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing Vary: User-Agent header on gate redirects in both middleware twins. decideDesktopGate decisions depend on the User-Agent header and cookies, but neither web/oss/src/middleware.ts nor its documented byte-identical twin web/ee/src/middleware.ts sets a Vary header on the redirect/set-cookie-redirect responses. A shared cache or CDN in front of either deployment could serve a cached redirect to the wrong device class.

  • web/oss/src/middleware.ts#L21-L45: add response.headers.set("Vary", "User-Agent, Cookie") on both the redirect and set-cookie-redirect branches.
  • web/ee/src/middleware.ts#L21-L45: apply the identical fix to keep the twin files byte-identical, as required by the file's own "TWIN NOTE" comment.
docs/design/agenta-mobile/plans/2026-07-25-wp1-infra-tail.md (1)

121-131: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Extend path filters when workspace dependencies are wired.

The trigger currently excludes web/packages/**. When Task 7 adds @agenta/entities, @agenta/shared, or @agenta/chat imports, changes in those packages can change the mobile image without starting this workflow.

Add the specific dependency paths, or a scoped web/packages/** filter, when the Dockerfile and Turbo dependency wiring lands.

Also applies to: 571-574


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b4e177e9-4308-43e8-ac09-b9952611442f

📥 Commits

Reviewing files that changed from the base of the PR and between 85f8fd4 and 45fa66f.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • docs/design/agenta-mobile/README.md
  • docs/design/agenta-mobile/chat-headless-contract.md
  • docs/design/agenta-mobile/design.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp0-sessions-query-and-stamping.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md
  • docs/design/agenta-mobile/plans/2026-07-25-wp1-infra-tail.md
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md
  • hosting/docker-compose/ee/docker-compose.dev.yml
  • hosting/docker-compose/ee/docker-compose.gh.yml
  • hosting/docker-compose/ee/env.ee.dev.example
  • hosting/docker-compose/oss/docker-compose.dev.yml
  • hosting/docker-compose/oss/docker-compose.gh.yml
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/docker-compose/run.sh
  • web/ee/src/middleware.ts
  • web/mobile/package.json
  • web/mobile/src/middleware.ts
  • web/mobile/src/pages/index.tsx
  • web/mobile/tests/unit/middleware.test.ts
  • web/mobile/vitest.config.ts
  • web/oss/src/middleware.ts
  • web/oss/tests/playwright/acceptance/mobile-gate/gate.spec.ts
  • web/packages/agenta-shared/package.json
  • web/packages/agenta-shared/src/utils/mobileGate/index.ts
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts
  • web/turbo.json

Comment on lines +83 to +90
useTurnRenderModel(message, ctx): RenderItem[] // lifted from AgentMessage.tsx:329-416
useComposerAttachments({sessionId, limits}): {files, rejections, add, remove, clear, atMax, toParts}
useSessionHydration({sessionId})
useApprovalDock({messages, onRespond}): {current, count, respond, approveAll, renderer}
useClientToolDispatch()
useAgentChatQueue(...) // exists — moves in
useAgentModelKeyStatus(...) // exists — moves in
useConversationScroll(ref, {messages, status}) // DESKTOP-ONLY opt-in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)(chat-headless-contract\.md|.*WP3.*|.*approval.*|.*Approvall.*|.*Approval.*)' || true

echo
echo "== target lines =="
if [ -f docs/design/agenta-mobile/chat-headless-contract.md ]; then
  sed -n '1,130p' docs/design/agenta-mobile/chat-headless-contract.md | cat -n
fi

echo
echo "== search useApprovalDock =="
rg -n "useApprovalDock|approval|Approval|approveAll|renderer" -S . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -n 200

echo
echo "== search WP3 contract snippets =="
rg -n "function useApprovalDock|useApprovalDock\\(|approveAll|registry|renderer|render" docs/design/agenta-mobile -S . || true

Repository: Agenta-AI/agenta

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== changed files if available =="
git diff --name-only -- docs/design/agenta-mobile/chat-headless-contract.md || true

echo
echo "== staged/unstaged diff around target =="
git diff -- docs/design/agenta-mobile/chat-headless-contract.md | sed -n '1,220p' || true

echo
echo "== locate markdown by exact filename variants =="
fd -a 'chat-headless-contract\.md$' . || true

Repository: Agenta-AI/agenta

Length of output: 350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target document relevant sections =="
sed -n '80,112p' docs/design/agenta-mobile/chat-headless-contract.md | cat -n

echo
echo "== WP3 plan approval hook sections =="
sed -n '680,705p' docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md | cat -n

echo
echo "== WP3 plan approval registry section =="
sed -n '705,740p' docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md | cat -n

echo
echo "== README summary references =="
sed -n '120,135p' docs/design/agenta-mobile/README.md | cat -n

Repository: Agenta-AI/agenta

Length of output: 11550


Remove renderer from the useApprovalDock contract.

useApprovalDock is documented to return only current, count, respond, and approveAll; approval body rendering is resolved through the approval registry and approved copy overrides. Return the registry-entry values from resolveApprovalBody, or update the contract and WP3a task contract consistently before implementation.

Comment thread docs/design/agenta-mobile/design.md
Comment on lines +197 to +202
**Files**
- Modify: `api/oss/src/core/sessions/service.py` (`query_sessions` — after fetching streams,
batch-fetch the latest turn per session via `SessionTurnsDAO` (one query,
`DISTINCT ON (session_id) ... ORDER BY session_id, turn_index DESC` or the DAO's existing
latest-turn helper from the turn-index fix `9613e7964e`) and attach `references` (+
`trace_id` if cheap) to each row)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Require a batch latest-turn query for R3.

R3 requires one query for all session IDs, but this step permits the existing latest_turn helper. The audit states that helper is per-session and that no batch helper exists. Using it makes /sessions/query an N+1 path. Require latest_turn_per_session(session_ids) with DISTINCT ON, and keep the one-call assertion in the service test.

Comment thread docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md
Comment on lines +515 to +516
- [ ] `cd web && pnpm turbo run lint --filter=@agenta/mobile --dry=json | python3 -c "import json,sys; d=json.load(sys.stdin); print([t['taskId'] for t in d['tasks']])"` → includes `@agenta/mobile#lint` (proves `pnpm run lint` in `11-check-code-styling.yml:96` reaches mobile, tokens:check chained).
- [ ] `cd web && pnpm -r --filter=!agenta-web-tests --if-present run test:unit --reporter=dot 2>&1 | grep -i chat` → `@agenta/chat` suite executes (the exact Phase-1 command from `run-tests.ts:200`); confirm `web/packages/agenta-chat/test-results/junit.xml` exists afterwards (matches the publish glob `12:133-135`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve failures from the verification commands.

Lines 515-516 pipe Turbo and unit-test output into Python or grep without pipefail. A failed producer can therefore produce a successful overall command.

Add set -o pipefail before these pipelines, or capture and check the producer exit status separately.

🧰 Tools
🪛 LanguageTool

[locale-violation] ~516-~516: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...nta-chat/test-results/junit.xmlexists afterwards (matches the publish glob12:133-135`)...

(AFTERWARDS_US)

Comment on lines +536 to +538
docker run -d --name mobile-smoke -p 3000:3000 agenta-web-mobile:smoke
sleep 3
curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/m # expect: 200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Replace the fixed three-second wait with a readiness loop.

Line 536 uses sleep 3 before checking the server. A slower runner can fail before the entrypoint and Next.js server finish starting.

Reuse the bounded polling loop from the workflow. Add an exit trap to remove mobile-smoke after failures.

}
```

- [ ] Run: `cd web && pnpm --filter @agenta/shared test:unit -- mobileGate` — expect **25 passed**.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the expected unit-test count.

web/packages/agenta-shared/tests/unit/mobileGate.test.ts defines 27 tests. This step says to expect 25 passed. Update the expected count.

Comment on lines +68 to +71
Opt-in in dev too: `run.sh --dev --with-mobile` (originally it rode `with-web` and auto-started,
but a live dev run showed the second Next dev server pushes an 8GB Docker VM into OOM-killing
the main web app's first big Turbopack compile — dmesg-confirmed `next-server` kills at ~4.5GB
RSS. Running both dev servers comfortably wants a 12GB+ VM).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the documented Compose entrypoint path.

The command does not state a working directory. Use hosting/docker-compose/run.sh and specify the required OSS or EE edition with the dev and mobile options.

As per coding guidelines, run the local dev stack through hosting/docker-compose/run.sh and use the same edition and image mode as the loaded environment.

Source: Coding guidelines

Comment on lines +154 to +169
### WP5 device gate — EXECUTED (2026-07-26, 5 commits)

Ships the mobile device gate (design.md "Gate and routing") behind a runtime flag,
`AGENTA_MOBILE_GATE`, **default off**: with the flag off, request behavior is byte-identical to
today. `NoMobilePageWrapper` retirement (T8) is specified but deliberately **not executed** —
it ships only in the deployment window where the flag is actually flipped on. See
[plans/2026-07-26-wp5-device-gate.md](./plans/2026-07-26-wp5-device-gate.md) for the full task
breakdown and grounding facts.

| Commit | Content |
|--------|---------|
| `cf272e1227` | T1: `@agenta/shared/utils/mobileGate` — pure, framework-free decision core (detection, deep-link maps, cookie semantics, documented exceptions); 27 unit tests in the package's vitest harness |
| `6b3aaf5654` | T2: `web/oss/src/middleware.ts` + `web/ee/src/middleware.ts` — twin desktop forward-gate adapters (byte-identical, both NEW files) wrapping the shared core |
| `2df5e4d2a1` | T3: `web/mobile/src/middleware.ts` — reverse-gate middleware carrying a declared verbatim copy of the reverse-gate subset (mobile has zero workspace deps until WP2), new minimal mobile vitest harness, and the "View desktop site" `?view=desktop` escape link on the placeholder page |
| `7be0b8d528` | T4: plumb `AGENTA_MOBILE_GATE` through dev + gh compose files (default `false`), documented in both dev env examples |
| `9ac651525f` | T6: self-skipping Playwright UA-emulation smoke (`web/oss/tests/playwright/acceptance/mobile-gate/gate.spec.ts`), 6 tests, skips unless the runner asserts `AGENTA_MOBILE_GATE=true` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reconcile the WP5 commit record.

The heading says five commits and the table lists five commits. The section also claims T1-T7 executed and later refers to a six-commit review. Add the T7 documentation commit and update the count, or correct the execution claim.

Also applies to: 182-182

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/design/agenta-mobile/README.md (2)

41-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove executed WP5 work from the unwritten wave-2 list.

This file records WP5 T1-T7 as executed at Lines 154-169, but these sections still list WP5 as unwritten work. Keep WP2, WP3b, and WP4 in the wave-2 planning list. State that only T8 remains deferred.

Also applies to: 221-225


231-236: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make sequential shell snippets independent of the current working directory.

Both snippets change directories and then use paths that assume the repository root. Use subshells, reset to the repository root, or use paths relative to the current directory.

  • docs/design/agenta-mobile/README.md#L231-L236: check mobile/.next/standalone/mobile/server.js after entering web.
  • docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md#L39-L49: wrap each verification command in (cd ... && ...) or change directory once.
♻️ Duplicate comments (3)
docs/design/agenta-mobile/plans/2026-07-12-wp0-sessions-query-and-stamping.md (1)

3-15: 📐 Maintainability & Code Quality | 🟠 Major

Reconcile the execution status in both plans.

Each header claims completion while later text records unfinished or unverified work.

  • docs/design/agenta-mobile/plans/2026-07-12-wp0-sessions-query-and-stamping.md#L3-L15: Keep one authoritative state for the four tasks.
  • docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md#L3-L13: Mark Phase 6 unverified, or verify the image build before claiming all phases are complete.
docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md (2)

787-793: 🎯 Functional Correctness | 🟠 Major

Use the executed dark-theme role-map values.

The header says dark accent must use p.scales.zinc[2].dark and dark destructive-foreground must use p.componentsDark.Button.primaryColor. The map still uses p.surface.controlItemBgActive.dark and p.surface.white.dark, which generates incorrect dark tokens.


930-935: 🎯 Functional Correctness | 🟡 Minor

Use paths relative to the command working directory.

Both verification blocks change directory and then retain repository-relative prefixes.

  • docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md#L930-L935: After cd web/mobile, use src/components/ui and package.json.
  • docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md#L1095-L1100: After cd web, use mobile/.next/..., or run the checks from the repository root.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d098db38-1061-46ef-a31b-4c754dd726da

📥 Commits

Reviewing files that changed from the base of the PR and between 45fa66f and e9e9e65.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • docs/design/agenta-mobile/README.md
  • docs/design/agenta-mobile/chat-headless-contract.md
  • docs/design/agenta-mobile/design.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp0-sessions-query-and-stamping.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md
  • docs/design/agenta-mobile/plans/2026-07-12-wp3a-chat-headless-core.md
  • docs/design/agenta-mobile/plans/2026-07-25-wp1-infra-tail.md
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md
  • hosting/docker-compose/ee/docker-compose.dev.yml
  • hosting/docker-compose/ee/docker-compose.gh.yml
  • hosting/docker-compose/ee/env.ee.dev.example
  • hosting/docker-compose/oss/docker-compose.dev.yml
  • hosting/docker-compose/oss/docker-compose.gh.yml
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/docker-compose/run.sh
  • web/ee/src/middleware.ts
  • web/mobile/package.json
  • web/mobile/src/middleware.ts
  • web/mobile/src/pages/index.tsx
  • web/mobile/tests/unit/middleware.test.ts
  • web/mobile/vitest.config.ts
  • web/oss/src/middleware.ts
  • web/oss/tests/playwright/acceptance/mobile-gate/gate.spec.ts
  • web/packages/agenta-shared/package.json
  • web/packages/agenta-shared/src/utils/mobileGate/index.ts
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts
  • web/turbo.json
🚧 Files skipped from review as they are similar to previous changes (20)
  • hosting/docker-compose/oss/env.oss.dev.example
  • web/packages/agenta-shared/package.json
  • hosting/docker-compose/ee/docker-compose.gh.yml
  • web/turbo.json
  • hosting/docker-compose/oss/docker-compose.gh.yml
  • web/mobile/src/pages/index.tsx
  • web/oss/tests/playwright/acceptance/mobile-gate/gate.spec.ts
  • web/mobile/package.json
  • web/mobile/vitest.config.ts
  • web/oss/src/middleware.ts
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts
  • hosting/docker-compose/run.sh
  • hosting/docker-compose/ee/env.ee.dev.example
  • web/ee/src/middleware.ts
  • hosting/docker-compose/oss/docker-compose.dev.yml
  • web/mobile/tests/unit/middleware.test.ts
  • web/mobile/src/middleware.ts
  • hosting/docker-compose/ee/docker-compose.dev.yml
  • web/packages/agenta-shared/src/utils/mobileGate/index.ts
  • docs/design/agenta-mobile/plans/2026-07-26-wp5-device-gate.md

Comment on lines +111 to +113
- Escape hatch both ways: a `agenta-mobile-optout` / `agenta-mobile-optin` cookie set by
"View desktop site" / "Open mobile version" links; middleware honors it. This also fixes the
current banner's dismissal-not-persisted annoyance.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== design file slice =="
sed -n '90,125p' docs/design/agenta-mobile/design.md 2>/dev/null || true

echo
echo "== relevant mentions =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'agenta-mobile-(optin|optout)|View desktop site|Open mobile version|mobile-gate|mobile gate|query parameter|queryParams|preference|cookie' \
  . | head -n 200

Repository: Agenta-AI/agenta

Length of output: 26895


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== shared mobileGate implementation =="
sed -n '1,190p' web/packages/agenta-shared/src/utils/mobileGate/index.ts 2>/dev/null || true

echo
echo "== OSS middleware route/contract =="
sed -n '1,70p' web/oss/src/middleware.ts 2>/dev/null || true

echo
echo "== design section around Rules with context =="
sed -n '96,120p' docs/design/agenta-mobile/design.md 2>/dev/null || true

echo
echo "== deterministic query precedence probe from source text =="
python3 - <<'PY'
from pathlib import Path
p = Path('web/packages/agenta-shared/src/utils/mobileGate/index.ts')
s = p.read_text()
print('MOBILE_OPTIN_COOKIE=', 'MOBILE_OPTIN_COOKIE' in s)
print('MOBILE_OPTOUT_COOKIE=', 'MOBILE_OPTOUT_COOKIE' in s)
for needle in ['input.cookie(MOBILE_OPTIN_COOKIE)', 'input.cookie(MOBILE_OPTOUT_COOKIE)', '?view=mobile', '?view=desktop']:
    print(f'contains {needle!r}:', needle in s)
print('cookie preference checks precede query settings:', s.index('input.cookie(MOBILE_OPTIN_COOKIE)') < s.index('?view=mobile'))
PY

Repository: Agenta-AI/agenta

Length of output: 11156


Document the mobile-gate query parameters and precedence.

decideDesktopGate and decideMobileGate accept ?view=desktop / ?view=mobile, strip them after setting the corresponding cookie, and existing cookies are honored before query-based redirect decisions. Update this section to name view and state that a matching cookie overrides the query parameter.

Comment on lines +111 to +114
- Escape hatch both ways: a `agenta-mobile-optout` / `agenta-mobile-optin` cookie set by
"View desktop site" / "Open mobile version" links; middleware honors it. This also fixes the
current banner's dismissal-not-persisted annoyance.
- `NoMobilePageWrapper` is retired once the gate ships.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align wrapper retirement with the flag-flip runbook.

The design says to retire NoMobilePageWrapper when the gate ships. The current rollout keeps T8 unexecuted and retires the wrapper only after AGENTA_MOBILE_GATE=true, WP2, and WP4 are ready. Update this condition to prevent premature removal.

Comment on lines +172 to +187
### Residual gaps (the revised WP0 scope)

1. **`updated_at` ordering/cursor** — the query DAO windows on `id` (uuid7 ≈ creation order);
the FE sorts client-side per page, which breaks "last-activity" ordering across pages for
infinite scroll. Add `updated_at` support to `apply_windowing` + switch the sessions query
to it.
2. **Title search** — `SessionQuery` has no free-text filter; `name` is now a real column, so
this is a plain `ilike`.
3. **References echo on list rows** — `/sessions/query` rows do not carry the session's agent
references, but the mobile list must label each row with its agent and resolve
continue-session without N per-session turn lookups. Hydrate latest-turn references onto the
response rows (the service already joins turns for filtering).
4. **Runtime-shape zod test for `querySessions`** — the wrapper + schema exist but the drift-
pinning unit test was never written (the false-green tsc class).
5. Optional, deferrable: liveness-flags filter on the root query (mobile can filter client-side
from the returned `flags`); Fern regen so `include_ended` stops being a runtime cast.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark the WP0 residuals as completed or historical.

The README states that WP0 is complete and that updated ordering, search, references, and the zod wire test are implemented. This file still presents those items as open gaps and active WP0 scope. Move them to an implemented-history section or update their status.

Also applies to: 367-367

Comment on lines +89 to +93
"""apply_windowing must support `updated_at` as the order/cursor attribute.

The sessions list is ordered by last activity (`updated_at` is heartbeat-fed on
session_streams). Both ORDER BY and the keyset cursor filters must ride updated_at —
ordering by updated_at while cursor-filtering on another column paginates incorrectly.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep R1 NULL-safe in both implementation and tests.

The execution note requires coalesce(updated_at, created_at), but these steps still use plain updated_at. Because updated_at is nullable, descending PostgreSQL ordering places null rows first.

The test also rejects created_at in the full SQL statement. select(SessionStreamDBE) includes that column, and the correct ordering intentionally uses it inside coalesce.

Update the implementation and cursor instructions to use the same coalesced expression. Inspect the WHERE and ORDER BY fragments separately, and reject only bare created_at references.

Also applies to: 122-139, 148-149

Comment on lines +350 to +363
- [ ] In `web/turbo.json`, add these three task entries inside `"tasks"` (place after `"@agenta/ee#build"`; the generic `dev` task already covers `pnpm dev-mobile`):
```json
"@agenta/mobile#build": {
"inputs": [
"src/**",
"public/**",
"next.config.ts",
"postcss.config.mjs",
"tsconfig.json",
"components.json"
],
"outputs": [".next/**", "!.next/cache/**"],
"env": ["NODE_ENV", "NEXT_PUBLIC_*"]
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg '(^|/)(package.json|turbo.json|pnpm-lock.yaml)$|2026-07-12-wp1-mobile-foundation\.md$' || true

echo
echo "== target plan excerpt =="
if [ -f docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md ]; then
  wc -l docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md
  sed -n '330,375p' docs/design/agenta-mobile/plans/2026-07-12-wp1-mobile-foundation.md
fi

echo
echo "== existing web/turbo.json =="
if [ -f web/turbo.json ]; then
  wc -l web/turbo.json
  sed -n '1,220p' web/turbo.json
fi

echo
echo "== package manifests and lockfile package entry examples =="
for f in package.json web/package.json mobile/package.json apps/mobile/package.json packages/mobile/package.json pnpm-lock.yaml; do
  if [ -f "$f" ]; then
    echo "-- $f --"
    sed -n '1,220p' "$f"
  fi
done

echo
echo "== turbo defaults reference in docs/code =="
rg -n '"$TURBO_DEFAULT$"|TURBO_DEFAULT|array\[.*package\.json|`@agenta/mobile`#build|turbo.*inputs' -S .

Repository: Agenta-AI/agenta

Length of output: 17760


🌐 Web query:

Turbo $TURBO_DEFAULT$ default package.json inputs documentation

💡 Result:

In Turborepo, the inputs key in your turbo.json file defines the file glob patterns used to calculate a task's hash for caching purposes [1][2]. When you define an inputs array for a task, you effectively opt out of Turborepo's default behavior, which is to include all files in the package that are tracked by Git [3][1]. Because defining inputs disables this default behavior (and causes Turborepo to stop respecting your .gitignore for that specific task), you may need to restore the default behavior while still applying custom modifications [1][2]. The $TURBO_DEFAULT$ microsyntax is used to re-introduce those default inputs into your custom list [3][4]. Common Usage: You can use $TURBO_DEFAULT$ to build upon the default set of inputs, such as when you want to ignore a specific file that doesn't affect the task's output [1][2]. Example: { "tasks": { "build": { "inputs": ["$TURBO_DEFAULT$", "!README.md"] } } } In this example, Turborepo includes all tracked files (the default behavior) but ignores changes to the README.md file, allowing the task to hit the cache even if the README is modified [1][2]. Important Notes: 1. Default Inputs: By default, if no inputs key is provided, all files in the package that are tracked by Git are included [1][2]. 2. Mandatory Inputs: Certain files, such as package.json, turbo.json, and package manager lockfiles, are always considered inputs, even if you attempt to exclude them [3][5][6]. 3. Interaction with global.inputs: If futureFlags.globalConfiguration is enabled, files specified in global.inputs are automatically prepended to every task's inputs [4][7].

Citations:


Keep package.json in the mobile build cache key.

Defining inputs overrides Turbo’s default file selection, so @agenta/mobile#build only hashes the fields currently listed. Add $TURBO_DEFAULT$ with a !tests/** ignore, or list web/mobile/package.json and any other manifest that affects the build.

Comment on lines +193 to +198
- name: Determine build metadata
id: meta
run: |
PR="${{ github.event.pull_request.number }}"
INPUT_TAG="${{ inputs.image_tag }}"
SHA="$(git rev-parse --short HEAD)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not interpolate inputs.image_tag into shell source.

Line 197 inserts a workflow-dispatch input directly into INPUT_TAG="...". A value containing shell syntax can execute commands in the prepare job. Pass dynamic values through env: and validate the tag before using it.

Proposed fix
       - name: Determine build metadata
         id: meta
+        env:
+          INPUT_TAG: ${{ inputs.image_tag }}
+          PR_NUMBER: ${{ github.event.pull_request.number }}
         run: |
-          PR="${{ github.event.pull_request.number }}"
-          INPUT_TAG="${{ inputs.image_tag }}"
+          PR="$PR_NUMBER"
           SHA="$(git rev-parse --short HEAD)"
+
+          if [[ -n "$INPUT_TAG" && ! "$INPUT_TAG" =~ ^[A-Za-z0-9._-]+$ ]]; then
+            echo "::error::image_tag contains invalid characters"
+            exit 1
+          fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Determine build metadata
id: meta
run: |
PR="${{ github.event.pull_request.number }}"
INPUT_TAG="${{ inputs.image_tag }}"
SHA="$(git rev-parse --short HEAD)"
- name: Determine build metadata
id: meta
env:
INPUT_TAG: ${{ inputs.image_tag }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
PR="$PR_NUMBER"
SHA="$(git rev-parse --short HEAD)"
if [[ -n "$INPUT_TAG" && ! "$INPUT_TAG" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "::error::image_tag contains invalid characters"
exit 1
fi

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.

1 participant