Repository navigation
Spaces: the web app, and CORS on Harbor for it - #1178
Merged
Merged
Conversation
ramnique
force-pushed
the
ramnique/spaces-web-shell
branch
from
October 9, 2026 09:55
d555be4 to
d6ae8a3
Compare
The Spaces web app is served from its own origin and calls every org's host and the apex cross-origin. HARBOR_WEB_ORIGINS (comma-separated) names the origins allowed. cors.ts mounts Hono's CORS middleware ahead of auth on the render face and the apex, so a preflight, which carries no Authorization, is answered instead of refused with a 401. Error responses keep the headers, so a page can read a 401 and refresh. Bearer only: no credentials and no cookies. WWW-Authenticate is exposed. The live face needs nothing, since WebSockets have no preflight and already take ?token=. Unset means no CORS, as before. The dev binary defaults to the web app's dev server, http://localhost:5174. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A second Vite entry (web.html → src/web/main.tsx) builds the desktop's Spaces components as a standalone web app. The components are unchanged; only the host under them differs: - host.ts installs the same window.ipc contract in the tab, validated as the preload validates it. A channel the browser doesn't serve is refused by name. - handlers.ts serves the shared Harbor channel table from @x/spaces-client, plus the browser's own channels: the org list from the apex, sign-in, org creation, and uploads from bytes. - account.ts is the phone's sign-in as a full-page redirect: discovery from the apex, one dynamic client registration per browser, PKCE. Tokens live in localStorage, and refreshes run under a Web Lock so two tabs never spend the same rotating refresh token. The OAuth helpers move into @x/spaces-client (oauth.ts). - orgs.ts keeps one REST client and live socket per org. A dev Harbor (VITE_SPACES_DEV_HARBOR) runs it locally with dev tokens. - routes.ts and app.tsx make the address bar the navigation: /<org>/s/<space>, …/t/<thread>, …/a/<file>, /<org>/activity. What needs the person's own machine is hidden where LOCAL_RUNTIME (feature-flags.ts) is false, which only the web host sets: @rowboat and /ask, send-later, /remind and the Scheduled list, and the @rowboat text in placeholders. Voice already hides itself when its config call fails. vercel.json builds it, rewrites every path to web.html, and sets a CSP with no inline or foreign script. The theme bootstrap moves to a file for that reason. WEB.md says how to run and ship it, and SPEC §4 says where the web app keeps tokens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ramnique
force-pushed
the
ramnique/spaces-web-app
branch
from
October 9, 2026 09:57
6c816e7 to
511bd96
Compare
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.
Step 2b of the Spaces web-app plan: Spaces runs in a browser, using the desktop's own components.
Stacked on #1176, the shared channel table. Merge #1177 first. That's the Markdown sanitizer, and in a browser, message HTML is the main route to stealing someone's session.
Harbor (commit 1)
HARBOR_WEB_ORIGINS(comma-separated) lists the browser origins allowed cross-origin.cors.tsmounts Hono's CORS middleware ahead of auth on every org face and the apex:WWW-Authenticateis exposed.?token=.http://localhost:5174.HARBOR_WEB_ORIGINSin the Dockerfile header and the guide.test/cors.test.tscovers the preflight without a bearer, headers on 200 and 401, unlisted origins, the unset case, and the apex.The web app (commit 2)
A second Vite entry,
web.html→src/web/main.tsx. The desktop'sSpacesViewand everything under it are unchanged.host.tsinstalls thewindow.ipccontract in the tab and validates requests as the preload does. Channels the browser doesn't serve are refused by name.handlers.tsserves the shared table from Spaces: one table of the channels that are a single Harbor call #1176 plus the browser's own channels: the org list from the apex, sign-in, org creation, uploads from bytes.Sign-in (
account.ts) is the phone's journey as a full-page redirect:localStorage, with refreshes under a Web Lock so two tabs never spend the same rotating refresh token@x/spaces-client(oauth.ts)Routes (
routes.ts,app.tsx):/<org>/s/<space>,…/t/<thread>,…/a/<file>,…/w/<board>,/<org>/activity,/<org>/browse. Each org's path segment is its slug.Hidden in the browser, because they need the person's own machine (
LOCAL_RUNTIME, set only by the web host):/ask/remindand the Scheduled listVoice already hides itself when its config call fails. The desktop is unchanged: the preload never sets the flag, so it's on there.
vercel.json:build:weband rewrites every path toweb.htmlweb-public/theme.jsfor that reasonDocs:
apps/x/apps/renderer/WEB.mdcovers running and shipping it. SPEC §4 says where the web app keeps tokens. The root guide links WEB.md.Verified locally
npm run typecheck(apps/x) passes.routes.test.ts).cors.test.ts).pnpm devHarbor:Not verified: real sign-in. That needs a deployment whose
HARBOR_WEB_ORIGINSlists the page.Not in this PR (next)
app://URLs (next: a service worker). File-picker and drag-drop uploads send a path the browser doesn't have; pasted images upload fine.Deploying it (needs a person)
HARBOR_WEB_ORIGINSon staging Harbor (Render) to the staging web origin.apps/x/apps/renderer, and setVITE_SPACES_APEX_URL=https://spaces.staging.x.rowboatlabs.comfor staging.🤖 Generated with Claude Code