T-HUB/infra/occupymedia/element/AUTH-RESTYLE.md
glass beadian b89dea5c93 feat: per-connection publish governance — auto, 1-admin, majority, consensus
Per the owner's spec: each social connection gets publishGovernance in its
fields — unset = manual publish (today), auto = dispatch on approval,
admin1/admin_majority/admin_consensus = a publish-phase chatgov poll
(subject draft_publish, its own open-poll slot, no collision with review
gates). Dispatch holds until the publish poll passes (E3 pre-dispatch
check); auto dispatches on approval. Includes migration 033, the PATCH
/connections/:id/publish-governance route (chatgov.manage), and 19+31 new
tests. Implementation by the branding-panel process (claude/opus), lint
and integration fixes here.
2026-10-14 08:04:56 +00:00

13 KiB

Chat sign-in restyle and deployment report

Repo implementation only; no production connection, deployment, or git commit was made. The server-owned /opt/occ-element/config.json is not in this repo. The supplied patch merges into that file rather than replacing its configuration.

Signed-out visitors opening chat.occupymedia.cc will see a small Hub mark and wordmark in the top-left, a centered card containing Sign in, username and password fields, and a red submit button. Password recovery and account creation remain available as quiet links. Registration uses the same compact card. The background is #060403, the card is #0F0A09, and the font is system-ui. The mark is capped at 32px; there is no logo sidebar, background artwork, or hero. Long forms and errors scroll on small screens rather than overlapping the brand. Element's footer remains small and muted.

Verified Element Web 1.12.30 behavior

No Element source or config template was found in the repository or its installed dependencies. Research used the official v1.12.30 source archive and the matching ghcr.io/element-hq/element-web:v1.12.30 image, in temporary local files.

Configuration Effect in 1.12.30
branding.auth_header_logo_url Image source in login/registration headers and the default welcome component. This is nested under branding, not a top-level key.
branding.logo_link_url Link destination where Element actually renders a linked logo, such as default welcome.
auth_header_logo_click_url Does not exist in this release. The login/registration header renders an unlinked image; a config option cannot make that header clickable.
brand App name used in translated text; does not change the header's layout or image.
branding.welcome_background_url Background artwork across auth pages. The injected CSS replaces the inline background with a solid color.
embedded_pages.login_for_welcome Uses the login form for the signed-out landing page, bypassing default/custom welcome content.
default_server_config Selects the homeserver and identity-server connection details; does not control branding or layout. Left intact.
disable_custom_urls Removes server editing. The CSS hides the redundant server label only when the edit control is absent.
disable_3pid_login Removes the username/email/phone selector, leaving username/password.
disable_login_language_selector Removes the language dropdown from the auth header.

See the pinned configuration docs, AuthHeaderLogo source, and DefaultWelcome source.

The structure is .mx_AuthPage → .mx_AuthPage_modal → main.mx_AuthPage_modalContent, with .mx_AuthHeader and .mx_AuthBody as siblings. The large logo comes from .mx_AuthHeaderLogo > img inside a 206px sidebar; the original body is 500px wide. The wrapper also renders a blur layer and .mx_AuthFooter. See AuthPage and upstream auth CSS. There is no .mx_AuthPage_logo in this release's auth structure.

setting_defaults.custom_themes accepts theme colors, fonts and Compound token overrides, not an arbitrary CSS stylesheet URL. source_url records a theme JSON's origin, not a CSS file. The injected link intentionally has no data-mx-theme, so Element's theme switching will not disable it. See the theming docs and theme loader/types.

On first visits from iOS/Android, Element redirects a bare / to /mobile_guide/ before loading config. Both chat vhosts redirect that app-download page to /#/login, which bypasses the mobile redirect. Disabling mobile_guide_toast also removes the later mobile-app prompt. See the pinned startup source.

Files changed

