oficina / memoria Public

Speak the suite's sign-in door copy in Memoria

The door's button reads 'Sign in'; sent and signed-out pages come from
estilo; titles read '<Page> · Memoria' (was 'Sign in — Memoria'); the
email subject is 'Sign in to Memoria'.
Reviewed by GPT-6-Astra via Codex (#22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Squashed from chore/door-copy into main.
Paul Campbell pushed by claude agent, operated by paul@keymail.dev e19e40b117eaa1d6db04c0a91cd0156349461894 parent 0cc9d09
9 files changed, +36 −41
  • go.mod +1 −1
  • go.sum +2 −2
  • internal/memoria/auth.go +1 −1
  • internal/memoria/handlers.go +13 −23
  • internal/memoria/home_render.go +0 −2
  • internal/memoria/home_router_test.go +3 −2
  • internal/memoria/return.go +6 −9
  • internal/memoriatest/signin_door_test.go +4 −1
  • internal/memoriatest/signinoutcome_test.go +6 −0

CI passed — run details

CI log
=== vet ===
go vet ./...
=== fmt ===
=== test ===
go test ./...
?   	amadan.net/oficina/memoria/cmd/memoria	[no test files]
ok  	amadan.net/oficina/memoria/internal/memoria	3.706s
ok  	amadan.net/oficina/memoria/internal/memoria/chunk	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/fact	(cached)
?   	amadan.net/oficina/memoria/internal/memoria/icons	[no test files]
ok  	amadan.net/oficina/memoria/internal/memoria/index	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/notion	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/provider	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/query	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/seal	(cached)
ok  	amadan.net/oficina/memoria/internal/memoria/sibling	(cached)
ok  	amadan.net/oficina/memoria/internal/memoriatest	1.814s
?   	amadan.net/oficina/memoria/scripts/writeestilo	[no test files]
?   	amadan.net/oficina/memoria/tools/bench	[no test files]
?   	amadan.net/oficina/memoria/tools/drive	[no test files]
?   	amadan.net/oficina/memoria/tools/seed	[no test files]
?   	amadan.net/oficina/memoria/tools/vendorassets	[no test files]
=== migration-check ===
go run github.com/carlosframework/rastrillo/cmd/rastrillo migration check
rastrillo migration check: models and migrations agree
diff --git a/go.mod b/go.mod
index 0dfacdb..326bdca 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.26
toolchain go1.26.5
require (
- amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0
+ amadan.net/oficina/estilo.git v0.0.0-20261001084202-0b4d92c3dc16
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489
amadan.net/oficina/suite v0.0.0-20260905215529-60b01a8a0d6d
diff --git a/go.sum b/go.sum
index 945bb9f..b33184b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0 h1:Qww+xGIYJaj+6TIX4Knr71ghxeYJjtgFm6WukX6NkGg=
-amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
+amadan.net/oficina/estilo.git v0.0.0-20261001084202-0b4d92c3dc16 h1:F5v3x25dEj1rJNnBm71G7shN+fM+T2actUlaGPvoeH8=
+amadan.net/oficina/estilo.git v0.0.0-20261001084202-0b4d92c3dc16/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9 h1:K/PaOVdQqRMwifPD6ipiwLeGR5nbFSxm9FxjrDJlZcg=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM=
diff --git a/internal/memoria/auth.go b/internal/memoria/auth.go
index 453b0dd..b09f2eb 100644
--- a/internal/memoria/auth.go
+++ b/internal/memoria/auth.go
@@ -216,7 +216,7 @@ func NewAuth(sqlDB *sql.DB, env AuthEnv, logger *slog.Logger, authorize func(str
Authorize: authorize,
SigninPath: signinPath,
SignedInPath: landing,
- Subject: "Sign in to Memoria",
+ Subject: chrome.EmailSubject("Memoria"),
Logger: logger,
})
if err != nil {
diff --git a/internal/memoria/handlers.go b/internal/memoria/handlers.go
index d8a7d10..408d4d2 100644
--- a/internal/memoria/handlers.go
+++ b/internal/memoria/handlers.go
@@ -1170,14 +1170,6 @@ type signinPage struct {
SignedOut *chrome.SignInNotice
}
-// The door's words (spec §9.2). Local mode has no lede: "to continue to
-// Memoria" says nothing on Memoria's own page. "Memoria holds your team's
-// work…" is retired with it.
-const (
- signinHelp = "We’ll email you a link that signs you in. It works once and expires in 15 minutes."
- signinSentBody = "If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes."
-)
-
// localSignin serves the sign-in page: GET /signin standalone, and in Home
// mode the local-address form (/signin/local, and /signin whenever
// connect hands an outcome back: ?sent, ?err, ?force).
@@ -1248,25 +1240,21 @@ func (a *app) localSignin(w http.ResponseWriter, r *http.Request) {
if remembered, ok := a.rememberedReturn(r); ok {
again += "?return_to=" + url.QueryEscape(remembered)
}
+ // The door's words; again is where "Use a different address" goes.
+ sent := chrome.SignInNotice{Icon: icon, App: appName, Sky: view}.Sent(again)
render(w, "signin", signinPage{
- page: page{Frame: a.frameAs(r, viewer{}, frame{parts: []string{"Check your email"}, signinPage: true}), ModelOn: a.modelOn()},
- Sent: &chrome.SignInNotice{
- Icon: icon, App: appName, Sky: view,
- Heading: "Check your email", Body: signinSentBody,
- Link: &chrome.Link{Href: again, Label: "Use a different address"},
- },
+ page: page{Frame: a.frameAs(r, viewer{}, frame{parts: []string{sent.Page()}, signinPage: true}), ModelOn: a.modelOn()},
+ Sent: &sent,
})
return
}
+ // The door supplies its own heading, label, help and submit; a plain
+ // visit carries no lede.
door := &chrome.SignIn{
Icon: icon, App: appName, Sky: view,
- Heading: "Sign in",
- Action: "/auth/begin",
- AddressLabel: "Email address",
- Help: signinHelp,
- Submit: "Sign in", // Paul, 2026-10-01: the door says "Sign in"
- OpenSource: &chrome.Link{Href: "/help/open-source", Label: "Open source"},
+ Action: "/auth/begin",
+ OpenSource: &chrome.Link{Href: "/help/open-source", Label: "Open source"},
}
if hasReturn {
door.Return = ret
@@ -1277,8 +1265,9 @@ func (a *app) localSignin(w http.ResponseWriter, r *http.Request) {
switch kind {
case "":
case "warn":
- // ?reauth=1. Not an error, so not the door's negative callout: it
- // stands where the lede would, and says why the form is back.
+ // ?reauth=1. Not an error, so not the door's negative callout:
+ // it stands in the lede, and says why the form is back. The one
+ // lede this door carries: a state, never an introduction.
door.Lede = msg
case "field":
// An address that is not one belongs to the field, not a banner.
@@ -1308,7 +1297,8 @@ func (a *app) localSignin(w http.ResponseWriter, r *http.Request) {
// session was just ended, so it tells nobody else anything.)
func signinOutcome(q url.Values) (msg, kind string) {
if q.Get("sent") != "" {
- return signinSentBody, "good"
+ // No sentence: the door's check-your-email state says it.
+ return "", "good"
}
switch q.Get("err") {
case "":
diff --git a/internal/memoria/home_render.go b/internal/memoria/home_render.go
index 6376512..8867326 100644
--- a/internal/memoria/home_render.go
+++ b/internal/memoria/home_render.go
@@ -49,8 +49,6 @@ var homeCopy = func() map[string]string {
out["unknown_title"] = "Oficina account not connected"
out["unknown_body"] = "Sign in to your existing account, then connect your Oficina account from the account menu."
out["success_body"] = "You can now use your Oficina account to sign in to this account."
- out["signedout_body"] = "You’ve signed out of Memoria on this device."
- out["signedout_action"] = "Sign in"
out["documents_action"] = "Back to Memoria"
return out
}()
diff --git a/internal/memoria/home_router_test.go b/internal/memoria/home_router_test.go
index 8c9e16b..9fdcfbf 100644
--- a/internal/memoria/home_router_test.go
+++ b/internal/memoria/home_router_test.go
@@ -424,13 +424,14 @@ func TestSignedOutIsTheDoor(t *testing.T) {
for _, want := range []string{
"<div estilo-door", "estilo-sky-layer", "<section estilo-signin", "<span>Memoria</span>",
"/static/chrome.", "/static/sky.", "<title>Signed out · Memoria</title>",
- "You’re signed out", "You’ve signed out of Memoria on this device.", `href="/signin"`, ">Sign in<",
+ "<h1>You’re signed out</h1>", "You’ve signed out of Memoria on this device.", `href="/signin"`, ">Sign in<",
} {
if !strings.Contains(b, want) {
t.Errorf("/signed-out lacks %q", want)
}
}
+ // Home is never named to people (Paul, 2026-09-23).
if strings.Contains(b, "Home") {
- t.Error("/signed-out names Home")
+ t.Errorf("/signed-out names Home:\n%s", b)
}
}
diff --git a/internal/memoria/return.go b/internal/memoria/return.go
index b4d3b39..889dfc9 100644
--- a/internal/memoria/return.go
+++ b/internal/memoria/return.go
@@ -221,17 +221,14 @@ func signedOutAfter(signout http.HandlerFunc) http.HandlerFunc {
}
}
-// signedOut serves GET /signed-out: "You’re signed out", and a primary
-// Sign in. The same page standalone and with Home.
+// signedOut serves GET /signed-out: the door's signed-out page ("You’re
+// signed out", and a primary Sign in), in the door's words. The same page
+// standalone and with Home, which is never named.
func (a *app) signedOut(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
+ out := chrome.SignInNotice{Icon: assets.Path("static/brand/icon.svg"), App: appName, Sky: sky.For(r.Host)}.SignedOut(signinPath)
render(w, "signin", signinPage{
- page: page{Frame: a.frameAs(r, viewer{}, frame{parts: []string{"Signed out"}, signinPage: true}), ModelOn: a.modelOn()},
- SignedOut: &chrome.SignInNotice{
- Icon: assets.Path("static/brand/icon.svg"), App: appName, Sky: sky.For(r.Host),
- Heading: "You’re signed out",
- Body: "You’ve signed out of " + appName + " on this device.",
- Link: &chrome.Link{Href: signinPath, Label: "Sign in"},
- },
+ page: page{Frame: a.frameAs(r, viewer{}, frame{parts: []string{out.Page()}, signinPage: true}), ModelOn: a.modelOn()},
+ SignedOut: &out,
})
}
diff --git a/internal/memoriatest/signin_door_test.go b/internal/memoriatest/signin_door_test.go
index 1aceb81..3fdbb10 100644
--- a/internal/memoriatest/signin_door_test.go
+++ b/internal/memoriatest/signin_door_test.go
@@ -47,6 +47,8 @@ func TestSigninPageIsTheDoor(t *testing.T) {
`action="/auth/begin"`,
// Paul, 2026-10-01: the door's button says "Sign in".
`>Sign in</button>`,
+ // The door's tab title (estilo chrome.Title).
+ `<title>Sign in · Memoria</title>`,
} {
if !strings.Contains(html.UnescapeString(body), html.UnescapeString(want)) {
t.Errorf("the sign-in page lacks %q", want)
@@ -56,7 +58,8 @@ func TestSigninPageIsTheDoor(t *testing.T) {
// "Memoria holds your team's work…", and the
// footer's egress claim, which is about searching and is said only to
// somebody signed in.
- for _, gone := range []string{"Email me a link", "holds your team", "<footer", "leaves this instance"} {
+ // And the old title, "Sign in — Memoria".
+ for _, gone := range []string{"Email me a link", "holds your team", "<footer", "leaves this instance", "Sign in — Memoria"} {
if strings.Contains(body, gone) {
t.Errorf("the sign-in page still carries %q", gone)
}
diff --git a/internal/memoriatest/signinoutcome_test.go b/internal/memoriatest/signinoutcome_test.go
index d655034..3676db5 100644
--- a/internal/memoriatest/signinoutcome_test.go
+++ b/internal/memoriatest/signinoutcome_test.go
@@ -82,6 +82,12 @@ func TestSigninPageSaysWhatHappened(t *testing.T) {
if !strings.Contains(body, "<h1>Check your email</h1>") {
t.Error("the sent notice does not say check your email")
}
+ if !strings.Contains(body, "<title>Check your email · Memoria</title>") {
+ t.Error("the sent page's tab title is not \"Check your email · Memoria\"")
+ }
+ if !strings.Contains(body, `<a href="/signin">Use a different address</a>`) {
+ t.Errorf("the sent notice does not link back to the form as \"Use a different address\":\n%s", body)
+ }
if strings.Contains(body, `action="/auth/begin"`) {
t.Error("the sign-in form is still on the page after a link was sent")
}