Skip to content

feat: a project's record on its own GitHub Pages, and live brief sessions - #1010

Draft
REPPL wants to merge 177 commits into
mainfrom
feat/hosted-sites
Draft

REPPL wants to merge 177 commits into
mainfrom
feat/hosted-sites

Conversation

@REPPL

@REPPL REPPL commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Builds two planned intents and plans a third. Draft: this holds until after v0.15.0 is tagged, and the hosted-sites cut is fifth in the agreed release order, after the ideate-librarian release.

What it delivers

  • itd-2610091010348892, a project's record on its own GitHub Pages (spc-2610141709461788). The new parts:

    • site build and lint site take --base-path.
    • Every page opens with a line saying it is the record of an abcd-managed repository.
    • A github-pages hosting provider.
    • A publish switch, asked at site setup --provider github-pages.

    The release-built workflow uses only the run's own token. It publishes only while the repository is public and the switch is on at both the release tag and the default branch's head. It never publishes over a live Pages site without being asked.

  • itd-2610091016262335, a team editing its brief live (spc-2610141709430146). Three new verbs:

    • abcd session serve, the relay, run on a person's machine behind Tailscale Serve or deployed to the team's own account.
    • abcd session blueprint, which writes a Render blueprint, a digest-pinned and checksum-verified Dockerfile, and relays.json.
    • abcd session page, which writes the static editing page, embedding a vendored, provenance-pinned Yjs and CodeMirror bundle (adr-2610141949110653).

    People join by QR code or short code. Guests' suggestions need a team member's approval. A session goes back to GitHub as issues or as one pull request, with Co-authored-by:, Suggested-by: and Approved-by: lines.

  • itd-2610142145236613, abcd's own site moving to its GitHub Pages: planned and ready, not built (spc-2610142146250176, proposed adr-2610142147242544).

Revised 2026-10-15: a project site renders only what abcd writes

The product thinker ruled that a project's site renders only what abcd already writes to the repository, and that its docs are read on GitHub. The per-project route now works as follows:

  • Setup writes only the settings and the workflow.
  • The about page is built from the identity block, the brief's opening, the newest shipped intents' press releases and the changelog.
  • Interface words, styles and scripts come from the binary.
  • The gate fails any text from a file abcd does not write, and a site with no title.

Hardening along the way:

  • Repository details come from the origin remote only.
  • A missing record-lint config reads abcd's standard layout.
  • Project sites show nothing from git history (no names, counts or dates); whether they should is to be revisited.
  • The site workflow verifies abcd's build provenance against its release workflow on main and the tagged commit, from the highest stable release.

abcd's own site plan (itd-2610142145236613) is reshaped: abcdev.app becomes abcd's docs, and the record moves to abcd's GitHub Pages. abcdesign.app is published from intentdriven/abcdesign.

Records

  • adr-2610032150581128 is amended in the open: the session relay is abcd's second listener. Brief invariant 7 and adr-2610091016270711 are updated to match.
  • The move of abcd's own site is split out of itd-2610091010348892 into its own intent.

Checks

  • make preflight passed on a3d7ffd (latest main merged in), and the push receipt is minted.
  • Since the first review, three fixes from a later review pass are in: the dev server binds a loopback listener, decision-log links go through the site's base path, and the repository records live sessions as local.
  • The merge with main brought the after-update bundle. Its ahoy install --only-waiting run now also puts the live-sessions question when a repository never answered it; the test that every install question can wait failed on the merge and passes now.
  • Each intent was reviewed by an independent pair. The brief session had two security rounds and a final verification. Every finding is fixed or captured.
  • Live checks ran in a headless browser against the real relay and a fake GitHub.

Not yet exercised:

  • real GitHub (OAuth, the Pages API, pull requests)
  • Safari and mobile browsers
  • a live Render deploy

Held unarmed until the security and bug-fix release is tagged, so that release carries none of this code.

Outward steps held for the product thinker's go at the cut

  • abcdesign.app's DNS and its domain verification for the organisation (intentdriven/abcdesign is created and idle until a release carries abcd session page).
  • firstSessionRelease, currently v0.17.0, set to the release that first carries abcd session.
  • Closing both specs, after a docs fidelity review.

