Skip to content

Add GitHub auth and public session access - #1

Merged
rt2zz merged 20 commits into
mainfrom
codex/github-auth-public-sessions
Aug 20, 2026
Merged

rt2zz merged 20 commits into
mainfrom
codex/github-auth-public-sessions

Conversation

@rt2zz

@rt2zz rt2zz commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • allow one anonymous model-backed turn before requiring GitHub sign-in
  • add guest-cookie ownership, coarse IP throttling, and authenticated write authorization
  • keep conversations public and surface the public-conversation notice in the sign-in gate
  • let the agent page through messages and artifacts from a known public session ID
  • document auth setup and add Vercel SPA routing for direct session URLs

Verification

  • bun run typecheck
  • bun run build
  • npx tsc --noEmit
  • npx convex dev --once
  • live Convex pagination smoke tests against an existing session

Deployment notes

Production still needs its own Convex Auth keys, GitHub OAuth credentials, anonymous IP salt, OpenAI key, and exact frontend SITE_URL.

eleven and others added 20 commits August 17, 2026 17:02
…h tweaks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The band below the input sat empty until a visitor discovered the hot
words. Now it slowly cycles a few things worth asking, reusing the
explainer machinery: hover previews into the placeholder, click sends.
First real intent (hot word, typing, submit) retires the cycle; hovering
the band pauses it; reduced motion gets one static card. aria-live
starts off so the rotation is never narrated, flipping to polite once
changes are visitor-driven.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sub-agents cards now launch the conversation onto a rich diagram
answer instead of a cold model call — the conceit stays pure (every card
is still a prefill that sends) but the first turn is instant and shows
rather than tells. The explainer is not a special case: topics.open
persists it as a real frame in the durable log (assistant message
broadcast so the model can see it — app-authored frames have no
generator, and assistant audience defaults to self), so the agent
treats the diagram as something it said and can answer follow-ups
about it. Messages grow a widget field; the client renders the
subagents widget (spawn fan, spectrum lanes, one shared log rail,
follow-up chips that send) in place of the prose equivalent.

