Skip to content

Add authenticated shared web UI - #319

Draft
Gratenes wants to merge 25 commits into
zeronsh:mainfrom
Gratenes:wip/browser-remote-parity
Draft

Gratenes wants to merge 25 commits into
zeronsh:mainfrom
Gratenes:wip/browser-remote-parity

Conversation

@Gratenes

@Gratenes Gratenes commented Sep 11, 2026 •

Copy link
Copy Markdown

Summary

Adds an authenticated browser build of the shared GPUI interface. The browser renders the shared Shell/application state and connects to a native Zeron device through the existing RPC protocol and DeviceRoom relay. Execution stays on the native host; the browser does not run an engine or use loopback IPC.

Included

  • WASM entry point for the shared UI, with browser support for chat, terminal, files/Git, queues, attachments, HTTP previews and theme import.
  • WorkOS-backed browser sessions, owner-scoped device discovery, logout and revocation.
  • Epoch-guarded connection lifecycle and no automatic mutation replay.
  • Upload backpressure that permits relay-safe frames above the browser buffer high-water mark while continuing to deliver incoming RPC messages. Attachments are chunked: the attachment limit is 24 MiB; the encoded relay-frame ceiling is 1 MiB.
  • Exact published GPUI runtime dependency, locked build instructions, local serving headers and focused web CI.

Merge-readiness sweep

  • Reconciled upstream 7655c662c2e0e1dea91a9c4d9929725e219abd82, resolved the root lock conflict and reviewed the preview/sidebar auto-merges.
  • Updated the independent web and lifecycle lockfiles for upstream version 0.2.61.
  • Replaced sibling runtime paths with published Gratenes/zui@c1700b8de27a07b5bc1a190440b64e7323c008f9, including the input/runtime fixes from Fix browser runtime wakes, fonts, touch and text input zui#8.

Validation

  • Clean-checkout trunk build --release --locked: passed, using nightly-2026-09-08, Trunk 0.21.14 and the published runtime pin; no local Cargo source override.
  • 14 browser lifecycle/transport tests passed, including consecutive upload chunks, buffer draining, inbound progress, cancellation and no replay.
  • Edge typecheck and 67 tests passed after npm ci in the clean checkout (44 unit + 23 workerd).
  • Native cargo check --locked -p zeron: passed after reconciliation, using locally supplied Linux development libraries.
  • 18 preview tests passed; one existing environment-dependent coordinator test remains ignored.
  • The focused native attachment chunk-boundary test passed.

Commands/configuration: apps/web/README.md.
Hosted checks: Web validation workflow.

Manual coverage and limits

The author has verified phone taps, scrolling, keyboard behavior and popovers; phone copy/paste and emoji testing remains pending. Device/browser versions and the tested revision were not recorded.

Earlier development checks reported real WorkOS login, device selection, chat, terminal commands, file editing, Git diffs, queue operations, previews and theme persistence. Those reports are not a reproducible end-to-end suite for this final commit; this sweep does not claim to repeat those authenticated flows.

Remote project preview is HTTP-only: WebSocket upgrades and Vite HMR are not supported. Full desktop/browser parity, one-command deployment and direct/self-hosted transport are outside this change; SELF_HOSTING_ROADMAP.md remains a proposal.

Before merge

  • Record the author's phone copy/paste and emoji result; keep draft until then.
  • Obtain passing hosted checks and maintainer review.
  • Merge zui Use native word-editing shortcuts on every platform #8 first; repin the web manifest/lockfile to its published upstream revision and rerun the locked build before merging this PR.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Proof-of-work snapshot: WorkOS browser sessions, DeviceRoom RPC, shared UI parity and friendly device names. Pins published GPUI runtime fixes. Excludes local credentials, generated artifacts and experimental apps. Locked WASM build and scoped tests passed; exact native build remains unverified after validation timeouts. Not PR-ready.
@Gratenes Gratenes changed the title Add web UI support Add authenticated shared web UI Sep 12, 2026
@Gratenes

