oficina / home Public

Hide the id.* hand-off page while it submits itself

/authorize/resume renders a Continue page that static/assertion.js
submits at once, so every sign-in and every app switch that needs an
exchange flashed "Continue to Oficina". The copy is now hidden for 1.5 s
(estilo-reveal) and appears only if the submit stalls; a noscript rule
shows it at once with scripts off. Spec shared-chrome §9.3; the switcher
tiles wait on this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 385b858793ca6166908ab2d829199f309de51fc0 parent ce6f0a1
2 files changed, +61 −0
  • internal/home/handoff_reveal_test.go +47 −0
  • internal/home/templates/assertion.html +14 −0

CI failed — run details

CI log
amadan: .amadan/timeout: this job's budget is 15m0s (this runner allows up to 30m0s)
=== vet ===
CGO_ENABLED=0 go vet ./...
go: downloading amadan.net/carlos/platform.git/launchctx v0.0.0-20260911135504-fb577f96f7d1
go: downloading amadan.net/oficina/estilo.git v0.0.0-20260911182719-0bcb9287988d
internal/home/credits.go:8:2: amadan.net/oficina/estilo.git@v0.0.0-20260911182719-0bcb9287988d: invalid version: git fetch -f --end-of-options origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /home/paulca/.local/state/amadan/runner/repos/oficina__home/cache/go-mod/cache/vcs/bf4984e7a537af013cffcd0a654026b503a4e1566cddf740e3c37d2f6ae937e4: exit status 128:
	fatal: unable to access 'https://amadan.net/oficina/estilo/': The requested URL returned error: 429
internal/home/app.go:25:2: amadan.net/oficina/estilo.git@v0.0.0-20260911182719-0bcb9287988d: invalid version: git fetch -f --end-of-options origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /home/paulca/.local/state/amadan/runner/repos/oficina__home/cache/go-mod/cache/vcs/bf4984e7a537af013cffcd0a654026b503a4e1566cddf740e3c37d2f6ae937e4: exit status 128:
	fatal: unable to access 'https://amadan.net/oficina/estilo/': The requested URL returned error: 429
internal/operator/command_linux.go:10:2: amadan.net/carlos/platform.git/launchctx@v0.0.0-20260911135504-fb577f96f7d1: invalid version: git fetch -f --end-of-options origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /home/paulca/.local/state/amadan/runner/repos/oficina__home/cache/go-mod/cache/vcs/55cd75de7b117f7c5cd43edfd800f8d47521af42d6b939d46f385b6d34c14c9a: exit status 128:
	fatal: unable to access 'https://amadan.net/carlos/platform/': The requested URL returned error: 429
internal/operator/command_linux.go:11:2: amadan.net/carlos/platform.git/launchctx@v0.0.0-20260911135504-fb577f96f7d1: invalid version: git fetch -f --end-of-options origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /home/paulca/.local/state/amadan/runner/repos/oficina__home/cache/go-mod/cache/vcs/55cd75de7b117f7c5cd43edfd800f8d47521af42d6b939d46f385b6d34c14c9a: exit status 128:
	fatal: unable to access 'https://amadan.net/carlos/platform/': The requested URL returned error: 429
make: *** [Makefile:69: test-vet] Error 1

amadan: step vet failed: exit status 2

amadan: job failed at step vet — its output is above, not at the tail of this log
diff --git a/internal/home/handoff_reveal_test.go b/internal/home/handoff_reveal_test.go
new file mode 100644
index 0000000..28bf21c
--- /dev/null
+++ b/internal/home/handoff_reveal_test.go
@@ -0,0 +1,47 @@
+package home
+
+import (
+ "bytes"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+// TestHandoffInterstitialStaysHiddenWhileItAutoSubmits is spec
+// shared-chrome §9.3: the assertion hand-off page submits itself, so its
+// fallback copy is hidden for 1.5 s and appears only if the submit
+// stalls. Without it every sign-in, and every app switch that needs an
+// exchange, flashes "Continue to Oficina" mid-hop. With scripts off
+// nothing submits, so a <noscript> rule shows the fallback at once.
+func TestHandoffInterstitialStaysHiddenWhileItAutoSubmits(t *testing.T) {
+ app := newTestApp(t)
+ var buf bytes.Buffer
+ if err := app.renderer.pages["assertion.html"].ExecuteTemplate(&buf, "layout", map[string]any{"Action": "https://blue.docs.oficina.works/auth/assertion", "Token": "sample-token"}); err != nil {
+ t.Fatal(err)
+ }
+ page := buf.String()
+ head, body, ok := strings.Cut(page, "</head>")
+ if !ok {
+ t.Fatal("no head")
+ }
+ compact := regexp.MustCompile(`\s+`).ReplaceAllString(head, "")
+ for _, want := range []string{
+ "[estilo-handoff]{animation:estilo-reveal0s1.5sboth;}",
+ "@keyframesestilo-reveal{from{visibility:hidden}to{visibility:visible}}",
+ "<noscript><style>[estilo-handoff]{animation:none;}</style></noscript>",
+ } {
+ if !strings.Contains(compact, want) {
+ t.Fatalf("head lacks %q:\n%s", want, head)
+ }
+ }
+ // Everything the person could read or press is inside the hidden
+ // wrapper, and the form the script submits is still the one there.
+ main := body[strings.Index(body, "<main"):strings.Index(body, "</main>")]
+ inner := strings.TrimSpace(main[strings.Index(main, ">")+1:])
+ if !strings.HasPrefix(inner, "<div estilo-handoff>") || !strings.HasSuffix(inner, "</div>") {
+ t.Fatalf("content not wrapped:\n%s", main)
+ }
+ if !strings.Contains(inner, `data-assertion-handoff`) || !strings.Contains(inner, `<button type="submit" rst-btn="primary">Continue</button>`) {
+ t.Fatalf("hand-off form changed:\n%s", inner)
+ }
+}
diff --git a/internal/home/templates/assertion.html b/internal/home/templates/assertion.html
index c70e695..6b1e5e6 100644
--- a/internal/home/templates/assertion.html
+++ b/internal/home/templates/assertion.html
@@ -1,10 +1,24 @@
{{define "title"}}Continue to Oficina · Oficina{{end}}
+{{define "head"}}
+{{/* The page submits itself (static/assertion.js), so its fallback copy
+ stays hidden for 1.5 s and shows only if the submit stalls. With
+ scripts off nothing submits, so noscript shows it at once. Spec
+ shared-chrome §9.3; the rule moves to estilo chrome.css when the
+ id.* pages adopt the shared chrome. */}}
+<style>
+[estilo-handoff] { animation: estilo-reveal 0s 1.5s both; }
+@keyframes estilo-reveal { from { visibility: hidden } to { visibility: visible } }
+</style>
+<noscript><style>[estilo-handoff] { animation: none; }</style></noscript>
+{{end}}
{{define "content"}}
+<div estilo-handoff>
<h1>Continue to Oficina</h1>
<p>Select Continue if this page does not move on.</p>
<form method="post" action="{{.Action}}" rst-form data-assertion-handoff>
<input type="hidden" name="assertion" value="{{.Token}}">
<button type="submit" rst-btn="primary">Continue</button>
</form>
+</div>
{{end}}
{{define "scripts"}}<script src="/static/assertion.js" defer></script>{{end}}