Refs: iss-2610141947553722
Refs: iss-2610142036270003
Refs: iss-2610142306552961
Refs: iss-2610150102542349
Refs: iss-2610150529485165
Refs: iss-2610150553031954
Refs: iss-2610150558540639
Refs: iss-2610150656007932
Refs: iss-2610150706489874
Refs: iss-2610150756067603
Refs: iss-2610070637562567
Refs: iss-2610151023089957

Assisted-by: Claude:claude-opus-5-5

REPPL added 30 commits October 14, 2026 20:48
Fills spc-2610141709430146 with the design and its four steps, and
captures the privacy-hygiene false positive on a dotted RFC section
number found while checking the record.

Assisted-by: Claude:claude-opus-5-5
collab.js bundles Yjs 13.6.33, y-codemirror.next 0.3.6, y-protocols
1.0.7, CodeMirror 6 with Markdown and qrcode-generator 2.0.4, all MIT,
resolved to versions published before 2026-09-29 and built outside the
repository with esbuild 0.28.2, so no Node toolchain enters it (adr-47).
PROVENANCE.json names every package, version and npm integrity hash and
the bundle's SHA-256; LICENSES.txt carries each licence.

Assisted-by: Claude:claude-opus-5-5
Amends adr-2610032150581128 in the open, as its own rule requires,
with the terms the session relay runs under: started on purpose,
loopback unless an address is chosen, reached on a person's machine
through Tailscale Serve and never Funnel, admission by join code and
GitHub's answer, and no project content at rest. Brief invariant 7
and adr-2610091016270711's consequences now say the same.

Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
Rooms opened by a verified team member, six-character join codes from
the 31-symbol alphabet, guests and members, the update log replayed to
newcomers, guest updates refused, suggestions routed to members stamped
with the guest's name, the roster with credited Yjs client ids, wrong
codes limited per address, size and count limits, and purge on host
close or idle. In memory only; no transport, no stdout.

Assisted-by: Claude:claude-opus-5-5
The front door onto the relay core: the session WebSocket, the sign-in
exchange that adds the team's app secret on the relay and never echoes
it, the public client id, and a health check. Origins are checked
against an allowlist (abcdesign.app and loopback by default), the
forwarded address is trusted only behind a declared proxy, and a
token is checked against GitHub's login and push permission and then
dropped. Adds github.com/coder/websocket v1.8.15, the one approved
dependency.

Assisted-by: Claude:claude-opus-5-5
A project's GitHub Pages address is owner.github.io/<repo>/, and every page
the site build wrote linked its stylesheet, scripts, pictures, navigation
and record routes root-absolute, so served there it would load none of
them. `abcd site build` and `abcd lint site` take --base-path (default /,
normalised to a leading and trailing slash, plain path segments only), and
every root-absolute URL goes through one helper, sitePath. The gate
resolves the stylesheet against the prefixed root and a new base-path check
refuses a root-absolute link outside it. The default output is byte for
byte what it was: the golden files are unchanged and a build of this
repository's own site before and after matches on all 3082 files.

Assisted-by: Claude:claude-opus-5-5
The blueprint pins one abcd release and the SHA-256 its checksums.txt
publishes for abcd-linux-amd64, read when the files are written. The
Dockerfile checks the download against that digest and runs the relay
on 0.0.0.0:$PORT behind the host's proxy; render.yaml declares one free
docker web service with a /healthz check and the sign-in secret as an
unsynced variable. Each file carries a digest of its own body, so a
rerun replaces an untouched file and refuses an edited one, writing
nothing.

