Goal
Add a fully-integrated "Aufgaben" (task overview) section to the RealUnit handbook (handbook.realunit.app/de/) whose data lives only in the private DFXServer/server repo, never in RealUnitCH/app. The handbook image stays generic; the task data is supplied at runtime by a single CSV file that is mounted into the nginx container from the server repo.
Rationale: the task overview contains internal project data (stakeholder names, internal status, PR/issue references). That content must not be committed into RealUnitCH/app. DFXServer/server is private and already controls what files land on dfxdev/dfxprd, so the data file lives there and is mounted in.
Design decisions (already made — implement as specified)
- Data/presentation split. The data is a CSV (pure tabular text, trivial to edit, one row per task). The presentation (table layout, status badges, link formatting) lives in the handbook (
RealUnitCH/app) as a small client-side renderer. Changing the look never touches the CSV; changing the data never touches the handbook.
- No HTML fragment, no SSI. An earlier idea (server-side include of an HTML fragment) was rejected: it mixes data with markup and needs an nginx config change. CSV + a tiny
fetch()-and-render script is cleaner and needs no nginx change.
- DEV and PRD are kept consistent. Every change below is applied to both
dfxdev and dfxprd — same mount, same file. Do not implement only one environment.
- One file holds everything. No database. The entire overview is one CSV.
Architecture / data flow
DFXServer/server (private) RealUnitCH/app (handbook image)
infrastructure/<host>/realunit-app-handbook/
tasks.csv ──(deploy-infrastructure.yml docs/handbook/de/index.html
rsyncs the service dir to └─ new "Aufgaben" <section> + nav entry
$HOME/realunit-app-handbook └─ inline <script>: fetch('tasks.csv')
on dfxdev/dfxprd) → parse CSV → render <table>
docker-compose.yaml
volumes:
./tasks.csv → /usr/share/nginx/html/de/tasks.csv:ro
│
▼
nginx serves GET /de/tasks.csv (behind existing Basic-Auth)
▲
the handbook page's fetch('tasks.csv') reads it same-origin
(browser replays the Basic-Auth session — no extra auth handling)
The CSV file is not baked into the handbook image. If the mount is absent (e.g. local docker build with no volume), fetch('tasks.csv') returns 404 and the renderer shows a graceful fallback message — see "Fallback".
Existing infrastructure (verified, for context)
- Handbook is the nginx image
dfxswiss/realunit-app-handbook:beta, built from RealUnitCH/app/Dockerfile.handbook, running on dfxdev + dfxprd (container port 8080 → host 6130), behind HTTP Basic-Auth (handbook.nginx.conf), exposed via Cloudflare Tunnel at handbook.realunit.app (PRD) / dev-handbook.realunit.app (DEV).
- nginx serves
/de/... via try_files $uri $uri/ =404; static files under the doc root are served directly. The page is docs/handbook/de/index.html.
- nginx currently sets no Content-Security-Policy, so an inline
<script> in the handbook page is allowed. (If a CSP is added later, it must permit the inline renderer.)
DFXServer/server: infrastructure/{dfxdev,dfxprd}/realunit-app-handbook/docker-compose.yaml defines the service (currently no volumes: key). .github/workflows/deploy-infrastructure.yml rsyncs each infrastructure/<host>/<service>/ directory to the server (excluding .env), then runs docker compose up. So a new file placed next to the compose file is rsynced to $HOME/realunit-app-handbook/ on the host and is available to mount.
- nginx serving
.csv: fetch().text() does not care about the response Content-Type, so no mime.types/nginx change is required. (Only relevant if someone opens the CSV directly in a browser; not a goal here.)
Part A — DFXServer/server (private data + mount) — DEV and PRD
- Add
infrastructure/dfxdev/realunit-app-handbook/tasks.csv and infrastructure/dfxprd/realunit-app-handbook/tasks.csv with the task data (schema below). The two files are identical (DEV/PRD parity).
- In both
docker-compose.yaml files, add a read-only bind mount to the realunit-app-handbook service:
services:
realunit-app-handbook:
# ...existing keys...
volumes:
- ./tasks.csv:/usr/share/nginx/html/de/tasks.csv:ro
- Deploy follows the normal path: merge →
deploy-infrastructure.yml rsyncs the dir + docker compose up -d. Because it's a bind mount, updating the data later is just editing tasks.csv and redeploying — no image rebuild.
Initial data: populate tasks.csv with the current handbook task overview (the live planning list). That content is internal and is intentionally NOT reproduced in this issue — the maintainer will provide the initial CSV (or it is created directly in the private server repo). Do not copy task data into RealUnitCH/app.
Part B — RealUnitCH/app (presentation only — no task data)
Edit only docs/handbook/de/index.html:
- Nav entry — after the existing appendix entries (
M mails, S store-listing), add:
<a href="#spec-tasks"><span class="spec-num">A</span>Aufgaben</a>
- Section — add a new
<details id="spec-tasks" class="spec"> consistent with the existing spec styling, containing:
- a short intro line explaining the data is the live task overview, sourced from the private server repo (one sentence, no task data);
- a container element
<div id="tasks-table"></div>;
- an inline
<script> (the renderer, below).
- No CSV, no task data, no nginx change in this repo. The only addition is the section markup + the renderer script + any CSS for the table/badges (reuse existing handbook CSS variables where possible).
Renderer behaviour (inline <script>)
- On load,
fetch('tasks.csv') (relative URL → resolves to /de/tasks.csv, same-origin, Basic-Auth replayed automatically).
- Parse CSV with a small RFC-4180-aware parser (must handle quoted fields containing commas, escaped
"" quotes, and \n/\r\n line endings). Do not split naively on ,.
- First row = header → render as
<th>. Remaining rows → <tr>/<td>.
- The Links column may contain one or more whitespace-separated URLs; render each as an
<a href target="_blank" rel="noopener"> showing a short label (e.g. the last path segment like #659). Other columns render as text (emojis like ✅/🔄/⬜ pass through as UTF-8).
- HTML-escape every CSV cell value before inserting (defense against markup in the data).
- Render the result into
#tasks-table as a styled <table> matching the handbook look.
Fallback / error handling
fetch 404 / network error / empty body → render a neutral notice in #tasks-table, e.g. "Aufgabenübersicht wird auf dem Server bereitgestellt (kein Mount in dieser Umgebung)." — never throw an uncaught error, never leave the section blank.
- This keeps the PR-gate build green:
handbook-build-check.yaml builds the image with no CSV mounted, the page loads, the renderer shows the fallback, and the existing smoke test (which only checks /healthz, /de/ auth wall, and screenshots) is unaffected.
CSV schema
-
UTF-8, RFC-4180 CSV (comma-separated, fields with commas/quotes wrapped in "...", embedded " doubled as "").
-
First line is the header. Suggested columns (final wording is the maintainer's to set in the data file):
| Column |
Meaning |
Kategorie |
grouping (e.g. status bucket / phase) |
Task |
short task description |
Code |
code/dev status (free text or emoji) |
Test |
test status |
Gemeldet |
reported-to-stakeholder status |
Links |
space-separated PR/issue URLs |
-
Generic example (illustrative only — NOT real data):
Kategorie,Task,Code,Test,Gemeldet,Links
In Arbeit,"Beispiel-Feature A",✅,🔄,⬜,https://github.com/org/repo/pull/123
Offen,"Beispiel-Feature B, mit Komma im Text",📋,⬜,⬜,
Acceptance criteria
Validation
- Server repo: after merge, confirm
tasks.csv rsynced to $HOME/realunit-app-handbook/ on both hosts and the container has it at /usr/share/nginx/html/de/tasks.csv (docker exec ... ls).
curl -u <user>:<pass> https://dev-handbook.realunit.app/de/tasks.csv returns the CSV; the /de/ page renders the table.
- Edit a CSV row, redeploy, confirm the table updates without an image rebuild.
Branch / workflow
RealUnitCH/app: feature branch → PR against staging (per CONTRIBUTING.md), open as Draft, 3-subagent review loop, watch handbook-build-check.
DFXServer/server: follow that repo's convention for the compose + data change (confirm with maintainer whether direct-to-develop or a PR is expected).
- Coordinate ordering: the app-repo renderer ships the fallback, so it can merge independently of the server-repo mount; the section goes "live with data" once the server-repo mount is deployed. Keep DEV and PRD in lockstep.
Open points
- Final column set / wording lives in the data file, not here.
- If the handbook later gains a Content-Security-Policy, the inline renderer must be allowed (or moved to a served
.js file in the app repo — still no task data there).
Goal
Add a fully-integrated "Aufgaben" (task overview) section to the RealUnit handbook (
handbook.realunit.app/de/) whose data lives only in the privateDFXServer/serverrepo, never inRealUnitCH/app. The handbook image stays generic; the task data is supplied at runtime by a single CSV file that is mounted into the nginx container from the server repo.Rationale: the task overview contains internal project data (stakeholder names, internal status, PR/issue references). That content must not be committed into
RealUnitCH/app.DFXServer/serveris private and already controls what files land on dfxdev/dfxprd, so the data file lives there and is mounted in.Design decisions (already made — implement as specified)
RealUnitCH/app) as a small client-side renderer. Changing the look never touches the CSV; changing the data never touches the handbook.fetch()-and-render script is cleaner and needs no nginx change.dfxdevanddfxprd— same mount, same file. Do not implement only one environment.Architecture / data flow
The CSV file is not baked into the handbook image. If the mount is absent (e.g. local
docker buildwith no volume),fetch('tasks.csv')returns 404 and the renderer shows a graceful fallback message — see "Fallback".Existing infrastructure (verified, for context)
dfxswiss/realunit-app-handbook:beta, built fromRealUnitCH/app/Dockerfile.handbook, running on dfxdev + dfxprd (container port 8080 → host 6130), behind HTTP Basic-Auth (handbook.nginx.conf), exposed via Cloudflare Tunnel athandbook.realunit.app(PRD) /dev-handbook.realunit.app(DEV)./de/...viatry_files $uri $uri/ =404; static files under the doc root are served directly. The page isdocs/handbook/de/index.html.<script>in the handbook page is allowed. (If a CSP is added later, it must permit the inline renderer.)DFXServer/server:infrastructure/{dfxdev,dfxprd}/realunit-app-handbook/docker-compose.yamldefines the service (currently novolumes:key)..github/workflows/deploy-infrastructure.ymlrsyncs eachinfrastructure/<host>/<service>/directory to the server (excluding.env), then runsdocker compose up. So a new file placed next to the compose file is rsynced to$HOME/realunit-app-handbook/on the host and is available to mount..csv:fetch().text()does not care about the response Content-Type, so nomime.types/nginx change is required. (Only relevant if someone opens the CSV directly in a browser; not a goal here.)Part A —
DFXServer/server(private data + mount) — DEV and PRDinfrastructure/dfxdev/realunit-app-handbook/tasks.csvandinfrastructure/dfxprd/realunit-app-handbook/tasks.csvwith the task data (schema below). The two files are identical (DEV/PRD parity).docker-compose.yamlfiles, add a read-only bind mount to therealunit-app-handbookservice:deploy-infrastructure.ymlrsyncs the dir +docker compose up -d. Because it's a bind mount, updating the data later is just editingtasks.csvand redeploying — no image rebuild.Part B —
RealUnitCH/app(presentation only — no task data)Edit only
docs/handbook/de/index.html:Mmails,Sstore-listing), add:<details id="spec-tasks" class="spec">consistent with the existingspecstyling, containing:<div id="tasks-table"></div>;<script>(the renderer, below).Renderer behaviour (inline
<script>)fetch('tasks.csv')(relative URL → resolves to/de/tasks.csv, same-origin, Basic-Auth replayed automatically).""quotes, and\n/\r\nline endings). Do not split naively on,.<th>. Remaining rows →<tr>/<td>.<a href target="_blank" rel="noopener">showing a short label (e.g. the last path segment like#659). Other columns render as text (emojis like ✅/🔄/⬜ pass through as UTF-8).#tasks-tableas a styled<table>matching the handbook look.Fallback / error handling
fetch404 / network error / empty body → render a neutral notice in#tasks-table, e.g. "Aufgabenübersicht wird auf dem Server bereitgestellt (kein Mount in dieser Umgebung)." — never throw an uncaught error, never leave the section blank.handbook-build-check.yamlbuilds the image with no CSV mounted, the page loads, the renderer shows the fallback, and the existing smoke test (which only checks/healthz,/de/auth wall, and screenshots) is unaffected.CSV schema
UTF-8, RFC-4180 CSV (comma-separated, fields with commas/quotes wrapped in
"...", embedded"doubled as"").First line is the header. Suggested columns (final wording is the maintainer's to set in the data file):
KategorieTaskCodeTestGemeldetLinksGeneric example (illustrative only — NOT real data):
Acceptance criteria
handbook.realunit.app/de/ANDdev-handbook.realunit.app/de/show an integrated "Aufgaben" section (nav entryA) rendering the CSV as a styled table in the handbook look.DFXServer/server(tasks.csv, both hosts);RealUnitCH/appcontains no task data, only the section markup + renderer.tasks.csv+ redeploy updates the table with no image rebuild.handbook-build-check.yamlstays green.Validation
tasks.csvrsynced to$HOME/realunit-app-handbook/on both hosts and the container has it at/usr/share/nginx/html/de/tasks.csv(docker exec ... ls).curl -u <user>:<pass> https://dev-handbook.realunit.app/de/tasks.csvreturns the CSV; the/de/page renders the table.Branch / workflow
RealUnitCH/app: feature branch → PR againststaging(per CONTRIBUTING.md), open as Draft, 3-subagent review loop, watchhandbook-build-check.DFXServer/server: follow that repo's convention for the compose + data change (confirm with maintainer whether direct-to-developor a PR is expected).Open points
.jsfile in the app repo — still no task data there).