Skip to content

Handbook: integrated task-overview section from a private CSV (mounted via DFXServer/server) #662

@TaprootFreak

Description

@TaprootFreak

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

  1. 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).
  2. 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
  3. 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:

  1. 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>
  2. 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).
  3. 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

  • handbook.realunit.app/de/ AND dev-handbook.realunit.app/de/ show an integrated "Aufgaben" section (nav entry A) rendering the CSV as a styled table in the handbook look.
  • The task data exists only in DFXServer/server (tasks.csv, both hosts); RealUnitCH/app contains no task data, only the section markup + renderer.
  • Editing tasks.csv + redeploy updates the table with no image rebuild.
  • Links column renders as clickable links; commas inside quoted fields render correctly.
  • With no CSV mounted (CI image build), the page loads and shows the fallback notice; handbook-build-check.yaml stays green.
  • DEV and PRD are identical (same mount, same file).

Validation

  1. 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).
  2. curl -u <user>:<pass> https://dev-handbook.realunit.app/de/tasks.csv returns the CSV; the /de/ page renders the table.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions