oficina / home Public

Complete preview catalog and record bounded navigation exception

Paul Campbell pushed by paul@keymail.dev 822a49b9e9f352284f3b4409eb78b64cfd20d166 parent 3d04033
3 files changed, +17 −2
  • docs/superpowers/specs/2026-08-30-home-design.md +13 −0
  • internal/home/directory_catalog.go +2 −0
  • internal/home/directory_catalog_test.go +2 −2

CI passed — run details

diff --git a/docs/superpowers/specs/2026-08-30-home-design.md b/docs/superpowers/specs/2026-08-30-home-design.md
index bd7b00f..40265f7 100644
--- a/docs/superpowers/specs/2026-08-30-home-design.md
+++ b/docs/superpowers/specs/2026-08-30-home-design.md
@@ -1342,6 +1342,19 @@ registry is not invented for this screen — §4's `<team>.<app>.oficina.works`
has to resolve per app and §7 has to know what can be provisioned, and
without it that knowledge is scattered across templates.
+**Preview navigation exception, 2026-09-09 — approved by Paul for the
+current preview connection.** The directory may read the operator-only
+`HOME_DOCS_PREVIEW_ORIGIN` setting and link the Docs catalog entry to that
+canonical HTTPS origin's fixed `/signin` path, labelled **Open preview**.
+An empty setting keeps the existing `/apps/docs` team chooser. This is an
+opt-in preview launcher, not a team instance or a change to the canonical
+`<team>.<app>.oficina.works` route policy. It does not register a consumer
+or grant access; the destination must already implement its own Home
+handshake. The setting is rejected outside the directory role, for a
+noncanonical origin, or for the credential/directory origins themselves.
+The current catalog is a staged list with explicit unavailable states;
+the full team-scoped grid and its switcher remain to be implemented.
+
**Mail and Files are `integrated: false`** — Keymail and Slopbox are
separate products in separate repositories with their own auth. They render
as plain outbound links, never as team-scoped tiles, until each verifies
diff --git a/internal/home/directory_catalog.go b/internal/home/directory_catalog.go
index 8d95eab..a4f6006 100644
--- a/internal/home/directory_catalog.go
+++ b/internal/home/directory_catalog.go
@@ -18,6 +18,8 @@ func directoryCatalog(previewOrigin string) []catalogApp {
{Name: "Slides", Status: "Not connected to Home yet"},
{Name: "Calendar", Status: "Not connected to Home yet"},
{Name: "Meet", Status: "Coming soon"},
+ {Name: "Mail", Status: "Separate sign-in"},
+ {Name: "Files", Status: "Separate sign-in"},
}
}
diff --git a/internal/home/directory_catalog_test.go b/internal/home/directory_catalog_test.go
index 71bb0fc..f11d7b9 100644
--- a/internal/home/directory_catalog_test.go
+++ b/internal/home/directory_catalog_test.go
@@ -18,7 +18,7 @@ func TestDirectoryCatalogRequiresSigninAndDoesNotInventLaunchDestinations(t *tes
}
noStore(t, page)
body := page.Body.String()
- for _, name := range []string{"Docs", "Sheets", "Slides", "Calendar", "Meet"} {
+ for _, name := range []string{"Docs", "Sheets", "Slides", "Calendar", "Meet", "Mail", "Files"} {
if !strings.Contains(body, ">"+name+"<") {
t.Fatal("missing app", name)
}
@@ -26,7 +26,7 @@ func TestDirectoryCatalogRequiresSigninAndDoesNotInventLaunchDestinations(t *tes
if strings.Count(body, "<a ") != 1 || !strings.Contains(body, `href="/apps/docs"`) || !strings.Contains(body, "Choose a team") || strings.Contains(body, "<script") {
t.Fatal("catalog advertised unsupported launch", body)
}
- if strings.Count(body, "Not connected to Home yet") != 3 || strings.Count(body, "Coming soon") != 1 {
+ if strings.Count(body, "Not connected to Home yet") != 3 || strings.Count(body, "Coming soon") != 1 || strings.Count(body, "Separate sign-in") != 2 {
t.Fatal("catalog readiness missing", body)
}
entry := f.request("GET", docsEntryPath, "", "", cookie)