File Purpose
../edge/hub-chat-auth.css Scoped auth overrides: corner branding, compact responsive card, warm colors, focus styles and red primary actions. Uses stable mx_* classes, structural selectors and Compound data-kind, with no hashed class selectors.
../edge/nginx.conf Both HTTP tunnel and HTTPS chat vhosts serve /hub-chat-auth.css and inject its link once before </head> in HTML. Clears upstream Accept-Encoding so sub_filter can read HTML. Routes the mobile guide to login. Matrix API and SSO helper keep their own locations.
../edge/docker-compose.edge.yml Read-only bind mount for the stylesheet.
auth-branding.patch.json Hub logo URL, supported logo-link setting, direct login landing and removal of extra login selectors.
apply-auth-branding.py Merge/preview utility. Preserves unrelated settings and writes in place to retain Docker's single-file bind mount.
../edge/README.md Links this report and corrects the documented chat routing.
AUTH-RESTYLE.md This research and deployment report.

The stylesheet is downloaded with the SPA shell; nginx cannot see hash routes. It applies only within .mx_AuthPage, including recovery/auth verification screens using that wrapper. Signed-in room UI keeps the existing custom theme. The element.occupymedia.cc alias vhost is not changed.

Exact deployment steps (operator runs these; not executed here)

Use the existing CT 150 checkout at /opt/anarchoposter. Run server commands with permission to edit /opt/occ-element/config.json and manage Docker.

  1. On the server, before copying files, back up the config and edge files. Keep this shell open so occ_auth_backup remains available for later steps.

    occ_auth_backup="/opt/occ-element/backups/hub-auth-$(date -u +%Y%m%dT%H%M%SZ)"
    mkdir -p "$occ_auth_backup"
    cp -p /opt/occ-element/config.json "$occ_auth_backup/config.json"
    cp -p /opt/anarchoposter/infra/occupymedia/edge/nginx.conf "$occ_auth_backup/nginx.conf"
    cp -p /opt/anarchoposter/infra/occupymedia/edge/docker-compose.edge.yml "$occ_auth_backup/docker-compose.edge.yml"
    if test -f /opt/anarchoposter/infra/occupymedia/edge/hub-chat-auth.css; then
      cp -p /opt/anarchoposter/infra/occupymedia/edge/hub-chat-auth.css "$occ_auth_backup/hub-chat-auth.css"
    fi
    printf 'Backup: %s\n' "$occ_auth_backup"
    
  2. From this workstation's repo root, copy only the reviewed infra files. Set OCC_EDGE_SSH to your existing SSH destination for CT 150; no hostname or credentials are assumed here.

    OCC_EDGE_SSH='YOUR_EXISTING_CT_150_SSH_DESTINATION'
    rsync -aR -- \
      infra/occupymedia/edge/nginx.conf \
      infra/occupymedia/edge/docker-compose.edge.yml \
      infra/occupymedia/edge/hub-chat-auth.css \
      infra/occupymedia/edge/README.md \
      infra/occupymedia/element/ \
      "$OCC_EDGE_SSH:/opt/anarchoposter/"
    
  3. On the server, validate the candidate edge config with the new mounts, then merge the auth patch and recreate only the edge service:

    cd /opt/anarchoposter/infra/occupymedia/edge
    docker compose -f docker-compose.edge.yml config --quiet
    docker compose -f docker-compose.edge.yml run --rm --no-deps edge nginx -t
    python3 ../element/apply-auth-branding.py /opt/occ-element/config.json > "$occ_auth_backup/config.preview.json"
    diff -u "$occ_auth_backup/config.json" "$occ_auth_backup/config.preview.json"
    python3 ../element/apply-auth-branding.py /opt/occ-element/config.json --write
    docker compose -f docker-compose.edge.yml up -d --no-deps --force-recreate edge
    docker exec occ-edge-1 nginx -t
    

    Run each command only if the preceding validation succeeded; diff exits 1 for the expected changes. Recreating the edge is required for the new CSS mount and ensures it sees copied files even if their inodes changed. Element needs no restart: the merge utility retains the config inode. Its existing setting_defaults.custom_themes, Compound tokens, default_theme, and default_server_config remain intact. Do not replace config.json with the patch JSON or try to register this CSS as a custom theme.

  4. On the server, verify the public HTML, stylesheet and served config:

    curl -fsS --compressed https://chat.occupymedia.cc/ -o "$occ_auth_backup/index.served.html"
    curl -fsS 'https://chat.occupymedia.cc/hub-chat-auth.css?v=1' -o "$occ_auth_backup/auth.served.css"
    curl -fsS https://chat.occupymedia.cc/config.json -o "$occ_auth_backup/config.served.json"
    cmp hub-chat-auth.css "$occ_auth_backup/auth.served.css"
    python3 - "$occ_auth_backup" <<'PY'
    import json, sys
    from pathlib import Path
    backup = Path(sys.argv[1])
    html = (backup / "index.served.html").read_text()
    assert html.count('href="/hub-chat-auth.css?v=1"') == 1
    old = json.loads((backup / "config.json").read_text())
    served = json.loads((backup / "config.served.json").read_text())
    for key in ("setting_defaults", "default_theme", "default_server_config", "default_server_name"):
        assert served.get(key) == old.get(key), key
    assert served["branding"]["auth_header_logo_url"] == "https://app.occupymedia.cc/brand/logo.svg"
    assert served["branding"]["logo_link_url"] == "https://app.occupymedia.cc"
    assert served["embedded_pages"]["login_for_welcome"] is True
    assert served["disable_3pid_login"] is True
    assert served["disable_custom_urls"] is True
    print("Public HTML injection, stylesheet and preserved theme verified")
    PY
    

    If a CDN serves an older HTML/config response, purge those URLs and retry. In a fresh signed-out browser, open /, /#/login and /#/register, and check the page on a phone as well. Check password errors, keyboard focus, recovery and an existing signed-in room. Browser reloads load the new config. Element also supports domain-specific config files; if this installation starts serving a successful /config.chat.occupymedia.cc.json, apply the same merge to its mounted source because it takes precedence over config.json.

