ContributingDraft
Glass plan (28 Sep)
1. What's landed -- none of it
git cherry origin/main HEAD marks all 21 commits on work/glass-live as + (not on main),
confirmed by content, not just SHA: origin/main's core/computers/driver/policy.js and
docker.js have no COMPUTERD_TOKEN_FILE, no .boot seeding, no URLBlocklist /
DownloadDirectory / http(s)-only fencing. Both HIGH fixes from the last e2e review
(4b88f1d9) are absent from 0.1.0-rc.1 -- secrets still sit in the container's Env, and an
agent's CDP session can still dump cookies, navigate file:///chrome://, and download
unbounded. Reported to the lead already; this is an RC blocker independent of anything below.
Also not on main: the vault fill contract (computers.fill.begin/end, shielding during a fill),
idle hand-back (computers.handback.*, the 10 s warning), computers.restart/limits, the
tailnet egress gate hardening, and the native-Capsule-era copy/event fixes (a8b9f0e, f2b8a1b,
ab983cb5). core/glass and deck/glass DO exist independently on main (a smaller, older line
of the same module -- watch.js 393 lines there vs 407 here) -- main was never empty, but it's
stale relative to this branch by about three weeks of work, and this branch is stale relative to
main's newer neighbors (sight, Design A, the settings hub).
2. What's outdated on this branch
- sight (cohesion, landed on main):
sight.framegives a still JPEG of an agent's screen for the phone, one screen service shared by both Glass's live view and sight's stills. This branch still treats Glass's watch/relay path as the only screen source; it should sit behind the same service sight now defines so a still and a live stream are two views of one thing, not two pipelines. - Design A (picked 27 Sep,
docs/design/one-app/DIRECTION.md): home is "Needs you", Glass is a place you go to, violet is the sole attention colour, one tree/three shapes. This branch's copy and layout predate that call --GlassWatch.dc.html/GlassTakeover.dc.htmlboards need a pass against Design A's placement rules (top bar, card chrome, badge colour) and the Chat-place shell. - the settings hub (
refactor(settings)on main): modules now declare their own settings, the kernel checks and stores them.computers.handback.*in this branch predates that; it should become a glass/computers-declared setting, not its own ad hoc tool pair, to land inside the hub instead of beside it. - per-session socket / registry: main's module loader (
core/modules/index.js) is unchanged in shape, but PERSON_ONLY presence enforcement (computers.takeover,glass.take,glass.release) landed differently upstream than this branch assumed -- reconcile against currentcore/presence/index.jsbefore merging, not after.
3. "True module"
Glass is already close: core/glass talks to computers only through its providers
(providers/computer.js, providers/box.js), declares tools/events/views in module.json, and
deck/glass is a self-contained view tree with vendored noVNC. The frozen boundary edges that
still leak:
- computerd
/fsroutes are computers-image surface, not glass's own -- contributed to the computers image rather than owned by glass. Fine as a documented contract, but it means glass can't ship or version independently of the computers image. Freeze the route shapes inteam/archive/work-journals/computers.mdexplicitly as glass's contract, the way vault's fill contract is now written down. computers.shield/computers.helperare computers tools glass calls; the vault fill contract adds a third caller (vault) to the same shield. That's the registry working as intended -- but glass'sguard.js(denied-path list) needs to stay equal tolink's copy perteam/archive/work-journals/glass.md's "Needs from others", which is a manual sync today, not enforced by a test. Add a same-file/checksum test so drift fails CI instead of getting found in review.- presence (
PERSON_ONLY) is enforced in the module loader, not in glass itself -- correct per the registry model, butcomputers.takeover/glass.takedeclaring their ownpresenceat all (noted as removed in this branch's "Changed contracts") suggests glass used to duplicate a check the registry now owns. Confirm nothing indeck/glassstill gates on a local presence check instead of trusting the tool call to fail closed. - event naming: glass emits both
computer.*andglass.*for the same take-over/hand-back (seewatch.js'sagentOf/switch handling both). That's a real seam, not tech debt -- computers owns the resource, glass owns the UI meaning -- but two event families for one user action is the kind of thing that should be one internal event translated at the module boundary, not duplicated at every listener.
4. "Feels like my own screen" -- concrete plan
Already built, verify and extend rather than build from scratch:
- Fullscreen (
watch.js:89,requestFullscreen/webkitRequestFullscreenon the stage element) and pinch zoom (pinchZoom(host, zoomView, ...),Fitreset button) exist today on Deck. Not yet: a native Mac window through the Capsule (Capsule is native-Swift now, no window chrome for Glass yet -- this is real, new work), and fullscreen has not been verified on the phone (deck/glass/phone.js) or measured for correctness on resize. - Quality/compression already adapts by device and link (
watch.js:34, laptop 6/2, phone 5/4, slow link 2/6) -- a latency-aware starting point exists; it is not closed-loop (doesn't react to measured fps/latency mid-session, only to link type at open). scaleViewport: true,resizeSession: false(watch.js:259-260) -- fit-to-window is the current default; 1:1 pixel and retina scale are not implemented as an explicit toggle.
New work, in priority order (reset 28 Sep per cohesion's binding interaction pass,
docs/design/interaction.md 5debc1bc -- items 1 and 3 below are now top-2, not mid-list):
- Close the latency badge's own loop (S→M). Shipped 47d90b0c as an open-time snapshot
(
link.latencyMsfromglass.open, read once at connect); every other "what's happening now" surface in Vyre updates live, so this is the one that goes stale mid-session. Needs continuous sampling and one event, not a bigger UI change -- the render side is already there. - Native full screen (M): CORRECTED 28 Sep by the new browser harness (section 8) -- the
Fullscreen button (
fullBtninwatch.js) is built but only ever placed in the phone layout's markup; on Deck it exists as a JS object with nowhere to render, so Deck has no fullscreen control at all today. The earlier draft of this plan said it was "wired on Deck" -- that was wrong, caught bydeck/test/glass-browser.jsactually loading the desktop route and finding no such element. Work: add the same control to the desktop layout (gl-stagewrapheader), verify iOS Safari's fullscreen restrictions on the phone one that already exists (iOS Safari has no real element fullscreen in some contexts -- may need a CSS "cover the viewport" fallback instead of the API). A Mac native window through the Capsule is L: needs a real window (not a panel/extension) hosting the same RFB canvas, which is new Capsule-native surface, not a Glass-side change alone -- coordinate with capsule-pro. This is the user's literal ask ("feels like my own screen"); reprioritized to the top per cohesion/the lead. sight.framefor the reconnect still and the resting-tile preview (S): already agreed with cohesion (2026-09-28) -- callsight.frameright when the socket drops, show that as the frozen frame, swap to the live stream oncesight.watch's ticket reconnects; same call, smallmaxWidth, for the resting tile, refetched on the nextsight.steppedrather than a timer. Replaces the "reconnect without a black flash" item below 1:1 -- this is now that item's implementation, not a separate one.- Fit and zoom (S, DONE): a Deck
1:1toggle shipped (f5fe3f1f), verified by the harness. DPR sizing is NOT the S fix this plan assumed -- corrected 28 Sep after actually readingvendor/novnc/core/display.js. noVNC's canvas backing store (canvas.width/height) already equals the remote framebuffer's real pixel size;scaleViewportonly sets the canvas's CSSstyle.width/heightto fit the container, via_rescale. So the canvas is not "DPR-unaware" in a way a client-side multiply fixes: on a 2x Retina screen, a 1024x768 remote desktop shown at ~1024 CSS px wide really is only 1024 physical pixels' worth of data, upscaled by the browser regardless of any canvas-sizing change here. The only way to a sharper Retina picture is more source pixels -- the container's Xvnc running at a higher resolution than today's config default -- which is a real tradeoff (more bytes over the relay for every viewer, not just Retina ones) and a computers/pool decision, not a glass client patch. Downgraded from S to a design question; not doing a cosmetic canvas-size change that would not actually help. - Latency/fps targets + measurement (S to define, M to instrument): propose p95 input-to-
paint under 150ms on Tailscale-direct / 300ms over relay, sustained 24fps minimum during
active use, under 5% dropped frames over a 10s window -- matching the smoothness bar Design A
already set for the rest of the app (
one-app/DIRECTION.md's 60fps/1% table is the sibling spec for page UI; Glass needs its own row in that same table, not a separate standard). Measure via RFB's existing frame timestamps plus a round-trip ping tool -- the same sampling loop item 1 needs, shared rather than built twice. - Input fidelity (M): keyboard shortcuts through noVNC's domkeytable/keysymdef are vendored
and presumably complete; clipboard is called out in
team/archive/work-journals/glass.md's Next as "clipboard to the holder only while shielded" -- still open. Two-way clipboard, IME support (noVNC's input layer is keysym-based, which is lossy for IME composition -- needs explicit testing with CJK input), and scroll fidelity (trackpad vs wheel) are all unverified. - Cursor (S): confirm noVNC's cursor decoder is on (local cursor rendering vs remote-only) -- cheap to check, meaningfully changes perceived latency since a local cursor never waits on the round trip.
- Audio (not applicable today -- Chrome-in-a-container has no audio pipeline in this design; skip unless a future computer type needs it).
- Instant take-over handoff (S, mostly done): shield/unshield and holder events already exist; the "instant" feel is mostly about not re-negotiating the RFB session on take-over, which the code already avoids (take-over is a permission change, not a reconnect). Verify no visible stall today; if there is one, it's likely the passkey/Touch ID round trip, not video.
5. Cheap opportunities (S/M) -- status 28 Sep
rc.2 was narrowed to 14f1824c only (the HIGH-fix merge); everything below is 0.1.1, built on work/glass-live behind it.
- Live latency badge -- done (47d90b0c):
latencyLabel(link)indeck/glass/watch.js, a pure function with a unit test (watch.test.js), rendered next to the connection badge. It is an open-time snapshot today, not the closed loop cohesion asked for (item 1 above) -- that part is still open. - 1:1 zoom toggle -- done (f5fe3f1f): a
1:1/Fitbutton on Deck's panel,rfb.scaleViewport/clipViewportflipped,.gl-1to1inglass.css(a fixed-height scrollable box instead of the aspect-ratio one). Verified: harness click-and-check plus a real screenshot. Was blocked ondeck/glasshaving no test harness formountScreen's RFB/DOM wiring; built one first (section 8,deck/test/glass-browser.js, 9/9 on testbox including this toggle). - DPR-aware canvas sizing -- downgraded from an S fix to a design question (section 4, item 4): not a client-side patch, needs a decision on Xvnc's container resolution.
- sight.frame reconnect still -- next.
6. Tests that improve UX (measurable)
Built, in deck/test/glass-browser.js (section 8): mount/connect, fit ratio, take-over class,
reconnect-keeps-last-frame, phone fullscreen wiring, and resize/layout at Deck (1440) and phone
(390) widths, plus a screenshot of each state. Still open:
- Latency budget test: scripted RFB connect + input round-trip, assert p95 under the target in section 4.3, over both Tailscale-direct and relay paths (two runs, two budgets). Needs the closed-loop sampling from item 1 to have a number to assert on.
- Frame-drop budget test: capture N seconds of frame timestamps during a scripted mouse-drag, assert dropped-frame percentage under budget.
- Deck fullscreen toggle test: once item 2 ships a Deck control, add the same
stub-and-click check
glass-browser.jsalready does for the phone one. - DPR/1:1 correctness: once those land, assert canvas backing-store size against devicePixelRatio and the toggle's state.
7. Coordinate with cohesion -- resolved 28 Sep
- sight.frame reuse: agreed. Glass calls
sight.frame {target: "agent:<name>", maxWidth}directly for both the reconnect still (call it the moment the socket drops, show the still, swap to the live stream oncesight.watch's ticket reconnects -- no separate JPEG path or polling loop of Glass's own) and the resting-tile preview (smallmaxWidth, refetched on the nextsight.steppedfor that target, not a timer). No changes needed on cohesion's side. Note the target format isagent:<name>(confirmed viacore/sight/sight.test.js), not glass's owncomputer:<name>-- the conversion happens at the call site. - Inline chat pictures: item 18 in
docs/design/cohesion.md(cohesion, sha baf5ec7c), now bound by the lead's interaction pass. Split as proposed: chat owns rendering (built once in chat-core), sessions passes image blocks through from the Agent SDK's own shape, sight owns the one capture path (sight.frame/sight.stepped) -- glass is a second caller of that same path (the reconnect tile, and now chat's per-step thumbnail), not a second capture contract. The other inline-image source (files the agent made -- Canva renders, saved screenshots, not a live screen) is explicitly unowned, open for 0.1.1, the lead's call.
8. Browser harness -- built 28 Sep
deck/glass had no test that mounts mountScreen -- only pure helpers extracted from
watch.js/input.js were unit-tested. Built one, real Chrome on testbox, real vyred, real Glass
WS relay, no Docker:
test/fixtures/fake-xvnc.js-- a minimal RFB server (extracted fromcore/computers/glass.test.js, which now imports it too rather than keeping its own copy), enough handshake for a real client to reachconnected, plussendFrame()(one raw-encoded rect, so a real canvas paints something) andcrash()(kills the TCP connection, standing in for a dead container).deck/test/glass-world.js-- a throwawayVYRE_HOMEwithrole: "box"(computers/glass are box-role modules; the default on a Mac islocal, which silently runs neither -- first thing the harness caught) andcomputers: { driver: "fake", local: {...} }pointed at the fake Xvnc, an agent with a realcomputers.checkout, and the same HTTP+WS proxydeck/test/world.jsuses so the real Deck page and the real Glass relay are exercised, not a mock. Prints{ready, url, agent}on stdout, same contract asdeck/test/native-bar/world.js.deck/test/glass-browser.js-- drives it withdeck/test/cdp.js(no Playwright dependency, matching the rest of the repo's browser checks), one JSON line per check plus a screenshot. Run:node deck/test/glass-browser.js --port 4757(testbox only, one Chrome at a time).
Run 28 Sep on testbox: 8/8 checks pass -- mount/connect, fit ratio, take-over (gl-held, no
passkey prompt, matching the PERSON_ONLY change), reconnect (gl-snap stays visible after the
backend crashes, no black canvas), phone fullscreen (real click, stubbed requestFullscreen,
called once on the stage element), and both resize/layout checks (phone lays out as gl-phone,
Deck as gl-stagewrap). Screenshots confirmed real UI (take-over state, the "kit is paused while
you drive" copy), not a blank page.
What the harness caught immediately: the fullscreen button (fullBtn in watch.js) is built
but only placed in the phone layout's markup -- Deck has no fullscreen control at all today. An
earlier draft of this plan (section 4, item 2) said it was "wired on Deck"; that was wrong, and
the harness is what caught it by actually loading the desktop route and finding no such element.
Corrected above. This is the harness earning its cost on its first run.
Two things it does not cover yet, needed before 1:1/DPR land: canvas pixel-content assertions
(today's checks are structural -- classes, sizes, element presence -- not "does this pixel match");
and a way to fetch devicePixelRatio-scaled screenshots (CDP's Page.captureScreenshot needs an
explicit Emulation.setDeviceMetricsOverride deviceScaleFactor, which cdp.js's openTab
already takes as scale, so this is wiring, not new capability).
0.1.1 build list
| Item | Size | Owner | Status (28 Sep) |
|---|---|---|---|
| Merge the two HIGH e2e fixes + fill contract onto main | -- | glass (this session) | done, 14f1824c, sent to e2e for rc.2 |
| Live latency badge | S | glass | done, 47d90b0c |
deck/glass browser harness (real Chrome, real vyred, fake Xvnc) | M | glass | done, deck/test/glass-browser.js, 8/8 on testbox 28 Sep |
| Close the latency badge's loop (continuous sampling, one event) | S/M | glass | top-2, cohesion's interaction pass |
| Native full screen: Deck control (missing entirely -- harness found it), phone iOS fallback, Mac window in the Capsule | L | glass + capsule-pro | top-2, the user's literal ask; scope corrected 28 Sep |
| Reconnect still + resting-tile preview via sight.frame | S | glass + cohesion | agreed, harness ready to verify it |
| 1:1 zoom toggle | S | glass | done, f5fe3f1f, 9/9 on testbox |
| Decide Xvnc's container resolution vs. viewer DPI (was "DPR-aware canvas sizing") | M, decision first | glass + computers | rescoped 28 Sep -- not a client patch, real bytes-over-the-wire tradeoff |
| Two-way clipboard while shielded | M | glass + computers | |
| IME-aware input testing/fix | M | glass | |
| fps/latency instrumentation + CI budgets | M | glass + e2e | shares the sampling loop above |
| guard.js/link denied-path sync test | S | glass | |
| Reconcile event families (computer.* vs glass.*) | M | glass | |
| Move handback settings into the settings hub | S | glass | |
| Re-skin Glass boards against Design A | M | glass + app-design |