oficina / home Public

Review: rastrillo off the frozen path (iOS zoom-on-tap) open

On branch chore/rastrillo-path

Review request: move Home off rastrillo's frozen import path. Paul approved this on 2026-09-30 (polish audit r3 SUMMARY item 1; Home r3 C3). The recipe is Sheets' migration (oficina/sheets #44, main a5c17ab).

Why. Home's root module imported github.com/carlosframework/rastrillo, which stops at v0.23.0. It had received no rastrillo ui fix since 2026-08-31, iOS's zoom-on-tap fix included. On a phone, id.oficina.works' sign-in field is 14px with autofocus, so iOS zoomed the page on every sign-in to every app.

Modules. Only the root module imported the old path. connect, handoff and roster already imported only amadan.net/rastrillo/rastrillo at 29e4491, so their go.mod files are untouched and no app needs a home/connect bump.

Pin. amadan.net/rastrillo/rastrillo at 29e4491 (v0.26.0+8), the version connect, handoff and roster already require, so the build has one amadan rastrillo. The old module stays in go.mod only as an indirect requirement of estilo (chrome imports its sessions.SafeReturn).

Commits

  • e75460d TestNoZoomOnTap (browser tag, new make test-browser), modelled on Sheets' zoombrowsertest.go. Red on purpose. It emulates a touch screen and measures every text control on id.'s sign-in and recovery-code pages, and on the directory's signed-out, Open source, signed-in directory and team chooser pages. On origin/main: `id. /signin: input[name=address] 14px, id.* /passkey/confirm: input[name=code] 14px (polish-audit/r3/scripts/migrate-home-zoom-red.txt). test-vet now also vets internal/home with -tags browser`.
  • b2f937b the move: - Every import rewritten (63 sites in 44 files). - go tool rastrillo doctor --fix re-copied tokens.css (+297 lines: the touch rule, button sizes, --rst-fs-lg, primary inputs, stat band, native meter/progress, date picker). The four scripts doctor also writes (rastrillo.js, select.js, datetime.js, calendar.js) are deleted. Home serves estilo's busy.js, and doctor's shim at this pin carries rastrillo's old busy rule. New TestNoRastrilloShimBesideBusy fails if one is on disk or served (shown red by touching static/rastrillo.js). - New internal/guardstest TestNoPreMoveRastrillo forbids the old path in every module (shown red by restoring main's app.go). - The CLI is a go.mod tool. - make release (and cmd/home-placeholder/README.md) stamps amadan.net/rastrillo/rastrillo.BuildVersion. Checked: a stamped build answers v0.1.0-74-gb2f937b from /api/version on both roles. - make credits: the new db package brings gormlite's licence. - AGENTS.md: the path, the doctor-then-delete step, and the stale "no vendored static/" paragraph. The class-spelling recount required on a pin move: ui/partials still has the same six utility class= occurrences at 29e4491.

What changes at this pin. sessions, csrf, passkey, webauthn, db and migrate differ from v0.23.0 only in the import path (diffed with the path normalised). mail becomes context-aware and sends a Date header. ui partials changed, but Home renders none of them. The root package (Serve) is unchanged.

Evidence

  • make test-vet test-fmt test-build test-go: green (GOTOOLCHAIN=go1.26.5). make test-race: green except internal/provisioner, which is Linux-only, so CI covers it.
  • make test-browser: green.
  • Old database boots. On the production-shaped local stack (polish-audit/home-run/r2/run.sh), the main build (2bec942) signed someone in and left a second magic link unredeemed. The new build then booted over the same state: schema inventory clean, the old directory cookie opens the signed-in directory, id.* accepts the old session (/passkey/codes), and the link minted by the old build signs in (home-run/r2/oldbd.out).
  • Screenshots in polish-audit/shots/r2-home-path-{before,after}[-touch]-*.png: 19 pages × 1440/390/320. With a mouse, before and after are pixel-identical apart from a caret blink. On a touch screen, only the sign-in field, its error state and the recovery-code field change: 14px → 16px, about 3px taller. No page overflows.
  • Shared chrome check (new polish-audit/r2-home-check.config.mjs): Home has no estilo bar yet, so it fails has [estilo-bar] everywhere, on main and here alike (r2-home-check-path.output.txt). That is feat/r2-shared-chrome's job.
  • CLS (polish-audit/r2-home-cls-{main,path}.output.txt) is identical on main and here: front page cold load 0.0028 at 1440 and 0.0034 at 390, every other hop 0.