Assisted-by: Claude:claude-opus-5-5
Every page the site build writes now opens with a line saying it is the
record of an abcd-managed repository, linking to the repository. The words
are a new interface string, managed_record, in the closed ui.json
allowlist (abcd's own and the seed setup writes, which stay byte-equal), so
an older ui.json gains it with its default words and the provenance gate
still refuses any word no repository file accounts for. A managed
repository with no package manifest takes its repository address from the
origin remote on github.com, which a release workflow's checkout carries.

Assisted-by: Claude:claude-opus-5-5
A second provider behind the hosting seam: a project's own GitHub Pages,
built by its release workflow. The repository half holds no secret: the
deploy job runs in the github-pages environment with the run's own token
(contents: read, pages: write, id-token: write) and deploys through
actions/upload-pages-artifact v5.0.0 and actions/deploy-pages v5.0.1,
pinned to full commits. The host half speaks the GitHub REST API through
the forge as the person, so it names no credential: it reads whether the
repository is public and has a Pages site, creates one built by a
workflow, sets a custom domain only when one is asked for, and reports
the address Pages gives. A private repository, a site published from a
branch and a domain routed elsewhere are refused rather than moved.

The seam gains Site.Repo and one method, Profile, carrying the deploy
environment and permissions, the switch, the public-only rule and the base
path the host serves the site below; cloudflare's profile is the site
environment, contents: read and the root. githubpagestest is an
in-process fake of the four routes.

Assisted-by: Claude:claude-opus-5-5
`abcd session serve` runs the relay in the foreground on loopback by
default; a non-loopback --listen is the explicit second choice, and
--behind-proxy is for the team relay. The start line prints the
Tailscale Serve command, never Funnel. The sign-in secret is read from
ABCD_SESSION_GITHUB_CLIENT_SECRET by name and never printed.
`abcd session blueprint` writes .abcd/relay/ pinned to a release's
checksum, read through the release-asset fetcher.

The listener gate becomes a two-entry allowlist, the dashboard and the
session relay, each citing adr-2610032150581128 and its amendment.
The verb is placed under Set-up beside dashboard, with its plugin page,
brief surface chapter, sentences, surface snapshot and CLI reference.

Assisted-by: Claude:claude-opus-5-5
A how-to for running the relay on the host's machine through Tailscale
Serve or as the team's own relay on Render, with the GitHub sign-in
app's setup. Acknowledges coder/websocket and the rascals join pattern
the relay follows.

Assisted-by: Claude:claude-opus-5-5
`abcd site setup --provider github-pages` asks whether to switch the
project site on, naming its address, before it writes anything. A yes
records "publish": true in the composition's hosting block, writes the
site's inputs and adds the release workflow, and nothing else; a no writes
nothing. The provider publishes public repositories only, so setup reads
the repository's visibility from the forge first and refuses a private one
in one line. The workflow template takes the provider's profile: the
github-pages deploy job runs in the github-pages environment with the
run's own token and three permissions, checks out nothing, and the render
job reads the switch at the released commit, building and publishing
nothing while it is off, and passes --base-path /<repo>/ to the build and
the gate. The cloudflare workflow is byte for byte what it was, pinned by
a golden file. With the switch off, setup touches nothing remote and
offers to remove the workflow it added, keeping one edited by hand. The
forge stage manages site-render and the provider's own environment; a
provider with no secrets lists no secret step and resolves no credential.

Assisted-by: Claude:claude-opus-5-5
Fills spc-2610141709461788's summary, footprint and four landed steps for
itd-2610091010348892, and captures iss-2610142036270003: on the
github-pages provider the security headers site-src/headers declares are
not served, because _headers is Cloudflare's and GitHub Pages ignores it.

Refs: iss-2610142036270003
Assisted-by: Claude:claude-opus-5-5
The brief surface cross-check pins every surface chapter and command
page: adds the session chapter and command, recomputes the prompt hash
and the example receipt's manifest hash. The session page tells the
user to run the foreground relay in their own Terminal instead of
fencing a bare-path command.

Assisted-by: Claude:claude-opus-5-5
Taken from the intent's accepted Mechanism.

Assisted-by: Claude:claude-opus-5-5
Taken from the intent's accepted Mechanism.

Assisted-by: Claude:claude-opus-5-5
On the host route the relay binds loopback and Tailscale Serve proxies
to it, so every visitor's socket address was 127.0.0.1 and one person's
wrong codes shut everyone out of joining for ten minutes. On a relay
bound to loopback alone, a connection from loopback is now counted by
the tailnet address Serve forwards in X-Forwarded-For, accepted only in
the shape Serve writes it (one line, one address in Tailscale's ranges);
anything else falls back to the peer address. A tailnet IPv6 address is
its own key, since every device of a tailnet shares one /64.

Assisted-by: Claude:claude-opus-5-5
Awareness frames, suggestions and hosting were bounded by counts only.
Each connection now has a token bucket for awareness (20 a second, burst
40) and for suggestions (1 a second, burst 5), and one address opens at
most 3 sessions in 10 minutes; verify keeps its cap of 5 per connection.
A message over its rate is dropped with error rate-limited, and the
connection stays open.

Assisted-by: Claude:claude-opus-5-5
The blueprint's Dockerfile built FROM alpine:3.22, a tag the registry
can move. It now renders alpine:3.22@sha256:5291449c..., the multi-arch
image index digest taken from Docker Hub on 2026-10-14, held as a
constant in the blueprint code. The goldens are updated.

Assisted-by: Claude:claude-opus-5-5
The relay's sign-in exchange is the token-mediating backend of OAuth 2.0
for Browser-Based Applications (draft-ietf-oauth-browser-based-apps-27,
since RFC 10017). It joins the references, with its CSL-JSON entry.

Assisted-by: Claude:claude-opus-5-5
The page a team holds its live brief session on (spc-2610141709430146
step 3): index.html with a strict content-security policy and no
referrer, session.js and session.css, and the vendored editor bundle.
The HTML names its files three levels up, so GitHub Pages' 404 fallback
serves it at /<owner>/<repo>/brief/ and a base path works.

The page signs a team member in through GitHub with PKCE (or a pasted
token, held in memory only), hosts a session, reads the brief through
the GitHub API and seeds one shared document once before the QR code
and join code show. Members edit live with presence and register their
client ids for credit; guests read and suggest a replacement for a
selected passage, which a member applies or declines. It rejoins with
the same code after a dropped connection. Sending the session back to
GitHub is left as the submitSession hook for step 4.

package web writes the six files into --out and nothing else, and
refuses a directory holding anything it did not write.

Assisted-by: Claude:claude-opus-5-5
The page writes exactly its six files, byte-identical to the embedded
assets, and refuses a foreign or symlinked directory untouched. The
vendored bundle and its entry module match PROVENANCE.json, and the
check is shown failing on a tampered copy. The HTML carries the policy
and loads only its own files; the script holds no innerHTML or eval,
imports only the bundle, names only GitHub's API and sign-in page, and
keeps the token out of storage, URLs, the page and logs.

Assisted-by: Claude:claude-opus-5-5
`abcd session page --out <dir>` writes the brief session's static page
from the assets the binary embeds, and nothing else; a directory holding
anything it did not write is refused with exit 2 and nothing written.
The sub-verb gets its sentence, surface snapshot, CLI reference and
generated appendix, and the plugin page and brief surface chapter
describe the page: joining by fragment, sign-in, hosting and seeding,
editing and credit, guests' suggestions and approvals.

Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
A repository that already served Pages at a custom domain, set up
without --domain, got a workflow built for /<repo>/ while GitHub
served the site at the domain's root, so every asset would 404 and the
gate passed. Inspect now refuses a live custom domain when none was
asked for and names the flag to pass. The host stage refuses an address
whose path differs from the profile's base path, and setup reads the
host before it asks or writes anything, so either refusal lands before
the repository half is written.

Assisted-by: Claude:claude-opus-5-5
A repository that already published a workflow-built Pages site had it
taken over silently. Inspect now names an existing site by its address
and build type. Setup names it in the switch question, or asks on its
own when the composition already holds the switch and the release
workflow is not there yet; a no writes nothing, and --yes is consent.
The host outcome names the site it publishes to.

Assisted-by: Claude:claude-opus-5-5
REPPL added 27 commits October 16, 2026 15:38
…dress or a token

The product thinker approved a new design for the live brief session on
2026-10-16, after the draft page asked for a relay address and a pasted
token before showing the brief (iss-2610161332024554). The intent and its
spec are rewritten to it, including the repository-setup part a later lane
builds, and adr-2610161400542916 records the architecture: the page finds
its relay itself, nobody pastes a token, a cloud relay keeps GitHub's web
flow and a local session signs in by GitHub's device flow, served from the
host's own computer. adr-2610091016270711 and adr-2610032150581128 carry
amendments pointing at it.

Refs: iss-2610161332024554
Assisted-by: Claude:claude-opus-5-5
…ken to give

The session page asked a product thinker for a relay address and a pasted
GitHub token before it showed the brief. Now it finds the relay itself: a
page the host's own computer serves is its own relay, and on abcdesign.app
the page takes the relay the invite link names, checked against the
repository's relays.json, or the first relay that file names. Where nothing
is set up it says so in plain words and asks for nothing. Once a session is
joined or started it opens on the brief's press release.

Nobody pastes a token: the page has no token field, and the relay's verify
takes only a GitHub App user access token. A cloud relay holding the app's
secret keeps GitHub's web flow; a relay holding the client id alone offers
GitHub's device flow through /auth/device, keeping the device code on the
relay. `abcd session start` is the host's one command for a local session:
it serves the page from the relay, answers only the computer's tailnet name
and loopback, and prints the Tailscale Serve step and the link to open.

Resolves: iss-2610161332024554
Assisted-by: Claude:claude-opus-5-5
Two findings from the rework, each its own record: the Host header Tailscale
Serve passes to a local session's relay is unverified on a live tailnet, and
no sign-in, hosting or joining ran against real GitHub. The spec's fifth
step records the commit it landed in.

Refs: iss-2610161444081367
Refs: iss-2610161444092883
Assisted-by: Claude:claude-opus-5-5
…own GitHub App

Repository setup asks "Set up live brief sessions?" (yes, no, decide later)
and, on a yes, "Where may sessions run?" (local, cloud, both), and records
the answer as sessions.live in .abcd/config.json. A no is never asked again;
deciding later asks again at the next setup run and when someone runs
abcd session start or blueprint at a terminal. --live-sessions answers it in
any run, and the bare detection carries both questions for the plugin page.

abcd session setup does the setting up, by hand at any time: it registers
the team's own GitHub App through GitHub's App manifest flow from a loopback
page, keeps only the public client id in .abcd/relay/github-app.json, and
says what is left: installing the app, ticking Enable Device Flow for local
sessions (or leaving it off for the cloud), Render's Deploy button, the
client secret pasted straight into Render's form, and --relay-origin to
record the relay's address.

The relay's Render Blueprint moves to render.yaml at the repository's root,
since Render's Deploy button reads only the root Blueprint; a root
render.yaml the team wrote is left alone and the Blueprint goes under
.abcd/relay/ instead. The recorded client id is written into it as a plain
value.

Refs: itd-2610091016262335, spc-2610141709430146
Assisted-by: Claude:claude-opus-5-5
…ueprint

The product thinker ruled that each team registers its own GitHub App for
live sessions, local ones included, and abcd owns no shared app; the open
question on the intent is closed. The spec records that Render's Deploy
button reads only a root render.yaml, with the sources, and how setup runs
the manifest flow. Two findings are captured for later.

Refs: itd-2610091016262335, spc-2610141709430146
Assisted-by: Claude:claude-opus-5-5
The device sign-in belongs to abcd session start alone, whose relay serves
its own session page. The relay offers it only when it serves its page, so
abcd session serve without a client secret reports sign-in off, as it did
before the device sign-in landed. The session page takes a relay's device
sign-in only when the relay serves that page; on any other origin it offers
the web sign-in alone.

Each client address now holds at most four device sign-ins at once, counted
as the exchange limit counts it, so a few addresses cannot take every place.

The session page's header and sign-in comments describe the current
behaviour: no pasted token, and the relay the page finds for itself.

adr-2610161400542916 carries an amendment saying so; the help text, the
plugin command page, the how-to, the brief and the spec follow.

Assisted-by: Claude:claude-opus-5-5
abcd session setup kept an already-recorded GitHub App and said nothing of
its callback URL. An app recorded for local sessions alone has none, so
the cloud's web sign-in could not return to the session page. Where the
chosen place allows the cloud, setup now adds a step naming the callback
URL to add on the app's settings page: the same address the manifest sets,
now one function, PageCallbackURL.

Where sessions may run is now recorded once the app is, so a refusal that
says "(nothing written)" writes nothing. The help text and the command page
say what a blueprint that cannot be written afterwards keeps.

Assisted-by: Claude:claude-opus-5-5
adr-2610161400542916 still listed a shared abcd-owned GitHub App as a
question put back to the product thinker. It now carries an amendment
naming the ruling recorded in itd-2610091016262335's Decisions: each team
registers its own sign-in app at repository setup, and abcd owns none.

Resolves: iss-2610161535126075
Assisted-by: Claude:claude-opus-5-5
The relay, server and CLI session tests now use RFC 5737 documentation
addresses wherever the value does not matter. The tailnet addresses whose
shape the tests exercise (100.64.0.0/10 and fd7a:115c:a1e0::/48, which the
relay treats apart) keep it, each marked with the rule's documented
abcd-lint:allow escape. abcd lint reports none of these files.

Resolves: iss-2610161535233301
Assisted-by: Claude:claude-opus-5-5
…n progress

The session page now tells the relay when the person stops a device
sign-in or leaves the page, and the relay frees its place at once
(/auth/device/cancel) instead of holding it until GitHub's code expires.
An address refused for holding its share is told, in Retry-After, when
its first sign-in ends, rather than the exchange window.

The page shows "Too many sign-ins are in progress. Wait a few minutes,
then try again." for a 429 or a busy relay, in place of the generic
failure or the not-set-up message.

Assisted-by: Claude:claude-opus-5-5
AddressKey had been declared inside addressKey's doc comment, so the
exported function carried both comments and the unexported one none. A
test parses codes.go and checks each comment begins with its own name.

Assisted-by: Claude:claude-opus-5-5
…lback names

The cloud's web sign-in now returns to the relay's own /auth/callback,
which the team's GitHub App lists as its callback URL, instead of to the
session page. The relay sends the person back to the page the sign-in
started from (an origin it admits, a repository it serves), naming its
own origin, and the page posts the code and its verifier to that relay
alone. A return that names no relay, or a relay other than the one the
sign-in started with, is refused; the exchange relay is never taken from
the invite link or the tab's storage. /auth/token exchanges only a code
given for the relay's own callback.

A relay the person confirmed, where the repository's relay list cannot
be read, now serves guest joins only: it is never sent a sign-in code or
a token, and the page says plainly that signing in is not available
through that link. The confirmation is kept for the browser tab and
lapses after twelve hours, a session's longest life, rather than being
remembered for the repository indefinitely.

`abcd session setup --relay-origin` and `abcd session blueprint
--relay-origin` print the exact callback URL to list, with wildcard
matching off, and a manifest posted once the relay's address is known
sets it. adr-2610161400542916 carries an amendment correcting its
premise; the spec, the brief, the relay how-to and the session command
page describe the new flow.

Assisted-by: Claude:claude-opus-5-5
…y to the relay GitHub returned to

Members of a private repository sign in on abcdesign.app again. The page
cannot read a private repository's relay list before sign-in, so a relay
the person confirmed may run the web sign-in; the control is the
callback binding: GitHub sends the code only to the callback the team's
app lists, and the page takes the exchange relay only from that return.

The token is now held to the relay it was signed in through (the relay
GitHub returned to, or the relay serving the page for a device
sign-in). It is presented for verification to that relay alone, never to
a relay an invite link named that GitHub did not return the person to.

The ADR amendment, the spec, the brief, the relay how-to, the session
command page and the intent's Decisions say so.

Assisted-by: Claude:claude-opus-5-5
…tches the page's

`abcd session setup --relay-origin https://r.onrender.com:443` printed the
callback with `:443`, but the page sends the callback as its URL.origin,
which never carries the scheme's default port, and GitHub matches a
redirect_uri against the registered callback exactly. NormalizeRelayOrigin
now drops 443 for https (and 80 for http), so relays.json, the printed
callback and the manifest name the relay the way the browser does.

Assisted-by: Claude:claude-opus-5-5
The relay's callback forwarded a code to any session page path on an
admitted origin, whatever base path sat in front of /<owner>/<repo>/brief/.
A static origin can carry other publishers' pages beside the session page,
so the callback now forwards only to the page's own base path.

An origin entry (--origin, ABCD_SESSION_ORIGINS, and render.yaml through
`abcd session blueprint --origin`) may go on to name the base path the page
is served below, as `abcd session page --base-path` writes it:
https://host/base/. The callback forwards only to a session page under
exactly that path on that origin; an entry naming none, and the default
origins, mean the root. A browser's Origin carries no path, so everywhere
else the entry admits its origin alone.

The base path is part of the origin entry rather than one relay-wide
setting: it belongs to one place the page is published, and one relay may
admit the page at abcdesign.app's root and a team's own copy below a
project path. The flag, the variable and render.yaml already carry the
entries, so no new setting is needed. Base paths follow the site builder's
rule (plain path segments).

adr-2610161400542916's amendment gains the base-path binding and the
default-port spelling of the relay's address (the previous commit); the
spec, the brief, the relay how-to, the session command page and the CLI
reference describe the entry's form.

Assisted-by: Claude:claude-opus-5-5
…back

An earlier version of `abcd session setup` had the team's app list the
session page's own address, https://abcdesign.app/<owner>/<repo>/brief/, as
its callback URL. The sign-in now returns to the relay's own callback alone,
but setup with an app already recorded gave no step to take the page's
address off. It now says, for a recorded app wherever sessions may run, to
remove that Callback URL on the app's settings page if it is listed. An app
created now never listed it and is told nothing about it.

The ADR amendment, the spec, the session command page, the relay how-to,
setup's help and the CLI reference say so.

Assisted-by: Claude:claude-opus-5-5
The relay kept a default port in the origins it admits, so an entry such as
https://pages.example:443 never matched the Origin a browser sends, which
omits the scheme's default port, and the sign-in's callback would not
forward to that page either. The relay's origin normalisation now drops 443
for https and 80 for http, as NormalizeRelayOrigin does for the relay's own
address.

Assisted-by: Claude:claude-opus-5-5
The live brief session page now carries the two designs the product
thinker chose. Guided, the default, has large cards, numbered areas,
"What happens next", and arrival cards whose main buttons share a line.
Shared has a live team bar over the document, floating sidebars, an
icon-only Invite at the end of the team bar, an icon-only Send to GitHub
that widens on hover or focus, Suggestions alone on the right, labels
that fold to icons as the window narrows, and a Close X on every overlay.

Ctrl+[ and Ctrl+] switch between the designs on every platform, the Mac
included, with a toast naming the design; the choice is kept per viewer
in localStorage, read through the existing try/catch helpers and checked
against the page's own list. Both designs toggle the documents with
Alt+Shift+D and the right panel with Alt+Shift+S, keep that state across
screens and widths, turn the sidebars into sheets on a phone, and close
the overlay on top with Escape. The accent is cobalt, one --accent token
in light and dark, on a system-ui font stack with inline SVG icons.

One set of elements and one set of logic serve both designs: the design
is an attribute the stylesheet reads, and the documents, people,
suggestions, invite and editor move into each design's places. The
sign-in, relay trust, device flow, rejoin, attribution and CSP paths are
unchanged, and every string still reaches the page through textContent.
The storage test now admits the design key alongside the display name,
and the address test admits SVG's namespace in its one constant.

Assisted-by: Claude:claude-opus-5-5
…d the intent

The product thinker chose the session page's look on 2026-10-16: the
guided cards design as the default, the shared-presence design as the
alternative, Ctrl+[ and Ctrl+] to switch on every platform, and a cobalt
accent. The intent's Decisions now restate that ruling; the spec
specifies the two designs beside the start screen, whose wording follows
the new cards; the brief's session chapter describes both designs, the
shortcuts and the Send panel, which had been described as still to come;
and the relay how-to tells a team how to switch and toggle the panels.
The page's storage comment now names the design beside the display name.

Assisted-by: Claude:claude-opus-5-5
…e them

A review of the two designs found focus, sidebar state and a phone's bars
going wrong. The page now:

- keeps the editor focused across a design switch, moves focus into the
  Send, invite and trust dialogs and gives it back to their opener on
  close or Escape, and hands focus on a control a layout change hides to
  the control that does its job now;
- keeps a phone's sidebar state, and the right panel's at the slide-over
  widths, apart from a wide window's, so Escape there never hides a panel
  for good;
- closes on Escape only an overlay that can be seen, leaves Escape to the
  editor and to an input method's composition, and never takes AltGr;
- moves the design switch to Ctrl+Shift+[ and Ctrl+Shift+] (the product
  thinker's ruling), so Ctrl+[ and Ctrl+] stay the editor's indent and
  outdent;
- gives each sidebar toggle one name with aria-pressed, puts Shared's
  toolbar first in the reading order, ends the guided app bar's project
  name in an ellipsis with the guest's sign-in as an icon on a phone,
  shortens the phone's tab labels so none is cut, sets Shared's invite
  actions on one row (stacked on a phone) with a gap above, and drops the
  unused words.

Behavioural tests run build() in a small fake DOM: sidebar state across
widths and designs, the keys, Escape's order, focus, the toggles' names,
the reading order, the phone bars, and a structural check that "What
happens next" is the guided design's alone, which replaces the check that
matched comments.

Assisted-by: Claude:claude-opus-5-5
The relay how-to, the brief, the spec and the intent now say that
Ctrl+Shift+[ and Ctrl+Shift+] switch the design, leaving Ctrl+[ and
Ctrl+] to the editor, and that on Windows and Linux the page takes those
keys from the editor's fold and unfold, with no fold arrows shown. They
say a phone keeps its own panel state apart from a wider window's, as the
right panel does at the slide-over widths, and exactly when "What happens
next" shows: before the session on each step the page can go on from and
when sessions are not set up, never while it checks or cannot go on, and
live for guests only. The intent's Decisions record the product thinker
moving the switch.

Assisted-by: Claude:claude-opus-5-5
The product thinker asked for Emacs keys on the session page and chose
a per-person option in the brief editor, off by default. The draft is
blocked by the live brief session intent. The design fix round left four
small page items, each captured on its own.

Assisted-by: Claude:claude-opus-5-5
Main replaced the single decision log with one record per decision, so
the branch's one log entry, the GitHub App client id file a local brief
session reads, is recorded as dcn-2610170619127193 and the log is
dropped as main dropped it.

Main folded the two outbound install options into one; the branch's
live-sessions option is kept beside it. The site export gives decision
records the same optional git dates as other records. The site
workflow template keeps the branch's switches with main's action pins,
and its golden files take the new pins. The brief keeps main's announce,
land and status rows and the branch's session row; the release-gate
manifest takes main's sorted rosters with session added. Generated
regions were rewritten by go generate.

Assisted-by: Claude:claude-opus-5-5
…ar two static-check findings

The app-manifest flow opened its loopback listener in the CLI package, which
the listener boundary does not admit; it now comes from the session package.
The deploy-link helper is package-private, and a design test spells its
curly quote as an escape.

Assisted-by: Claude:claude-opus-5-5
@REPPL

REPPL commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

A note from verifying this PR's sign-in App steps against GitHub's current docs: the registered callback URL must match the page's redirect_uri exactly, or sign-in fails. The how-to registers https://abcdesign.app/, while the page sends its own address, https://abcdesign.app///brief/, so as written every sign-in is refused with redirect_uri_mismatch.

The sign-in App itself is already registered under intentdriven, as abcd-brief, and is not yet installed on any repository. Its callback URL cannot be read through the API, so whoever takes this PR over should check it on the App's settings page, https://github.com/organizations/intentdriven/settings/apps/abcd-brief, and make it https://abcdesign.app/intentdriven/abcd/brief/.

REPPL added 2 commits October 17, 2026 22:41
Main brought the after-update bundle. Its --only-waiting run lists the
questions it may put by hand, and the live-sessions question this
branch adds was not on that list, so an update never offered it to a
repository that had not answered it (TestEveryQuestionTheInstallAsksCanWait
failed on the merge). The sessions question now waits like the others,
and where sessions run follows it.

Assisted-by: Claude:claude-opus-5-5

This branch has not been deployed

No deployments
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