Also fixes thread ordering: the user row is now written before the run
under the frame-persistence idempotency key, so it can no longer sort
after the assistant's streamed reply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The thread now reads as dynamic pages with an input at the bottom, not
a chat feed. On a speaker switch the new turn scrolls to the top of the
screen and prior turns become history above the fold; consecutive
messages from one speaker accumulate below without re-paging, and
streaming growth or optimistic→server swaps never re-trigger the sync
(turn count only moves on a switch). The thinking placeholder is not a
turn — the page flips when the agent starts saying something. Loading
an existing session cuts straight to the latest turn; reduced motion
always cuts. A viewport-tall runway under the thread lets the newest
turn reach the top even with nothing after it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The input is now absolutely positioned against the app shell — centered
on the full viewport and layered over chat and inspector alike — so it
reads as the one fixed object the pages move under. The durable-log
hint under it is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brand left, the Install/Star CTA steps dead centre (grid, so centre is
the viewport centre), Why/Docs/theme right — all reusing the landing's
shared step/nav-links/theme styles. Copy and theme behaviors are
re-implemented in React since the landing's inline bindings never see
the app's DOM; the toggle also repaints the hidden marketing button so
nothing is stale on exit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shell grows a left "app" pane (empty scaffolding where the agent
will draw dynamic UI; defaults closed) and the inspector becomes
toggleable (cmd+j, nav toggles, and t3-style panel-glyph minimize
buttons in each pane's header). Pane visibility is not client state:
it lives on a new ui node in the charter as a panes state, manipulated
through one setPanes action exposed with caller "any" — the model's
tool and the client's command are literally the same action writing the
same durable state, and the frame log shows both doing it.

sessions.sendCommand runs client commands through executeCommand on the
hydrated machine in a mutation (scheduleWork: false — a UI command
never wakes the model) and appends the produced frames to the log,
which folds the state.update into the instance snapshot. The client
mirrors the machine state locally for instant toggles and reconciles to
the snapshot, so the agent moving a pane moves the real UI.

Verified on dev: command flips durable state and logs
request/state.update/result frames; "open the inspector" makes the
agent call setPanes and the state follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One button per pane, absolutely positioned at the top left/right of the
app body: over an open pane's header it reads as minimize, over the
chat it reads as open — same button, same spot, never in the nav bar.
The in-header minimize buttons and nav toggles are gone; pane headers
gain an inset so their content clears the floating button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pane widths join open/closed in the ui node's panes state as flat rem
keys (14-44, schema-clamped), so a drag, a button, and the agent's
setPanes tool all move the same durable knobs. Dragging the slim rail
on a pane's inner edge tracks locally per pointer frame; the machine
hears one setPanes command with the final width on release, and the
snapshot reconcile is suppressed mid-drag so it can't yank the pane.
cmd+b toggles the app pane alongside cmd+j for the inspector.

Verified on dev: width patch lands in durable state; an out-of-range
width is rejected by the schema with a failed result frame and no
state change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sessions serialized before the ui child existed hydrated without it —
no panes state and no setPanes on the compiled tool surface, which is
why toggles wrote nothing and the agent claimed it couldn't control
the interface. hydrateSourceInstance now grafts the ui child when
missing, so every session heals on load and the next serialization
makes it durable. The inspector also defaults closed for new sessions;
open/width persist thereafter through the instance snapshot as before.

Verified on a pre-ui dev session: it heals, accepts setPanes commands,
and "close the right pane" now makes the agent do it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cmd+j, cmd+b, the pane buttons, and drag-release now run the setPanes
command through the framework's own client path, as the demo app does:
an optimistic effigy (createOptimisticEffigy over createMachineEffigy)
whose transport is sessions.sendCommand, with the pane UI rendered from
the effigy's overlaid instances. A toggle applies an optimistic patch
at the panes state address and the overlay retires when the command's
residue returns in the snapshot — the command is the state change; the
UI is a projection of it. Replaces the hand-rolled message + local
mirror (whose crypto.randomUUID callId silently threw in insecure
contexts, which is the likely reason browser toggles never reached the
machine while agent-driven changes persisted).

Verified on dev: sendCommand flips durable state and the snapshot
returns the callId in recentCommandResidue, which is what retires the
optimistic overlay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… list, past-list polish

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution

The app surface's TSX no longer lives in machine state. Every successful
writeAppSurface is folded into an append-only, versioned artifacts table at
the frame-persist boundary; sessions.get joins the latest row (with a legacy
in-snapshot fallback), the model retrieves source via the new getSurfaceSource
tool, and a one-shot backfill recovered existing sessions' surfaces. State
schema evolution can now never lose a surface, and instance snapshots stop
carrying a 32k source per write.

Framework: resolveStates heals schema-conflicted object state by merging init
under the existing value before falling back to a full init reset — the
silent-reset root cause behind sessions appearing to lose pane/app state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er frame

The machine emits one message per frame, so a write's request, version
state.update, and result land in separate frames — per-frame pairing never
matched and real runs produced no artifact rows. Collection now runs once per
appended sequence, and the backfill replays each session's frame log through
the same collector (recovering full version history from request inputs)
before falling back to the legacy snapshot scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… flag

appPanePing's mutation stamps workStartedAt on the session doc in the same
transaction that schedules the agent wake; sessions.get exposes it, and the
client blinks from it until streaming starts — no waiting on the model's
first token. Cleared transactionally when the run's frames persist, with a
finally-clear for crashed runs and a 90s client-side staleness guard.
Deliberately a session-doc field, not machine state: presence bookkeeping
that shouldn't add on/off writes to the frame log per poke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the JSON dump (which only showed the root instance's states — spawned
children were invisible) with the instance tree: named branches per instance,
per-state rows with collapsible values and address tooltips, purpose-built
renderings for panes/appSurface/audience, a change flash on every value update,
and a raw-JSON toggle as the escape hatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rt2zz
rt2zz marked this pull request as ready for review August 20, 2026 02:02
@rt2zz
rt2zz merged commit 122f21a into main Aug 20, 2026
0 of 2 checks passed
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.

1 participant