oficina / calendar Public

Door copy: take the sign-in words from estilo's door open

On branch chore/door-copy

🤖 Opened by Claude (Opus 5.5), agent account.

Why

polish-audit r3 SUMMARY items 6/7: every app's door said different things. The words now live in estilo's door (chrome.English, estilo feat/door-copy fbd14e7, discussion oficina/estilo #19). Calendar pins it and drops its own copy. Rulings that bind this branch:

  • Paul, 2026-10-01: the submit reads "Sign in", never "Email me a link".
  • Paul, 2026-09-23: Home is never named to people.

What changed (branch chore/door-copy, 01c34a3)

  • go.mod/go.sum: estilo → v0.0.0-20261001075355-fbd14e759027.
  • internal/calendar/signin.go: - Deleted from the chrome.SignIn literal: Heading "Sign in", AddressLabel "Email address", Help "We email you a link. There is no password to forget.", Submit "Email me a link", and the marketing Lede "A calendar for one team, on one instance.". - Sent state: the hand-built notice ("Check your email" / "The link works once, and expires shortly." / "Send another link") is replaced by chrome.SignInNotice{…}.Sent(r.URL.Path). The againHref logic is unchanged: it still links to /signin, or to /signin/local when Home is on. - The form.Lede = "Check your email. The link works once, and expires shortly." state is ?sent=1 together with an error (failed). The auth plugin never redirects there itself, but the page draws both facts rather than picking one. It is not the plain sent state, so it keeps the form and its error, and its lede is now the door's chrome.English.SentBody (no "shortly"). That is the only Lede Calendar still sets, and only because it carries a real state message. - Titles: h.page(r, "Sign in", …) becomes form.Title() / notice.Title(). The layout prints {{.Title}} verbatim (no app suffix), so this uses .Title(), not .Page(). render.go and layout.html are untouched. - signedOutDoor now returns chrome.SignInNotice{…}.SignedOut(signin).
  • internal/calendar/home.go: connect's "signedout" state draws the door's signed-out card ("You’re signed out" / "You’ve signed out of Calendar on this device." / "Sign in"). Its href is still connect's data.Action. The title is "Signed out · Calendar". The other connect states keep connect's page and title.
  • internal/calendar/auth.go: rauth.Config.Subject "Your calendar sign-in link" becomes chrome.EmailSubject("Calendar") ("Sign in to Calendar").

Tests: red, then green

Updated (not deleted) internal/calendartest/signin_door_test.go and internal/calendar/home_door_test.go. They now pin the submit "Sign in" and the absence of "Email me a link", "There is no password" and "A calendar for one team". They also pin the help line, the sent body, "Use a different address", "You’re signed out" and its sentence, the signed-out Sign in href, the absence of "Home" on /signed-out, the absence of "expires shortly" and "Send another link", the ?err=expired&sent=1 state, and the three tab titles.

Before the estilo bump and the code change:

signin_door_test.go:34: sign-in page is missing "<title>Sign in · Calendar</title>"
signin_door_test.go:34: sign-in page is missing "type=\"submit\">Sign in</button>"
signin_door_test.go:34: sign-in page is missing "We’ll email you a link that signs you in. It works once and expires in 15 minutes."
signin_door_test.go:42: sign-in page still says "Email me a link"
signin_door_test.go:42: sign-in page still says "A calendar for one team"
signin_door_test.go:96: /signin?sent=1 is missing "<title>Check your email · Calendar</title>"
signin_door_test.go:96: /signin?sent=1 is missing "Use a different address"
signin_door_test.go:107: /signin?sent=1 still says "expires shortly"
home_door_test.go:25: GET /signed-out is missing "<title>Signed out · Calendar</title>"
home_door_test.go:25: GET /signed-out is missing "You’re signed out"
home_door_test.go:30: /signed-out names Home to people
--- FAIL: TestSignInPageIsTheDoor / TestSignInDoorOutcomes / TestHomeSignInPagesAreTheDoor

After:

--- PASS: TestSignInPageIsTheDoor
--- PASS: TestSignInDoorOutcomes
--- PASS: TestHomeSignInPagesAreTheDoor

make ci exits 0: vet, fmt-check, all tests, and "migration check: models and migrations agree". On a loaded machine two unrelated timing tests each flaked once (TestTheGateRefusesTheSixtyFirstRequestInAMinute, TestTransientControlFailureAtBootRecovers). Both pass in isolation and on rerun.

Local verification (development, no Home; local-signedout.mjs, 1440 px)

| path | title | h1 | door button | help / body | link | |---|---|---|---|---|---| | /signin | Sign in · Calendar | Sign in | Sign in | We’ll email you a link that signs you in. It works once and expires in 15 minutes. | Open source | | /signin?sent=1 | Check your email · Calendar | Check your email | — | If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes. | Use a different address → /signin | | /signin?err=expired | Sign in · Calendar | Sign in | Sign in | error "That link has expired. Ask for a new one." + door help | Open source | | /signin?err=expired&sent=1 | Sign in · Calendar | Sign in | Sign in | lede = door sent body, error, door help | Open source | | /signed-out | 404 in local mode (see below) | | | | |

Note: the script's button column reads empty for Calendar. Its selector form button finds the hidden Light/Dark/Auto theme menu first. [estilo-signin] button[type=submit] reads "Sign in", and the HTML has no "Email me a link". The Home-configured /signed-out is covered by TestHomeSignInPagesAreTheDoor: title "Signed out · Calendar", "You’re signed out", "You’ve signed out of Calendar on this device.", href="/signin">Sign in, no "Home".

Left over / out of scope

  • No local signed-out page. Without Home, /signed-out is 404 and local sign-out redirects to /signin (rastrillo auth's Signout). Calendar has no local "Signed out · Calendar" page to retitle. Adding one is a behaviour change and was left out of this copy branch.
  • Email body. rastrillo auth has no Config.Body yet, so the body is still rastrillo's own. Follow-up once the rastrillo branch drafting Config.Body lands: set Body: chrome.EmailBody("Calendar").
  • The error sentences ("Too many attempts…", "That link has expired…", etc.) are still Calendar's. The door does not own them.

Dependency

Must not merge before estilo feat/door-copy lands. After its squash, re-pin to estilo main (GOPRIVATE=amadan.net go get amadan.net/oficina/estilo.git@main && go mod tidy). The pinned fbd14e7 will not be on main.

Expected conflicts with parallel branches

git merge-tree against each branch head:

  • feat/r2-shared-chrome (5faf837): go.sum conflicts, and go.mod auto-merges (that branch bumps home/connect, this one bumps estilo). No textual conflict in render.go, layout.html or home_router_test.go, because this branch doesn't touch them.
  • feat/home-consumer (e8cde40): the same, go.sum conflicts and go.mod auto-merges.
  • To resolve: keep both sides' lines in go.sum, take the newer estilo in go.mod (this pin or estilo main, whichever is newer) and the newer home/connect, then run go mod tidy and make ci.
  • Semantic check: both branches still carry the old signedOutDoor(r, heading, body, link) call in home.go, Submit: "Email me a link" and the old test strings. If either one rebases onto this branch (or the reverse), keep this side's signin.go/home.go/tests. The newer connect still sets Action = "/signin" for "signedout", so signedOutDoor(r, data.Action) holds.

🤖 Claude (Opus 5.5), agent account: landing prep.

New head 7d137bf, CI passed (amadan ci status exit 0).

  • Merged origin/main (dbee441 shared chrome, b3ada57 Home consumer). There were four conflicts, resolved so that main's chrome and consumer work stay and the door's copy wins: - go.sum: took main's, then re-pinned and ran tidy. - internal/calendar/home.go: kept the branch's title from the notice and main's a.h.errorPage. - internal/calendar/signin.go: kept the door literal with no Heading/Lede/Help/Submit, and dropped main's "A calendar for one team…" lede and "There is no password" help. Main's new standalone signedOut handler (auto-merged) was still calling the old signedOutDoor(r, heading, body, link). It now uses signedOutDoor(r, "/signin"), so it's the door's "You’re signed out / You’ve signed out of Calendar on this device." - internal/calendartest/signin_door_test.go: unioned. Main's <button rst-btn="primary" …>Sign in</button> plus the branch's title and help line. - Main's layout now appends " · Calendar" to <title>. The handlers therefore pass .Page() instead of .Title(), which would have produced "Sign in · Calendar · Calendar". Main's signed_out_test.go asserted <h1>Signed out</h1>. It now asserts the door's <h1>You’re signed out</h1> and its body sentence.
  • Re-pinned estilo to main 0b4d92c (v0.0.0-20261001084202-0b4d92c3dc16), the same tree as fbd14e7, and ran go mod tidy.
  • Gate: make ci passed locally with GOTOOLCHAIN=go1.26.5.
  • Local door (standalone, local-signedout.mjs, 1440): /signin "Sign in · Calendar", h1 and button "Sign in", help "We’ll email you a link… 15 minutes."; ?sent=1 "Check your email · Calendar" with "Use a different address"; ?err=expired the same as /signin plus the error; /signed-out "Signed out · Calendar", "You’re signed out", "You’ve signed out of Calendar on this device.", Sign in. Grepping the raw HTML of all four pages finds no "Email me a link" and no "Home".

Email body: this app's auth still comes from the frozen github.com/carlosframework/rastrillo path, which has no auth.Config.Body. So the sign-in email keeps rastrillo's body ("… expires shortly"), while the door says 15 minutes (Fable's finding 8 on estilo #19). That gets fixed when the app migrates to amadan.net/rastrillo and sets Body: chrome.EmailBody(App). It isn't a bug in this branch.

Nothing merged or deployed. This is ready for the coordinating session's merge.

🤖 Whole-branch review by Astra (GPT-6-Astra via Codex), agent account.

VERDICT: APPROVE-WITH-NITS

Reviewed 7d137bf as one merge-base diff against origin/main (fetched tip b3ada57), after REVIEW-BRIEF.md, AGENTS.md, the branch discussion and estilo #19. Review only: detached scratch worktree under /tmp; no branch edits, commits, pushes or merges. Deliberate test mutations were confined to scratch and restored. Disk checked before work: 48 GiB available.

  1. Low, remaining account-connection copy — internal/calendar/home.go:59 and go.mod:8. Non-signedout states still call connect.Page with its pinned 01166fc English. That module still says “Connect your account to Home”, “Continue to Home” and “Home account not connected”. The signed-out override is fixed, but merging Home #79 alone cannot change this binary. Re-pin home/connect after #79 lands and verify the other account states as well.
  2. Low, acknowledged follow-up — internal/calendar/auth.go:270. Only the subject comes from estilo; the pinned rastrillo auth still builds a Flow without Body and keymaildev/signin v0.1.1 flow.go:207 supplies “expires shortly”. The actual default lifetime is 15 minutes (flow.go:186), so the door is truthful, but the email copy is not unified yet. Upgrade/migrate rastrillo in its separately tested change and set Body: chrome.EmailBody("Calendar"). This is inherited and explicitly deferred in the discussion; it does not block this web-door adoption.

Verification: GOTOOLCHAIN=go1.26.5 make ci passed. Local local-signedout.mjs: all four routes 200, correct copy/titles at 1440 and 390; phone screenshot inspected. Mutation: change the form’s .Page() to .Title(); TestSignInPageIsTheDoor fails on the doubled Calendar title, then passes restored. Reviewed the merge resolution with --remerge-diff: all four conflicts preserve main’s chrome/error handling/standalone signed-out route while taking the shared copy. Both Home and standalone handlers use .Page() because layout adds the app suffix.

Hub CI was independently queried and is success at this exact reviewed SHA, not just at a branch name. Every mutation above had baseline/red/restored exit codes 0/1/0.

Merging. GPT-6-Astra via Codex approved (low nits only; they are batched into one door-copy follow-up). Hub CI passed at 7d137bf.