Rollback using the printed backup directory: restore the old nginx and compose files, restore the old config in place, then recreate the edge service with the old compose file:

cp -p "$occ_auth_backup/nginx.conf" /opt/anarchoposter/infra/occupymedia/edge/nginx.conf
cp -p "$occ_auth_backup/docker-compose.edge.yml" /opt/anarchoposter/infra/occupymedia/edge/docker-compose.edge.yml
if test -f "$occ_auth_backup/hub-chat-auth.css"; then
  cp -p "$occ_auth_backup/hub-chat-auth.css" /opt/anarchoposter/infra/occupymedia/edge/hub-chat-auth.css
fi
python3 - "$occ_auth_backup/config.json" /opt/occ-element/config.json <<'PY'
from pathlib import Path
import sys
data = Path(sys.argv[1]).read_bytes()
with Path(sys.argv[2]).open("r+b") as target:
    target.write(data)
    target.truncate()
PY
cd /opt/anarchoposter/infra/occupymedia/edge
docker compose -f docker-compose.edge.yml run --rm --no-deps edge nginx -t
docker compose -f docker-compose.edge.yml up -d --no-deps --force-recreate edge

Local validation

  • Real Element v1.12.30 and nginx:1.27-alpine ran on an isolated local Docker network with temporary certificates and loopback ports; Matrix responses and the Hub asset request were mocked. No live accounts or production services were accessed.
  • Full repo nginx config passed nginx -t; compose configuration parsed. Nginx emits an existing duplicate text/html MIME warning in the unrelated files sign-in location; it does not prevent validation.
  • Both chat HTTP/HTTPS vhosts injected exactly one stylesheet link despite the client advertising gzip. JSON and the SSO helper were unchanged; CSS content and content type matched the repo file.
  • Chromium checked bare / → login, 420px desktop card, 32px corner logo, system-ui, warm background, red submit, two login inputs, mock failed login, visible errors, and registration. Checked widths 320/390/844px including landscape: no horizontal overflow or brand/form overlap. A fresh iPhone user agent reached the form through the mobile-guide redirect.
  • The injected link remained enabled after custom-theme loading. A room DOM without the auth wrapper retained its original button color.
  • Config merge checks confirmed preservation of theme, Compound tokens, homeserver and sibling keys, idempotence, and the original file inode.

Real successful authentication and production cache/routing behavior still need the operator's post-deployment checks. Existing unrelated workspace changes were left alone; no files in apps/api were edited by this task.