Copy link
Copy Markdown
Author

Pushed 08fcd03: production deployment preparation and multiuser authentication. Keeping this PR in draft. Known unresolved issue from manual phone testing: emojis do not render correctly in chat messages or when pasted into the input. This is a rendering failure, not a passed emoji check; root cause has not yet been investigated. Previously verified taps, scrolling, keyboard behavior and popovers remain recorded separately.

@Gratenes
Gratenes marked this pull request as ready for review September 13, 2026 07:12
@Gratenes

Copy link
Copy Markdown
Author

Updated to 5c0a81b and marked ready for review.

The emoji issue mentioned in my earlier comment is fixed in staging. I’ve also manually verified terminal input/focus, mobile drawers, reconnect behavior and the mobile tap-highlight fix.

This update resolves the upstream conflicts and pins the published runtime fixes, so the web build no longer depends on local runtime overrides.

Local validation passed: locked release WASM build, 21 lifecycle tests, 44 unit tests, 24 workerd tests and 39 production-routing checks. The workerd runner assertions were investigated and addressed; repeated runs are now clean. Desktop/mobile browser startup checks also passed.

Please land zui #8 first; I’ll then repin to its upstream revision. Hosted CI still needs confirmation. Production has not been deployed, and the WorkOS/storage rollout checklist remains a pre-merge requirement.

@wingleeio wingleeio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the thorough work here — the design is sound and the security posture (PKCE + state/nonce, __Host- cookies, CSRF header + Origin checks, sealed provider tokens, owner-scoped sessions, browser forced to role=client, catalog-only preview relay) is careful. I audited the vendored crate, the forked git dependency, CI/deploy changes, the postinstall hook, and the edge auth code, and found nothing unsafe. Findings below, ordered by importance.

Blocking / should fix before merge

  1. Pin mismatch between description, README, and manifest. The PR body says zui is pinned at c1700b8d…, apps/web/README.md and docs/web-deployment-session-summary.md say aca6b042…, and apps/web/Cargo.toml / Cargo.lock actually pin 8d78b9c2…. Per your own checklist, zui #8 should land first and this should be repinned to a zeronsh/zui rev, with the [patch."https://github.com/zeronsh/zui"] block removed. As written, deploy.yml builds production web.zeron.sh assets from a personal fork.

  2. Per-frame authorization round-trip to a single global DO (edge/src/device-room.ts webSocketMessage → browserSessionActive → validateBrowserSession). Every relayed frame in both directions for a browser client does a sub-request to the one browser-sessions-v1 BrowserSessionStore instance, with refresh: true by default (so a frame path can trigger a WorkOS refresh). Terminal output is many frames/sec per client; this makes one DO a global throughput ceiling and adds latency to every frame. There's also a correctness concern: webSocketMessage is now async and awaits a non-storage sub-request, so workerd can deliver the next message before the previous one finishes validating — frames on a stream could be relayed out of order. The alarm-based recheck (BROWSER_SESSION_RECHECK_MS) plus the /browser-revoke push already cover revocation; I'd drop the per-frame check or make it a local, time-bounded cache on the socket attachment.

  3. Contributor-specific infra committed to the repo. edge/wrangler.staging.jsonc hardcodes a personal Cloudflare account_id, the zeron-test.embedez.com custom domain, and a separate WorkOS client id; docs/web-deployment-session-summary.md is a session log of that staging deploy. Either template the staging config or drop it and the session summary.

  4. /auth/browser/* broker API is dead code for this PR. edge/src/browser-auth.ts exposes exchange/refresh/revoke returning raw provider access+refresh tokens to any holder of BROWSER_BROKER_TOKEN. Nothing in the browser flow uses it, BROWSER_BROKER_TOKEN isn't in Env or wrangler config, and its only consumer is the "not implemented" SELF_HOSTING_ROADMAP.md. It's fail-closed (501) when unset, but it's a new credential-issuing surface plus a === timing-unsafe token compare. Suggest removing it (and the roadmap doc) from this PR and landing it with the self-hosting work.

Should fix

  1. forward() in edge/src/index.ts doesn't strip the new internal headers. It clears ROOM_KIND_HEADER for exactly this reason but passes through x-comet-browser-session-hash, x-comet-browser-room, and x-comet-browser-session-store from native bearer clients. DeviceRoom re-validates the hash and matches owner so I couldn't find an escalation, but the /browser-revoke route's only gate is that store header. Add the three to the delete list for defense in depth.

  2. Host device name is now sent to and stored on the edge (crates/engine/src/lib.rs .with_device_name(local_device_name(...)) → &name= → browser_devices.name). That's the machine hostname by default. Worth a note in the description and probably an opt-out, since it's a new piece of PII in DO storage.

  3. postinstall patching node_modules (edge/scripts/patch-vitest-pool.mjs) will break npm ci on any @cloudflare/vitest-pool-workers bump. Prefer patch-package / npm overrides, or upstream the fix and drop the hook.

Scope

  1. This PR bundles at least six independently reviewable changes: the RpcClient writer/shutdown refactor (affects native too — ServerFrame now rejects envelopes without exactly one of ok/item/err/done; I checked server.rs and it's compatible), large UI module splits (composer/input.rs, */presentation.rs — confirmed these are essentially moves), the alacritty vendoring (verified byte-identical to crates.io 0.26.0 apart from wasm cfg gating), the zeron-rpc/zeron-syntax feature-gating, the web app, and the edge BFF + deploy changes. Splitting the native-affecting refactors out first would make the remaining browser work much easier to review and bisect.

