# dossiers-ngi — dynamic edition bar + 10‑min rebuild SoT for the sticky edition bar on **dossiers.ngi.hacktivism.ch**. Live freezes sit under `editions//` on koopa; cookie `tdpd_edition` selects the build. The bar renders a **semi-graphic parent→children tree** from `/branch-status.json` (all `origin/*` tips, not a hard-coded ladder). | Path | Role | |------|------| | `sites/…/_cascade/` | Sticky bar shell + JS tree renderer | | `editions/` on koopa www | Full freezes switched by the bar | | `sites/…/cascade/` | Legacy tip stubs (optional) | | `scripts/dossiers-ngi/branch-status-hourly.py` | Fetch remotes → tree JSON → on-demand rebuild | | `scripts/dossiers-ngi/smoke-branch-tree.sh` | Temp-branch smoke (creates/deletes local origin only) | | `configs/systemd/user/dossiers-ngi-branch-status.{timer,service}` | Every **10 min** oneshot on koopa (`flock` against overlap) | ## Env | Var | Default | Meaning | |-----|---------|---------| | `TDPD_ROOT` | `$HOME/git/src/ngi/tech-dossier-project-data` | tdpd git checkout (may be git-dir only) | | `TDPD_VENV` | `(TDPD_ROOT)/.venv` | Frozen-Flask venv; symlinked into worktrees | | `OUT` | kal `sites/…/branch-status.json` | write path (live www on koopa) | | `EDITIONS_ROOT` | (none) | `…/editions` — required for `--rebuild` | | `REBUILD` / `--rebuild` | off | rebuild when `.edition-tip` ≠ tip | | `FORCE_REBUILD` / `--force` | off | rebuild every branch (timing) | | `FETCH` / `--no-fetch` | fetch on | `git fetch --prune origin` | | `FREEZE_CMD` | (make/venv freeze) | stub for smoke tests; `{root}` `{branch}` `{slug}` | | `DEFAULT_BRANCH` | `feat/website-views` | behind cookie `flask` | | `HOST` | `https://dossiers.ngi.hacktivism.ch` | public host in JSON | On koopa: `%h/src/koopa/koopa-admin-log` + `~/.config/dossiers-ngi/branch-status.env` for path overrides. NGI clone token lives in **koopa-admin-secrets-enc** (not this tree). ## Manual run ```bash # JSON only python3 scripts/dossiers-ngi/branch-status-hourly.py # rebuild stale freezes + timing block in JSON EDITIONS_ROOT=/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch/editions \ python3 scripts/dossiers-ngi/branch-status-hourly.py --rebuild # force all branches (wall clock → rebuild_timing) … --rebuild --force ``` `rebuild_timing` fields: `wall_seconds`, `built`, `skipped`, `errors`, `built_seconds_sum`, `built_seconds_max`. Per-branch `rebuild[name].seconds`. Per branch in JSON / sticky bar: | Field | Meaning | |-------|---------| | `tip` | short SHA | | `tip_time` | committer time of tip (`%cI`) | | `built_at` | when this edition freeze was written | | `build_seconds` | wall time of that freeze | | `vs_main_plus` / `vs_main_minus` | `git diff --numstat` tip vs **main** tip (sticky bar +grün/−rot; omitted on `main` itself) | Rebuild runs **only when** `.edition-tip` ≠ current tip (timer never forces). Each run also: `git fetch --prune`, `worktree prune`, drop stale `editions//` (gone remotes), purge leftover `tdpd-*` tmp dirs. ## Smoke ```bash bash scripts/dossiers-ngi/smoke-branch-tree.sh # → SMOKE_OK (temp origin + smoke/temp-sibling deleted) ``` ## Edition freeze missing the bar (cookie trap) `feat/website-views` freeze can omit `{% include "_cascade/bar.html" %}`. Cookie `tdpd_edition` then has no switcher. **No zypper / Caddy plugin.** Way back: `https://dossiers.ngi.hacktivism.ch/_cascade/use/flask` or `/_cascade/` (`index.html` switcher). On koopa (hernani, no root): ```bash bash scripts/dossiers-ngi/inject-editions-bar.sh ``` Hourly rebuild injects on skip **and** freeze-error (keep existing HTML + include). The bar is `position: fixed` (not sticky); `100vw` overflow is avoided. ## Deploy landing (Freigabe) Do **not** `--delete` the whole www root. Sync: ```bash SITE="$HOME/git/admin-logs/local/koopa-admin-log/sites/dossiers.ngi.hacktivism.ch" WWW=hernani@koopa:/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch rsync -avz --exclude .DS_Store "$SITE/_cascade/" "$WWW/_cascade/" rsync -avz --exclude .DS_Store "$SITE/branch-status.json" "$WWW/branch-status.json" # editions/ from a --rebuild run ``` ## Sticky cascade bar `bar.js` fetches `/branch-status.json` and draws: ``` main sep01 16:52 8.4s sep30 15:23 ├── feat/sm-content sep30 16:11 13s sep30 15:23 +N −M │ └── feat/website-features … └── project_template sep28 15:03 2.2s sep30 15:23 +N −M ``` Visible cells: name (tree prefix + branch) · tip `monDD HH:MM` · `Ns monDD HH:MM` (build seconds + freeze time) · `+N −M` vs main. No `@sha`, no `default` label (`data-default` stays for flask/cookie). The bar is **viewport-centered**, inner tree `max-width: min(80rem, 100vw − 1.5rem)` (was 45em at 11px ≈ 495px, which clipped long names). Rear cells are right-aligned `tabular-nums`. Poll every 30s **updates when/built/diff in place** when the branch structure is unchanged (no full tree remount). Each name links to `/_cascade/use/` (cookie + 302 `/`). Caddy map: ``` ~^[A-Za-z0-9_-]+$ {http.request.cookie.tdpd_edition} ``` Also serve `/cascade/*` from the site www root (path stubs). Live Caddy must not use a trailing `~.* flask` catch-all. Edition purity: responses send `Vary: Cookie` and `Cache-Control: private, no-cache` so `/static/css/style.css` (same path, different bodies per edition) cannot stick the green design CSS onto `main` after a views visit. so new branches need no Caddy edit — only an `editions//` tree. ```bash bash ~/bin/apply-dossiers-ngi-edition-switch.sh sudo ~/bin/caddy-apply ``` Stanza SoT: `scripts/dossiers-ngi/caddy-dossiers-ngi.snippet`. Caddy edition map must list each `editions/` explicitly. A map output of `{http.request.cookie.tdpd_edition}` did not expand on Caddy v2.11.4 (only `flask` worked → 404 for `main`/views). After new tips appear, refresh the snippet map (or regenerate from `ls editions`) before `apply-dossiers-ngi-edition-switch` + `caddy-apply`.