homepage: app-card art, zealot-first order, common uncensored warning, admin identity gate

- Every app card carries hex-clipped art: 19 amber-tinted brand marks
  from the Simple Icons CDN plus 19 improvised monogram SVGs, wired via
  a per-service "art" field; homepage_config.bats pins art presence for
  all 32 apps.
- Model cards render zealot -> dragoon -> archon (registry order pinned
  in the seam).
- Dragoon and zealot share one common uncensored legal warning (adult
  content, harmful/hateful/illegal descriptions, sensitive offensive
  cybersecurity topics, severe legal risk, "don't ruin this server for
  everybody"), hover label on card and docs.
- Archon gets a bordered red permission callout: ask Frank before use.
- SearXNG obfuscation-not-anonymity note moved to a card-level hover
  warning (.svc-warn) and a docs hover note.
- Gitea: self-service signup flow; no need to ask Frank.
- Footer docs/admin/account-settings become glowing bordered buttons;
  the admin button is shown only to homepage-admins members via a new
  Caddy /whoami endpoint echoing the outpost-injected X-Authentik-*
  headers (spoof-proof; the /admin* 403 stays the real gate). e2e pins
  both sides.
- Docs naming swept: Protoss names primary, bracketed api ids, explicit
  note that a future switchover renames the ids themselves.
This commit is contained in:
Frank committed 2026-10-08 03:21:26 -07:00
1 parent 3c5d77d1fa
commit c1e192df00
44 files changed
+338 -102

No files matched your search

+5 -2
View File
@@ -47,8 +47,11 @@ test("standard user is denied the admin panel (RBAC)", async ({ page }) => {
expect(resp.status()).toBe(403);
expect(await resp.text()).toContain("Forbidden: homepage admins only");
// The footer link a standard user can click also dead-ends at the gate.
// The admin button is identity-gated: /whoami reports the authentik
// groups, and the page hides the button from non-admins (the server-side
// 403 above stays the real gate). Direct navigation dead-ends all the same.
await page.goto("/");
await page.locator('footer a[href="/admin/"]').click();
await expect(page.locator("#admin-link")).toBeHidden();
await page.goto("/admin/");
await expect(page.locator("body")).toContainText("Forbidden: homepage admins only");
});
+4
View File
@@ -4,6 +4,10 @@ import { expect, test } from "@playwright/test";
// allow-leg: the same path returns 403 for the standard user (10-homepage).
test("admin panel renders admin-category services", async ({ page }) => {
// Identity gate (admin side): a homepage-admins member sees the admin
// button on the user homepage (the standard user does not: 10-homepage).
await page.goto("/");
await expect(page.locator("#admin-link")).toBeVisible({ timeout: 60_000 });
await page.goto("/admin/");
await expect(page).toHaveTitle(/frank — admin/, { timeout: 60_000 });
+28
View File
@@ -101,7 +101,35 @@ for s in fleet:
uncensored = "ncensored" in m["spec"] or "bliterated" in m["spec"]
if uncensored:
assert "legal" in m.get("warn", "").lower(), f"{s['name']} is uncensored but has no legal warning"
assert [s["name"] for s in fleet] == ["frank-zealot", "frank-dragoon", "frank-archon"], \
"card render order must be zealot, dragoon, archon: " + str([s["name"] for s in fleet])
w = {s["name"]: s["model"].get("warn", "") for s in fleet}
assert w["frank-zealot"] == w["frank-dragoon"] != "", "the legal warnings must be the common uncensored text"
assert "legal risk" in w["frank-zealot"].lower() and "everybody" in w["frank-zealot"].lower()
archon = next(s for s in fleet if s["name"] == "frank-archon")
assert "permission" in archon["model"], "archon card needs the ask-Frank permission callout"
PY
grep -q "renderModelInfo" "$REPO_ROOT/shared/homepage/app.js"
grep -q ".mi-card" "$REPO_ROOT/shared/homepage/style.css"
}
@test "every app card carries icon art and SearXNG carries the privacy note" {
# The family sees unit-portrait art on every card, not just the models:
# brand marks where Simple Icons has them, improvised monograms otherwise.
# SearXNG's card must warn (hover) that obfuscation is not anonymity.
python3 - "$REPO_ROOT/shared/homepage/services.json" "$REPO_ROOT/shared/homepage" <<'PY'
import json, os, sys
services = json.load(open(sys.argv[1]))
root = sys.argv[2]
apps = [s for s in services if "model" not in s]
for s in apps:
art = s.get("art", "")
assert art.startswith("/img/apps/"), f"{s['name']} has no app art"
assert os.path.isfile(os.path.join(root, art.lstrip("/"))), f"{s['name']} art file missing: {art}"
sx = next(s for s in services if s["name"] == "SearXNG")
assert "anonymous" in sx.get("warn", "").lower(), "SearXNG must warn that searches are obfuscated, not anonymous"
PY
grep -q "app-art" "$REPO_ROOT/shared/homepage/app.js"
grep -q ".app-art" "$REPO_ROOT/shared/homepage/style.css"
grep -q "/whoami" "$REPO_ROOT/dankclaw/Caddyfile"
}
+1 -1
View File
@@ -79,7 +79,7 @@ setup() {
@test "the user docs page exists and is reachable from the service pages" {
[ -f "$BASE/docs.html" ]
grep -q '<a href="/docs">docs</a>' "$BASE/index.html"
grep -q 'class="foot-btn" href="/docs"' "$BASE/index.html"
grep -q 'id="models"' "$BASE/docs.html"
grep -q '<base href="/">' "$BASE/docs.html"
grep -q 'style.css' "$BASE/docs.html"