Nits

  • dtolnay/rust-toolchain@master in both workflows; the repo otherwise uses @stable.
  • browser-sessions.ts swallows ALTER TABLE errors with bare catch {} on every DO construction — a migration table would be clearer than try/catch schema evolution.
  • web-validation.yml and deploy.yml duplicate the ~40-line WASM build/check block; a composite action would keep them in sync.

@Gratenes

Copy link
Copy Markdown
Author

Good morning — here is my attempt to un-slop this PR and address the review properly.

  1. The documentation now consistently identifies the current Gratenes/zui checkpoint. The final zeronsh/zui repin and removal of the temporary fork patch are intentionally waiting for zui Use native word-editing shortcuts on every platform #8 to land, so we can use its actual upstream merge SHA.

  2. Removed per-frame BrowserSessionStore validation from DeviceRoom.webSocketMessage. Frame relay is synchronous again, avoiding the global per-frame Durable Object round-trip, refresh work and possible message reordering. Alarm-based rechecks and /browser-revoke push revocation remain in place.

  3. Removed the contributor-specific staging Wrangler configuration, staging deployment document and deployment-session log. The read-only post-deployment check is now named production-smoke.mjs.

  4. Removed the unused /auth/browser/* token broker, its tests, route and BROWSER_BROKER_TOKEN documentation. The speculative self-hosting roadmap that depended on it was also removed.

  5. Added centralized forwarding-header sanitization. Caller-supplied room-kind and browser-session trust headers are stripped before verified Worker-controlled values are applied. Regression coverage was added for spoofed headers and authorized workspace forwarding.

  6. Documented that published device names may derive from the machine hostname and documented ZERON_DEVICE_NAME as the privacy-preserving override.

  7. Migrated from @cloudflare/vitest-pool-workers to the supported @cloudflare/vitest-plugin. Replaced the handwritten node_modules mutation script with a version-pinned patch-package patch.

  8. Reduced the scope by removing the staging, broker and speculative roadmap work. I have not rewritten the existing published history. If splitting the remaining native-affecting work is still required, I can handle that separately.

I also changed the Rust setup actions to use the stable action tag and replaced exception-driven SQL column migrations with explicit schema inspection.

The final upstream zui repin remains required after zui #8 merges. The PR also currently conflicts with upstream main; I’ll resolve and recheck that before merge.

8d78b9c2 was rewritten out of the zui#8 branch by a rebase, so it no
longer sits on any ref and could be garbage-collected, breaking the
locked web build. 93ab2ca is the current zui#8 head (rebased onto
zeronsh/zui main, plus the browser paste and soft-keyboard fixes).

Only the zui revision lines in Cargo.lock change. The final repin to a
zeronsh/zui SHA still waits for zui#8 to merge.
Trunk content-hashes the bundle and the new initializer script, so mark
them immutable in _headers. index.html keeps the default revalidation,
so a deploy is still picked up right away.

Add a first-open loader that shows WASM download progress through
Trunk's data-initializer hook, then "Starting Zeron…" while the module
compiles, and a visible failure state instead of a blank page.

The edge worker only routes an allowlist of paths to assets, so allow
the hashed initializer script and extend production-routing.mjs to
cover it and assert the cache headers.
Attachment thumbnails in the transcript are read from the owning device
over ReadAttachmentChunk. The device lookup, the load spawn and the
loader itself were compiled out on wasm32, so the browser had no
candidate devices and every history image fell through to the blank
"unavailable" thumbnail, while freshly staged images still showed.

The browser already has an AppState with a device-relay EngineHandle and
the edge/engine forward ReadAttachmentChunk to remote clients, so drop the
three wasm32 gates and let the browser use the same path as native.
- Resolve all 24 conflicted paths across proto, rpc, ui, and edge tests
- Gate engine, harness, update, gpui_tokio and native rpc/syntax features
  to non-wasm targets; restore the wasm web dependency set
- Gate zeron-rpc server/device-room modules behind `native` so the
  client-only web graph stays wasm-safe
- Restore platform-specific Instant aliases for browser-reachable UI clocks
- Repair the mobile right-sidebar drawer overlay: <768px transient drawers,
  90%/380px cap, occluding scrim, tap-backdrop close, full-width chat
- Port browser preview-frame handling onto the upstream bounded socket pump
- Add scripts/test-browser-instant.mjs authenticated browser regression
- Regenerate apps/web/Cargo.lock for the boundary changes
@Gratenes
Gratenes requested a review from wingleeio September 20, 2026 20:17
Gratenes added a commit to Gratenes/zeron that referenced this pull request Sep 24, 2026
…very

The web client now uses PR zeronsh#319's browser-session model end to end; the
manual connect flow (engine address, dev user id, pasted AuthKit code)
is gone.

- engine-client: `device-frame.ts` (the DeviceRoom binary framing ported
  from crates/rpc/src/device_frame.rs plus `RelaySocket` — the browser
  transport with ping/pong/echo liveness and buffered-amount
  backpressure) and `edge.ts` (the /api/browser/* session, login,
  devices, and logout API). EngineClient skips the first-frame Auth
  envelope when the credential is empty: the relay authenticates the
  browser session cookie at the WebSocket upgrade.
- app: `state/fleet.ts` is rewritten around the edge — boot checks
  /api/browser/session and redirects signed-out visitors straight to
  WorkOS (PKCE authorize URL from the edge, callback returns to the app
  root with the HttpOnly session cookie); signed in, the owner-scoped
  device list is polled and every device is supervised through the
  cookie-authenticated relay WebSocket. `/pair` is now an automatic
  connect gate, and the Devices settings page describes the
  zeron-login-driven flow instead of manual pairing.
- dev serving: vite allows the dev.embedez.com tunnel host and proxies
  /api/browser (HTTP + WS) to the local staging edge
  (ZERON_DEV_EDGE, default http://127.0.0.1:27641).

Verification: pnpm -r build green (engine-client + app typecheck, Vite
bundle); app suite 1873/1873 (one session-provider expectation updated:
registry restart re-pins identity to the device-id key).
@Gratenes
Gratenes marked this pull request as draft September 24, 2026 04:13
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.

2 participants