Not in this branch. rastrillo main (v0.27's CSP, time layouts and the migrate.Fn change). estilo still imports the old path.

Review. AGENTS.md requires a whole-branch review by the highest-capability model before merge. Not reviewed yet.

🤖 Generated with Claude Code

CI passed at b2f937b (amadan ci status exit 0): vet (now including -tags browser), fmt, build, go-test and race (internal/provisioner included, on Linux). Not merged; waiting for review.

feat/r2-shared-chrome is stacked on this branch; its own discussion follows.

Whole-branch review — chore/rastrillo-path @ b2f937b

Reviewer: Claude Fable 5.1 (claude-fable-5-1), the highest-capability model available to this session, as AGENTS.md's review rule requires. Read as one diff against origin/main (2bec942). Review only; nothing edited, committed, pushed or merged.

VERDICT: APPROVE-WITH-NITS

What I verified

  • The module diff, read myself, with the import path normalised (diff -r -I over the module cache, v0.23.0 vs 29e4491): sessions, csrf, passkey, webauthn, db and migrate differ only in import lines — not one semantic line. The root package (serve.go, basecatalog.go, localemw.go) likewise. So the __Host-rastrillo_session cookie, its 43-char token, 30-day expiry, HttpOnly/Secure/Lax attributes and the session/passkey/recovery tables are byte-for-byte the same code. Confirmed empirically: the same cookie name, value length and expiry are set by the 2bec942 build and this build.
  • Old state survives the upgrade on the production-shaped stack (home-run/r2/run.sh, same recipe as the discussion): signed in on a 2bec942 build, left a second magic link unredeemed, booted b8894b7 (which includes this branch) over the same state/. Schema inventory clean on both roles; the old directory cookie opens the signed-in directory; the old id.* session opens /passkey/codes; the link minted by the old build signs in on the new one.
  • Mail is the one real behavioural change at this pin (rastrillo/mail): the SMTP conversation is hand-rolled rather than smtp.SendMail, with the same STARTTLS-if-offered and refuse-AUTH-if-unoffered rules, a 30 s ceiling when the context has no deadline, and the connection closed when the context ends; a Date header is added (seen on the new build's mail; absent on main's). signin.Flow passes the request context to Send. Header-injection checks are unchanged in effect (every header value now goes through one check rather than three).
  • ui: Go changes are the stat-band style-guide entry, calendar.js added to vendoredNames, and <bdi> in a sample. Home renders no partial and serves none of the scripts, so none of it reaches a page.
  • TestVendoredAssetsMatchTheLibrary compares static/tokens.css byte-for-byte with ui.TokensCSS(), so the +297-line tokens.css is exactly doctor's output. Partials recount at 29e4491: the 6 utility class= occurrences stand, as AGENTS.md now says.
  • Gates (GOTOOLCHAIN=go1.26.5): make test green except internal/provisioner under -race (Linux-only; CI covered it). make test-browser green with RASTRILLO_CHROME pointed at Playwright's Chrome for Testing (the test does not find the chromium-*/chrome-mac-arm64/Google Chrome for Testing.app layout by itself — see nit 3).
  • Mutations, each red: restoring github.com/carlosframework/rastrillo/migrate in internal/operator/ledger.go → TestNoPreMoveRastrillo fails naming the file; touch internal/home/static/rastrillo.js → TestNoRastrilloShimBesideBusy fails.
  • Release stamp: the binary built with -X amadan.net/rastrillo/rastrillo.BuildVersion=… answers it from /api/version on both roles; the old symbol is no longer linked by anything that logs it.

Findings (none blocking)

  1. LOW — rastrillo/mail (new pin), reached from internal/home/signin.go:218 via signin.Flow.Begin(r.Context(), …). The new sender closes the SMTP connection when the request context ends and caps a deadline-less conversation at 30 s. Before, smtp.SendMail ignored the context entirely. A person who closes the tab between pressing "Sign in" and the relay's 250 now aborts the send mid-DATA rather than completing it; they see nothing either way (they left), and the next attempt mints a new link. The 30 s cap is an improvement (a wedged relay no longer pins a handler forever). Nothing to change; recording it because the discussion says mail "becomes context-aware" without naming this edge.
  2. NIT — go.mod:12–13. github.com/chromedp/cdproto and chromedp move from indirect to direct requirements for the browser test; the tool directive also pulls ledongthuc/pdf and orisano/pixelmatch into go.sum. Expected; noting so nobody "tidies" them away.
  3. NIT — internal/home/zoom_browser_test.go:87 / Makefile test-browser. The Chromium discovery misses the current Playwright layout (chromium-1234/chrome-mac-arm64/Google Chrome for Testing.app/…), so make test-browser fails on this machine with "no chromium found" until RASTRILLO_CHROME is set. Either teach the finder that app name, or say in the Makefile comment which env var to set. Not a correctness problem.
  4. NIT — Makefile test-browser runs -run 'OnTap' only, while test-vet vets the whole tagged package. Fine today (one browser test); when #81's successors add more, widen the pattern or drop it.

AGENTS.md: the path paragraph and the doctor-then-delete step are accurate against what I measured; the stale "no vendored static/" paragraph is rightly gone.