Commit Graph

1 Commits

Author SHA1 Message Date
Devin Foley 97590ff8c4
feat(dev): add pnpm dev:mobile and dev:both for prebuilt UI preview (#10718)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI is a React SPA served by the paperclip server; the
standard local dev flow is `pnpm dev`, which runs vite in dev mode with
HMR and an unbundled module graph
> - The unbundled dev bundle is hundreds of MB of JS across many
requests, which is fine on a local machine but unusable from a phone or
tablet on slow/lossy links (airplane wifi, mobile data, distant tailnet
peers)
> - Contributors who want to iterate on the board from a mobile device
today have no supported way to preview a small production-shaped bundle
without stopping the dev server and running a one-off `vite preview`
with manual proxy plumbing
> - This pull request adds `pnpm dev:mobile` — build the UI and serve
`ui/dist` via `vite preview` on port 3101, with `/api` proxied to the
running dev server on 3100 — plus `pnpm dev:both` to run both flavors
together
> - The benefit is a supported second flavor of the dev server for
phones/tablets that runs alongside the normal one, without touching the
primary `pnpm dev` flow

## Linked Issues or Issue Description

**Subsystem affected**

ui/ — React + Vite board UI

**Problem or motivation**

The vite dev server serves an unbundled module graph, which is fine on
localhost but unusable from a phone or tablet on a slow link.
Contributors testing responsive behavior on mobile devices have no
supported way to serve a small production-shaped SPA against the running
dev API. Running `vite preview` directly does not work either — the
server's board mutation guard checks that the browser's Origin matches
the request Host, and a preview on a second port would fail every
mutation.

**Proposed solution**

Add two root scripts:

- `pnpm dev:mobile` — build `ui/dist` and serve it via `vite preview` on
port 3101, with `/api` proxied to the API server on 3100.
- `pnpm dev:both` — run `pnpm dev` and `pnpm dev:mobile` together in a
single terminal with prefixed output and shared signal handling.

The vite preview config binds `0.0.0.0`, sets `allowedHosts: true` so it
accepts arbitrary hostnames (LAN, tailnet, ngrok, etc.), and the shared
`/api` proxy forwards the client's original Host header as
`x-forwarded-host`. The paperclip server's mutation guard already
prefers `x-forwarded-host` over `host` when computing trusted origins,
so the browser's Origin becomes trusted automatically.

**Alternatives considered**

- Bespoke node proxy script — works but duplicates what vite preview
already does.
- Loosen the mutation guard to accept arbitrary origins — reduces
security for the primary server for the sake of a dev-only workflow.
- Second server config that binds a second port from the paperclip
server itself — much larger change and mixes runtime concerns with a
dev-tooling convenience.

## What Changed

- New `pnpm dev:mobile` script — build UI then run `vite preview` on
port 3101.
- New `pnpm dev:both` script — run `pnpm dev` and `pnpm dev:mobile`
together via `scripts/dev-both.mjs`, which prefixes each child's output,
propagates SIGINT/SIGTERM, and exits when either child exits.
- `ui/vite.config.ts` — add a `preview` block (port 3101, host
`0.0.0.0`, `allowedHosts: true`, shared `/api` proxy).
- New `ui/src/lib/vite-api-proxy.ts` — extracts the `/api` proxy factory
shared by dev and preview, and forwards the client Host as
`x-forwarded-host` (plus `x-forwarded-proto`).
- New unit test `ui/src/lib/vite-api-proxy.test.ts` covering the
header-injection behavior and the pass-through when no Host is present.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/vite-api-proxy.test.ts` — 3 tests pass.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/ui build` — clean.
- Manual: ran `vite preview` against an echo listener and confirmed the
request arrives with `x-forwarded-host` set to the client Host header
and `x-forwarded-proto: http`. Then ran `pnpm dev:mobile` against the
live dev server and verified board mutations (mark issue read, resolve
recovery action, run routine) succeed from a second-port browser session
that previously 403'd.

## Risks

Low risk. Changes are limited to dev tooling — no runtime code paths, no
server changes, no schema/migrations. The `apiProxy` refactor is a no-op
behaviorally for the existing dev server (same target, same `ws: true`);
the only new behavior is the two `x-forwarded-*` headers, and the server
side already prefers those headers when trusting origins. `dev:mobile`
and `dev:both` are additive; existing `pnpm dev` is untouched.

## Model Used

Claude Opus 4.7 (1M context), extended thinking, tool use (bash, file
edits).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-02 20:26:03 -07:00