zeekayandHanzo Dev 8c0ded7bfe Sign in with Hanzo IAM, and ship as a pure static export
UNIFIED IDENTITY. A pasted token was the honest shape for a backendless app on day
one and the wrong one to keep: it is a credential handled by hand, and it is not
the identity the rest of Hanzo uses. Sign-in is OIDC authorization code + PKCE
against hanzo.id now — the same login as everything else — in about sixty lines
against their own discovery document, with no dependency. A static site cannot
keep a secret, so PKCE is not a convenience here; it IS what replaces the secret,
and `hanzo-tabs` is registered as a genuine public client.

The token still lives in this browser, because there is nowhere else it could:
inventing a session store would mean inventing the server this product exists
without. The difference is that it is now short-lived, refreshable, and issued to
this origin rather than copied from a terminal.

A PURE STATIC EXPORT. `output: 'export'` — no server at request time, not even a
Node one. Tabs reads the control plane from the browser with the caller's own
token and frames terminals the machines themselves serve over the zero-trust
fabric; both ends of that conversation are already authenticated, so a server
here would be a third party to it. The Dockerfile goes with the same reasoning:
what ships is a directory of files.

Deployed at tabs.hanzo.ai from S3 via hanzoai/static. Two things had to be fixed
to get there and both were ours: the S3 adapter refused directory requests
outright (fixed in hanzoai/static v0.5.4 — every static site was 404ing at its own
root), and the default CSP is `default-src 'none'`, which blocks every script, so
the page rendered and nothing ran.

Verified in a real browser against the live IdP: the sign-in button lands on
hanzo.id/login/oauth/authorize carrying the PKCE challenge, with no console
errors.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-03 23:56:20 -07:00

Hanzo Tabs

Keep tabs on your agents.

Your coding agents work in shells on real machines. Tabs puts every one of those shells in front of you — split, tiled, and reachable from anywhere you can open a browser.

hanzo link          # on any machine with a shell

Then open tabs.hanzo.ai.

Why it is shaped this way

There is no backend. Not "a small one" — none. hanzo link publishes a terminal from the machine over a zero-trust tunnel, already authenticated and already gated, and the browser frames it directly. A server here would be a third party to a conversation that has two, plus one more thing to keep running and one more place a credential could sit.

A shell is a URL. ttyd runs with --url-arg behind a wrapper that names a tmux session, so ?arg=build is a shell called build and tmux new -A attaches or creates. One link serves as many independent shells as you ask for. There is no spawn endpoint because none is needed — and closing a pane loses nothing, since reopening the name reopens the session with its scrollback.

The layout is a tree, and that is the whole design. A list of rectangles is the shape that looks easier and then cannot answer the only question that matters: close a pane, and who gets the space? With a tree it is structural — the space belongs to the sibling, so closing replaces the parent split with that sibling and nothing else moves. lib/tiles.ts is that tree and nothing else: split, close, resize, sweep order, page projection — every one a pure rewrite, tested without a browser.

Rectangles are derived, and that keeps a shell alive. Nesting panes in the DOM the way they nest in the tree remounts every <iframe> on any split, and each terminal reconnects. So geometry() projects the tree onto percentages, the panes render as one flat list that never reorders, and only their coordinates change.

A phone pages; it does not tile. 390px cannot hold two terminals and stay legible. Side-by-side splits become swipeable pages, stacked splits stay stacked, and how many share a page is derived from the room — landscape gets one rather than three slivers.

Layout

lib/tiles.ts        the layout tree + geometry + the phone projection (pure)
lib/panes.ts        what a pane shows: a machine and a shell name (pure)
lib/api.ts          the control-plane read, from the browser
components/         the workspace
app/                the marketing page and the workspace page
tests/              39 tests, no browser required

The terminal client itself lives in hanzoai/cli (assets/term/) — xterm.js, themed, with a touch key row — because it is served by the machine, not by this site.

Develop

npm install
npm run dev          # http://localhost:3000
npm test             # the layout engine and bindings
npm run typecheck

MIT.

S
Description
No description provided
Readme
503 KiB
Languages
TypeScript 96.8%
JavaScript 2.6%
CSS 0.6%