Compare commits

...

10 Commits

Author SHA1 Message Date
498b7aa9a5 Promote vergabe-teilnahme capability after SCOPE.md fill-in (T05 entry 2).
SCOPE.md was already filled; update the registry entry to D3/A1/C1/R1,
document issue-core and whynot-design relations, and align the index vector
with the authored scope and wiki evidence.
2026-07-07 15:23:17 +02:00
ff95fb3971 Fill in SCOPE.md (was an unfilled template)
Written from direct inspection of the 12-app Django domain model,
wiki/ProductRequirementsDocument.md, and wiki/ArchitectureBlueprint.md.
Requested during reuse-surface REUSE-WP-0017-T05 review follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:26:36 +02:00
d20d21b21a Draft capability entry (reuse-surface REUSE-WP-0017-T04, cohort 3)
Honest first-pass maturity vector grounded in README/docs/tests present
in this repo; no invented evidence. Flagged for human review before
publish. See reuse-surface history/2026-07-06-coverage-classification.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 19:50:54 +02:00
a3cec3f221 Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:29 +02:00
b31409649c Add .repo-classification.yaml (CUST-WP-0050 T11 agent first-pass) 2026-06-22 17:47:43 +02:00
7bd6730744 Add credential routing instructions for all agent runtimes
Propagate shared credential-routing section (Codex, Claude, Grok, llm-connect)
from state-hub template via scripts/propagate_credential_routing.py.
2026-06-18 22:48:40 +02:00
9cb8992486 Add capability registry scaffold (REUSE-WP-0014-T08 B06) 2026-06-16 02:02:54 +02:00
5ae4aba4a8 Lock issue-core from Gitea registry 2026-06-05 20:42:33 +02:00
00469c4cc0 Adopt whynot-design tokens (WP-0017 Phase 1)
Replace vergabe's blue brand-* palette with whynot's near-black/paper/yellow
visual language. Tokens vendored at static/src/vendor/whynot-design/ (synced
from commit 9419f16 via scripts/sync-whynot-design.sh / make sync-whynot-design).

main.css imports the vendored CSS first, exposes ink/paper/hi as Tailwind
@theme tokens (bg-paper, text-ink, border-line, etc.), and re-tones every
component class (.btn-*, .card, .field-row, .phase-*, .form-input, .table-*,
.sidebar-*). Border radii drop to whynot's 0-4px; .card loses its shadow.

Legacy text-brand-* / bg-brand-* / border-brand-* template references are
kept working via @theme aliases that map the old blue scale onto the whynot
ink ramp — Phase 1 is tokens-only, no template churn.

btn-danger keeps an off-spec red (#B22222) as a local --danger var until
upstream defines a canonical destructive color.

base.html body class swapped: bg-slate-50 → bg-paper-2 text-ink.

Phase 2 (component adoption) deferred until whynot-design ships Lit web
components + missing atoms (Card, Modal, Input, Table, Toast). See
wiki/DesignSystem.md and history/2026-05-23-whynot-design-cross-framework-analysis.md.

Verified: 8/8 e2e tests pass; dev server boots; static/dist/main.css contains
no #3b5bdb references. Visual pixel-level verification still pending Bernd's
browser walk.
2026-05-23 21:52:59 +02:00
fbb8def9ce Plan WP-0017: whynot-design adoption (tokens + CSS only)
Add cross-framework analysis to history/ and WP-0017 workplan for the
tokens+CSS phase of adopting ~/whynot-design. Component port deferred until
upstream ships Lit web components and missing atoms (Card, Modal, Input,
Table, Toast).

Decisions captured: vendor (not npm), big-bang swap (not pilot), keep
off-spec red for btn-danger until upstream defines one.
2026-05-23 19:09:18 +02:00
43 changed files with 1772 additions and 179 deletions

View File

@@ -0,0 +1,50 @@
# Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=vergabe-teilnahme` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes**`warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`

View File

@@ -1,11 +1,11 @@
## First Session Protocol ## First Session Protocol
Triggered when `get_domain_summary("vergabe_teilnahme")` shows **no workstreams**. Triggered when `get_domain_summary("communication")` shows **no workstreams**.
The project is registered but work has not yet been structured. The project is registered but work has not yet been structured.
**Step 1 — Read, don't write** **Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/vergabe_teilnahme/project_charter_v0.1.md` — purpose, scope - `~/the-custodian/canon/projects/communication/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/vergabe_teilnahme/roadmap_v0.1.md` — planned phases - `~/the-custodian/canon/projects/communication/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs - Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work** **Step 2 — Survey in-progress work**
@@ -17,20 +17,20 @@ roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)** **Step 4 — Create workplan file first, then DB record (ADR-001)**
``` ```
workplans/vergabe_teilnahme-WP-NNNN-<slug>.md ← write this first workplans/WP-NNNN-<slug>.md ← write this first
``` ```
Then register in the hub: Then register in the hub:
``` ```
create_workstream(topic_id="7d4d3e35-312f-4723-bde9-ddb43799109e", title="...", owner="...", description="...") create_workstream(topic_id="36c7421b-c537-4723-bf75-42a3ebc6a1dc", title="...", owner="...", description="...")
create_task(workstream_id="<id>", title="...", priority="high|medium|low") create_task(workstream_id="<id>", title="...", priority="high|medium|low")
``` ```
**Step 5 — Record the setup** **Step 5 — Record the setup**
``` ```
add_progress_event( add_progress_event(
summary="First session: structured vergabe_teilnahme into N workstreams, M tasks", summary="First session: structured communication into N workstreams, M tasks",
event_type="milestone", event_type="milestone",
topic_id="7d4d3e35-312f-4723-bde9-ddb43799109e", topic_id="36c7421b-c537-4723-bf75-42a3ebc6a1dc",
detail={"workstreams": [...], "tasks_created": M} detail={"workstreams": [...], "tasks_created": M}
) )
``` ```

View File

@@ -1,5 +1,5 @@
**Purpose:** Django 6 + Tailwind + HTMX bid management app **Purpose:** Coulomb project repository (bootstrapped from repo-seed template)
**Domain:** vergabe_teilnahme **Domain:** communication
**Repo slug:** vergabe_teilnahme **Repo slug:** vergabe-teilnahme
**Topic ID:** 7d4d3e35-312f-4723-bde9-ddb43799109e **Topic ID:** 36c7421b-c537-4723-bf75-42a3ebc6a1dc

View File

@@ -1,6 +1,7 @@
## Session Protocol ## Session Protocol
State Hub: http://127.0.0.1:8000 Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient** **Step 1 — Orient**
@@ -10,7 +11,7 @@ cat .custodian-brief.md
``` ```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed: Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
``` ```
get_domain_summary("vergabe_teilnahme") get_domain_summary("communication")
``` ```
If MCP tools are unavailable in the current agent session, use the REST API: If MCP tools are unavailable in the current agent session, use the REST API:
```bash ```bash
@@ -21,14 +22,14 @@ If the hub is offline: `cd ~/state-hub && make api`
**Step 2 — Check inbox** **Step 2 — Check inbox**
With MCP tools: With MCP tools:
``` ```
get_messages(to_agent="vergabe_teilnahme", unread_only=True) get_messages(to_agent="vergabe-teilnahme", unread_only=True)
``` ```
Mark read with `mark_message_read(message_id)`. Reply or act on coordination Mark read with `mark_message_read(message_id)`. Reply or act on coordination
requests before proceeding. requests before proceeding.
Without MCP tools: Without MCP tools:
```bash ```bash
curl -s "http://127.0.0.1:8000/messages/?to_agent=vergabe_teilnahme&unread_only=true" \ curl -s "http://127.0.0.1:8000/messages/?to_agent=vergabe-teilnahme&unread_only=true" \
| python3 -m json.tool | python3 -m json.tool
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}' -H "Content-Type: application/json" -d '{}'
@@ -39,12 +40,12 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/ ls workplans/
``` ```
For each file with `status: ready`, `active`, or `blocked`, note pending For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks. `wait`/`todo`/`progress` tasks.
**Step 4 — Present brief** **Step 4 — Present brief**
1. **Active workstreams** for `vergabe_teilnahme` — title, task counts, blocking decisions 1. **Active workstreams** for `communication` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:vergabe_teilnahme]` hub tasks 2. **Pending tasks** from `workplans/` + any `[repo:vergabe-teilnahme]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary: 3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"* - `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
- `alignment_warnings`: flag if active work is not aligned with current goal - `alignment_warnings`: flag if active work is not aligned with current goal
@@ -61,23 +62,23 @@ If no workstreams: follow First Session Protocol (`first-session.md`).
**Session close:** **Session close:**
With MCP tools: With MCP tools:
``` ```
add_progress_event(summary="...", topic_id="7d4d3e35-312f-4723-bde9-ddb43799109e", workstream_id="<uuid>") add_progress_event(summary="...", topic_id="36c7421b-c537-4723-bf75-42a3ebc6a1dc", workstream_id="<uuid>")
``` ```
Without MCP tools: Without MCP tools:
```bash ```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \ curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"topic_id":"7d4d3e35-312f-4723-bde9-ddb43799109e","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}' -d '{"topic_id":"36c7421b-c537-4723-bf75-42a3ebc6a1dc","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
``` ```
If workplan files were modified, ensure the local copy is up to date first: If workplan files were modified, ensure the local copy is up to date first:
```bash ```bash
git -C <repo_path> pull --ff-only git -C <repo_path> pull --ff-only
cd ~/state-hub && make fix-consistency REPO=vergabe_teilnahme cd ~/state-hub && make fix-consistency REPO=vergabe-teilnahme
``` ```
For repos where implementation runs on a remote machine (e.g. CoulombCore), For repos where implementation runs on a remote machine (e.g. CoulombCore),
use the combined target which pulls before fixing: use the combined target which pulls before fixing:
```bash ```bash
cd ~/state-hub && make fix-consistency-remote REPO=vergabe_teilnahme cd ~/state-hub && make fix-consistency-remote REPO=vergabe-teilnahme
``` ```
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback **C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes will sync the file to match DB. **C-16** (repo behind remote) blocks all writes

View File

@@ -1,7 +1,7 @@
## Workplan Convention (ADR-001) ## Workplan Convention (ADR-001)
File location: `workplans/vergabe_teilnahme-WP-NNNN-<slug>.md` File location: `workplans/WP-NNNN-<slug>.md`
ID prefix: `VERGABE_TEILNAHME-WP` ID prefix: `WP-`
Work items originate as files in this repo **before** being registered in the hub. Work items originate as files in this repo **before** being registered in the hub.
@@ -12,7 +12,7 @@ repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses. `needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-vergabe_teilnahme-WP-NNNN-<slug>.md`. The frontmatter id remains prefix: `YYMMDD-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference. unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**: Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
@@ -21,8 +21,20 @@ Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**
directly. Promote anything requiring analysis, design, approval, dependencies, or directly. Promote anything requiring analysis, design, approval, dependencies, or
multiple planned phases into a normal workplan. multiple planned phases into a normal workplan.
Ecosystem todos from other agents arrive as `[repo:vergabe_teilnahme]` hub tasks — Ecosystem todos from other agents arrive as `[repo:vergabe-teilnahme]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering visible at session start. Pick one up by creating the workplan file, then registering
the workstream. the workstream.
Task blocks use this shape:
```task
id: WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here --> <!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

19
.repo-classification.yaml Normal file
View File

@@ -0,0 +1,19 @@
repo_classification:
standard: Repo Classification Standard
version: '1.0'
classified_at: '2026-06-22'
classified_by: agent
category: experimental
domain: communication
secondary_domains: []
capability_tags:
- marketplace
- collaboration
- procurement
- governance
business_stake:
- product
- sales
business_mechanics:
- coordination
- operation

View File

@@ -2,12 +2,12 @@
## Repo Identity ## Repo Identity
**Purpose:** Django 6 + Tailwind + HTMX bid management app **Purpose:** Coulomb project repository (bootstrapped from repo-seed template)
**Domain:** vergabe_teilnahme **Domain:** communication
**Repo slug:** vergabe_teilnahme **Repo slug:** vergabe-teilnahme
**Topic ID:** `7d4d3e35-312f-4723-bde9-ddb43799109e` **Topic ID:** `36c7421b-c537-4723-bf75-42a3ebc6a1dc`
**Workplan prefix:** `VERGABE_TEILNAHME-WP-` **Workplan prefix:** `WP-`
--- ---
@@ -28,11 +28,11 @@ there is no MCP server for Codex agents.
cat .custodian-brief.md cat .custodian-brief.md
# Active workstreams for this domain # Active workstreams for this domain
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=7d4d3e35-312f-4723-bde9-ddb43799109e&status=active" \ curl -s "http://127.0.0.1:8000/workstreams/?topic_id=36c7421b-c537-4723-bf75-42a3ebc6a1dc&status=active" \
| python3 -m json.tool | python3 -m json.tool
# Check inbox # Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=vergabe_teilnahme&unread_only=true" \ curl -s "http://127.0.0.1:8000/messages/?to_agent=vergabe-teilnahme&unread_only=true" \
| python3 -m json.tool | python3 -m json.tool
``` ```
@@ -63,8 +63,8 @@ Omit `workstream_id` / `task_id` when not applicable.
```bash ```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"status": "in_progress"}' -d '{"status": "progress"}'
# values: todo | in_progress | done | blocked # values: wait | todo | progress | done | cancel
``` ```
### Flag a task for human review ### Flag a task for human review
@@ -81,9 +81,9 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
**Start:** **Start:**
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe) 1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
2. Check inbox: `GET /messages/?to_agent=vergabe_teilnahme&unread_only=true`; mark read 2. Check inbox: `GET /messages/?to_agent=vergabe-teilnahme&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks 3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check blocked tasks: `GET /tasks/?needs_human=true` 4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:** **During work:**
- Update task statuses in workplan files as tasks progress - Update task statuses in workplan files as tasks progress
@@ -95,21 +95,78 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
3. Note for the custodian operator: after workplan file changes, run from 3. Note for the custodian operator: after workplan file changes, run from
`~/state-hub`: `~/state-hub`:
```bash ```bash
make fix-consistency REPO=vergabe_teilnahme make fix-consistency REPO=vergabe-teilnahme
``` ```
This syncs task status from files into the hub DB. This syncs task status from files into the hub DB.
--- ---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=vergabe-teilnahme` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001) ## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files. read/cache/index layer that rebuilds from files.
**File location:** `workplans/VERGABE_TEILNAHME-WP-NNNN-<slug>.md` **File location:** `workplans/VERGABE-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to **Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-VERGABE_TEILNAHME-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is `workplans/archived/YYMMDD-VERGABE-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change. the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use **Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
@@ -121,11 +178,11 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
```yaml ```yaml
--- ---
id: VERGABE_TEILNAHME-WP-NNNN id: VERGABE-WP-NNNN
type: workplan type: workplan
title: "..." title: "..."
domain: vergabe_teilnahme domain: communication
repo: vergabe_teilnahme repo: vergabe-teilnahme
status: proposed | ready | active | blocked | backlog | finished | archived status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex owner: codex
topic_slug: ... topic_slug: ...
@@ -145,8 +202,8 @@ derived health labels, not frontmatter statuses.
## Task Title ## Task Title
` ` `task ` ` `task
id: VERGABE_TEILNAHME-WP-NNNN-T01 id: VERGABE-WP-NNNN-T01
status: todo | in_progress | done | blocked status: wait | todo | progress | done | cancel
priority: high | medium | low priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` ` ` ` `
@@ -154,9 +211,9 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
Task description text. Task description text.
``` ```
Status progression: `todo` → `in_progress` → `done` (or `blocked`) Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
To create a new workplan: To create a new workplan:
1. Write the file following the format above 1. Write the file following the format above
2. Notify the custodian operator to run `make fix-consistency REPO=vergabe_teilnahme` 2. Notify the custodian operator to run `make fix-consistency REPO=vergabe-teilnahme`
(or send a message to the hub agent via `POST /messages/`) (or send a message to the hub agent via `POST /messages/`)

View File

@@ -8,4 +8,5 @@
@.claude/rules/stack-and-commands.md @.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md @.claude/rules/architecture.md
@.claude/rules/repo-boundary.md @.claude/rules/repo-boundary.md
@.claude/rules/credential-routing.md
@.claude/rules/agents.md @.claude/rules/agents.md

View File

@@ -1,4 +1,4 @@
.PHONY: help db dev css seed migrate test lint shell superuser collectstatic .PHONY: help db dev css seed migrate test lint shell superuser collectstatic sync-whynot-design
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
@@ -41,3 +41,6 @@ shell: ## Open a Django shell (shell_plus if available)
collectstatic: ## Collect static files into staticfiles/ (production step) collectstatic: ## Collect static files into staticfiles/ (production step)
uv run manage.py collectstatic --noinput uv run manage.py collectstatic --noinput
sync-whynot-design: ## Re-vendor whynot-design CSS+tokens from the pinned ref
./scripts/sync-whynot-design.sh

159
SCOPE.md
View File

@@ -8,130 +8,147 @@
## One-liner ## One-liner
<!-- Describe the purpose of this repository in one precise sentence. --> Web application that structures a company's participation in tenders
<!-- Example: "Provides a lightweight event router for Kubernetes-native systems." --> (Ausschreibungen) end-to-end, from research through submission to
post-award retrospective.
--- ---
## Core Idea ## Core Idea
<!-- What is the main capability or idea behind this repository? --> **Vergabe Teilnahme** ("tender participation") guides a team through the
<!-- What problem does it try to solve? --> full lifecycle of bidding on a public or private tender: collecting tender
documents, analyzing requirements, deciding whether to participate, tracking
open items, finalizing pricing and documents, submitting on time, and
capturing reusable knowledge afterwards — win or lose. It is phase-guided,
never phase-locked: every element of a tender is reachable at any time, and
there is no forced completeness outside real submission-deadline
constraints. All data entry is manual in this first build stage — no
automated ingestion from tender platforms.
It operates on three levels, per `wiki/ProductRequirementsDocument.md`:
| Level | Content |
|---|---|
| Operational | deadlines, documents, tasks, lots (Lose), requirements, bidder questions, submission |
| Management | participate/no-bid decision, approvals, pricing level, subcontractor selection, outcome, retrospective |
| Strategic | reusable evidence/references, market price observation, loss reasons, win factors, competitor (Marktbegleiter) analysis |
--- ---
## In Scope ## In Scope
<!-- What this repository is responsible for. --> - Tender (Ausschreibung) and lot (Los) tracking through 8 navigable phases
<!-- Be explicit and concrete. --> - Requirements analysis and bidder-question (Bieterfragen) handling
- Task management (Aufgaben) scoped to a tender or a lot
- - Document management (Dokumente) for tender materials and submission artifacts
- - Pricing (Preise) tracking per lot/tender
- - Submission and post-award retrospective (Abgabe/Nachbetrachtung)
- Partner and reference library (Partner/Bibliothek) for reusable evidence
--- - Competitor/market observation (Marktbegleiter)
- Feedback/bug capture within the app (Feedback)
- Issue-tracking integration via `issue-core` (task facade, not a UI replacement)
## Out of Scope ## Out of Scope
<!-- What this repository deliberately does NOT do. --> - External user accounts for partners/subcontractors/service providers (data
<!-- This is often more important than "In Scope". --> objects only in this build stage, no system access of their own)
- Multi-tenancy
- - Automated ingestion from tender platforms, SharePoint, Teams, CRM, ERP,
- email, or calendars (deliberately manual-entry-first for v1)
- - Certification or legal validity of submissions — the system tracks
process state, it is not a legal compliance authority
--- ---
## Relevant When ## Relevant When
<!-- When should someone consider using or exploring this repository? --> - Deciding whether to bid on a tender and needing structured deadline,
document, and requirement tracking across a team
- - Needing a system of record for pricing decisions, submission evidence,
- and post-award retrospectives on public/private tenders
- - Wanting to reuse prior tender evidence, references, or competitor/pricing
observations when evaluating a new tender
---
## Not Relevant When ## Not Relevant When
<!-- When should someone ignore this repository? --> - Needing external bidder/partner portal access (not built yet)
- Needing automated tender discovery or platform integration (out of scope
- for this build stage)
- - Needing generic project management unrelated to the tender-participation
- domain
--- ---
## Current State ## Current State
<!-- Rough indication of maturity. No strict format required. --> - Status: active
- Implementation: substantial — 12 Django apps covering the full phase
- Status: <!-- e.g. concept / experimental / active / stable / deprecated --> model (accounts, aufgaben, ausschreibungen, bibliothek, core, dokumente,
- Implementation: <!-- e.g. idea / partial / substantial / complete --> feedback, lose, marktbegleiter, nachbetrachtung, partner, preise); 17
- Stability: <!-- e.g. unstable / evolving / stable --> workplans (WP-0001WP-0017) implemented in sequence from project
- Usage: <!-- e.g. none / personal / internal / production --> scaffold through whynot-design token adoption
- Stability: evolving — manual-entry-first v1; no CI workflow configured yet
<!-- Add any notes that help set expectations. --> (`.gitea/`/`.forgejo/` absent)
- Usage: internal collaboration tool, not yet published for external use
--- ---
## How It Fits ## How It Fits
<!-- Where does this repository sit in the bigger picture? --> - Upstream dependencies: `issue-core` (task-tracking facade, see
`vergabe_teilnahme/apps/aufgaben/issue_facade.py`), `whynot-design`
- Upstream dependencies: (visual language, vendored CSS/tokens)
- Downstream consumers: - Downstream consumers: none known
- Often used with: - Often used with: `railiance-apps` (deployment target per its own SCOPE.md,
which names `vergabe-teilnahme` as a user-facing service it deploys)
--- ---
## Terminology ## Terminology
<!-- Terms that are important to understand this repo. --> - Preferred terms: Ausschreibung (tender), Los (lot), Aufgabe (task),
<!-- Especially useful if naming differs from other repos. --> Marktbegleiter (competitor/market companion), Nachbetrachtung (post-award
retrospective)
- Preferred terms: - Also known as: "Vergabe Teilnahme" (product name), "tender participation
- Also known as: management system" (English gloss)
- Potentially confusing terms: - Potentially confusing terms: "Partner" here means reference/subcontractor
data objects, not system users
--- ---
## Related / Overlapping Repositories ## Related / Overlapping Repositories
<!-- List repositories that have similar or adjacent responsibilities. --> - `issue-core` — task-tracking backend consumed via `issue_facade.py`
<!-- Helps detect duplication and navigate the ecosystem. --> - `whynot-design` — visual language vendored into `static/src/vendor/`
- `railiance-apps` — deployment/workload layer for this service
- <repo-name> — <!-- how it relates -->
--- ---
## Getting Oriented ## Getting Oriented
<!-- If someone decides to look deeper, where should they start? --> - Start with: `wiki/ProductRequirementsDocument.md` (product intent, in
German), `wiki/ArchitectureBlueprint.md` (technology stack and design
- Start with: principles)
- Key files / directories: - Key files / directories: `vergabe_teilnahme/apps/` (12 domain apps),
- Entry points: `vergabe_teilnahme/urls.py` (route map), `workplans/` (WP-00010017
build history)
- Entry points: `manage.py runserver`; `vergabe_teilnahme/urls.py` maps
`/ausschreibungen/`, `/lose/`, `/aufgaben/`, `/dokumente/`, and more
--- ---
## Provided Capabilities ## Provided Capabilities
<!-- What can this repo's domain provide to other domains on request? --> Registered in `registry/capabilities/capability.procurement.vergabe-teilnahme.md`
<!-- Each capability block is parsed by the state-hub capability catalog ingest. --> (reuse-surface federation; vector **D3/A1/C1/R1** after SCOPE.md fill-in and
<!-- Remove the examples and add your own, or leave empty if none. --> REUSE-WP-0017-T05 entry-2 review, 2026-07-07).
<!--
```capability
type: infrastructure
title: Example capability title
description: What this capability provides, in one or two sentences.
keywords: [keyword1, keyword2, keyword3]
```
-->
--- ---
## Notes ## Notes
<!-- Anything else worth knowing. Keep it short. --> Product and architecture documentation (`wiki/`) is in German; this SCOPE.md
is in English per the reuse-surface registry's Markdown-first, agent-facing
convention. Refer to `wiki/ProductRequirementsDocument.md` for the
authoritative German-language product definition.

View File

@@ -0,0 +1,361 @@
---
date: 2026-05-23
topic: whynot-design adoption — cross-framework analysis
status: research / decision pending
author: claude (opus-4.7)
related:
- ~/whynot-design (the React DS in question)
- ~/whynot-control (the org control surface — explicitly out of scope)
- vergabe-teilnahme (the consuming Django app)
follow-ups:
- workplan: VERGABE_TEILNAHME-WP-0017 (whynot-design adoption) — not yet drafted, depends on strategy decision below
- possible new repo: whynot-design-django — see §5
---
# whynot-design — cross-framework adoption analysis
> Persisted from a research conversation on 2026-05-23. The conversation was triggered
> by the question "should vergabe-teilnahme adopt the whynot design system, and how?"
> The strategic question opened up because whynot-design ships React components
> while vergabe-teilnahme is Django + HTMX + Tailwind.
## 1. The headline insight
There is **no community** around "React → Django component porting." That pattern
doesn't exist as a tool, library, or codified practice. The reason: cross-framework
UI sharing has, since ~2020, converged on **Web Components** as the standard answer.
Every major design system that supports multiple frameworks (Shoelace / Web Awesome,
IBM Carbon, Adobe Spectrum, Salesforce Lightning, Material Web) either ships Web
Components or maintains parallel hand-rolled implementations per framework. There is
no third option that the industry has validated.
The W3C Design Tokens Community Group spec reached its **first stable version in
October 2025**, with Style Dictionary, Figma, Penpot, Tokens Studio, and others as
reference implementations. Tokens as a cross-framework contract are now genuinely
portable; *components* still aren't, except via web components.
The strategic question therefore reshapes itself: **do we accept the
parallel-implementations cost, or do we change what `whynot-design` actually ships?**
## 2. Research findings — existing approaches and their communities
| Approach | What it is | Community / activity | Fit for whynot |
|---|---|---|---|
| **Web Components** (Lit, Stencil) | Browser-standard custom elements; works in any framework | Very active; React 19 finally scores perfect on custom-elements-everywhere.com | **High** — best general answer if we're willing to refactor whynot-design |
| **Shoelace / Web Awesome** | Pre-built Web Components UI kit (Shoelace's successor) | Large, active OSS community | Not direct (competing DS), but proves the model. Has explicit HTMX integration guides |
| **Stencil JS** | Compiler that emits framework-specific bindings from one source | Mature; IBM/Apple use it; declining mindshare vs. Lit | **Medium** — heavier than Lit but generates per-framework wrappers automatically |
| **Style Dictionary + DTCG tokens** | One token file → CSS / Tailwind / iOS / Android / Flutter | W3C-backed, stable spec since 2025-10 | **High** — should be our token pipeline regardless of component choice |
| **Carbon / Spectrum / Lightning / Material pattern** | Single design source, hand-maintained parallel packages per framework | Active but they're 100+ engineer teams | **Low** at our scale — the "expensive but principled" reference path |
| **Single-spa / micro-frontends** | Multiple frameworks coexisting in one app | Niche, mostly enterprise integration | **Off-topic** — not about sharing components, about hosting heterogeneous apps |
| **django-cotton / django-components / django-bird** | Django-template-native component libraries; HTMX-friendly | Active, growing in 20242026; mature enough to depend on | **High** as the *Django-side runtime* — but doesn't solve cross-framework, just gives Django a real component model |
| **"Port React to Django" libraries** | (doesn't exist) | None | None |
**Read of the field:** the world has settled on "tokens are cross-framework;
components are framework-native." Most teams either pick web components (one
implementation, runs anywhere with some friction) or accept parallel implementations
from a shared design language.
## 3. Three viable strategies for whynot's situation
**Strategy A — Pivot whynot-design to Web Components.**
Rewrite `Atoms.jsx` + `Chrome.jsx` as Lit components. Ship one runtime artefact
(`@whynot/design`) that Django + HTMX templates can drop into a page via plain
`<whynot-button>` tags. No React dependency anywhere. Tailwind / CSS variables
continue to drive theming.
**Strategy B — Keep whynot-design React-canonical; add a parallel `whynot-design-django` repo.**
Treat the React JSX as the *visual + API specification*. Hand-port to Django
partials in a separate, reusable repo so vergabe-teilnahme and future Django
consumers share one implementation. Tokens stay in whynot-design (single source).
**Strategy C — Tokens only; components are framework-local forever.**
Don't promise component parity. Vergabe re-implements whynot's *look* using the
tokens but its components live and die in vergabe-teilnahme. Other Django apps
would do the same fork. Bernd implied this is unsatisfying — he wants a real
component library for cross-project consistency.
The real choice is **A vs. B**.
## 4. Pros / cons — A (Web Components) vs. B (parallel Django repo)
### Strategy A — Pivot to Web Components (Lit)
**Pros**
- *One* implementation. Zero divergence risk. Bug fixes ship once.
- Standards-backed; outlives any framework choice. Works in vanilla HTML, Django
templates, future Vue/Svelte/Solid apps, marketing sites, claude.ai artifacts.
- HTMX swaps work cleanly — web components are just DOM; HTMX doesn't care.
- The community is *here*. Tutorials, debugging tools, Storybook integration,
accessibility testing infrastructure all exist.
- The Claude atelier in claude.ai can still output JSX prototypes; Lit ports are
mechanical once the design is decided.
**Cons**
- One-time rewrite cost of the existing 11 React components. Real, but small
(~267 lines of JSX, not a library of 200 components).
- Web components have Shadow DOM trade-offs: form-association, deep style scoping,
and slotting have learning curves. Workable but real friction.
- React-shaped APIs (`<Button variant="primary">`) translate to attributes /
properties (`<wn-button variant="primary">`). Slightly less ergonomic in React,
equally ergonomic everywhere else.
- Lose the claude.ai → npm symmetry: claude.ai design tool outputs JSX; you'd
convert each new component.
- Whoever maintains whynot-design has to learn Lit (small surface area, but new
vocabulary).
### Strategy B — Parallel `whynot-design-django` repo
**Pros**
- Each implementation is idiomatic for its stack. Django templates feel like
Django; React feels like React.
- No new technology to learn. Both repos use mature, well-understood patterns.
- Django consumers get to use `django-cotton` or `django-bird` — real ergonomics,
not a half-port.
- claude.ai → JSX → Lit conversion step is avoided. The atelier output is
*directly* the React reference.
**Cons**
- *Two* implementations forever. Every component change needs both sides updated;
PRs need cross-repo review discipline.
- API drift is inevitable. By v0.5 the React `<Button>` and Django `{% button %}`
will have diverged in props/slots/behavior unless enforced with conformance tests.
- A *third* consumer stack (Vue, vanilla HTML, native mobile) means a *third* repo.
Cost scales linearly with the number of stacks; A's cost is roughly constant.
- The "community" of "people maintaining a React→Django twin DS" is exactly Bernd
and whoever he recruits. No prior art to lean on.
- Visual regression testing has to run twice (Playwright against examples in both
repos) to ensure parity.
### Recommendation
**Strategy A is the right long-term answer**; B is the right *interim* answer if
we don't want to halt vergabe-teilnahme to do a Lit rewrite first.
A real third path: **A scheduled, B today.** Build B as a reusable Django port now
to unblock vergabe-teilnahme; treat it as the *bridge* until whynot-design pivots
to Lit at, say, v0.5. The Django repo becomes obsolete when A lands — that's a
feature, not a bug; it forces a clear retirement decision instead of accumulating
tech debt.
If we don't want to commit to a future Lit pivot, then B is the right choice on its
own — just accept the linear-cost-per-stack reality.
## 5. Naming, establishing, workflow — if we go with B
### Naming proposals
| Candidate | Pros | Cons |
|---|---|---|
| **`whynot-design-django`** (recommended) | Mirrors `@whynot/design`. Unambiguous about stack. Discoverable. | Slightly long. |
| `whynot-django` | Short. | Ambiguous — sounds like a Django app, not a DS port. |
| `whynot-design-py` | Hedges for Jinja2/Flask later. | Vague — Python doesn't pick a template engine. |
| `whynot-dj` | Compact. | Cryptic. |
| `whynot-templates` | Honest about what it is. | Doesn't carry the "design" meaning. |
**Recommend `whynot-design-django`.** If a Jinja port appears, it gets its own repo
(`whynot-design-jinja`), not a confusing rename.
### Establishing the repo (concrete steps)
1. **Gitea org placement:** `gitea-remote:whynot/whynot-design-django.git` — same
org as `whynot-design`, signaling sibling status.
2. **Package shape:** a pip-installable Django app (`pyproject.toml`, importable as
`whynot_design`). Two install paths:
- `pip install git+ssh://...@v0.1.0` (matches whynot-design's tag-pinned discipline).
- For dev: editable install (`pip install -e ../whynot-design-django`).
3. **Layout** (matches Django conventions; mirrors whynot-design's structure):
```
whynot-design-django/
├── README.md ← restates the "match the React spec" contract
├── CONTRIBUTING.md ← parity rules: any change here must follow a whynot-design change
├── CHANGELOG.md
├── pyproject.toml
├── whynot_design/
│ ├── __init__.py
│ ├── apps.py
│ ├── templates/whynot_design/
│ │ ├── atoms/eyebrow.html, tag.html, button.html, stage_dot.html, stamp.html, icon.html
│ │ ├── chrome/top_nav.html, sidebar.html, page_header.html, pipeline_strip.html
│ │ └── _base.html ← imports the CSS once
│ ├── templatetags/
│ │ └── whynot.py ← {% wn_button variant="primary" %}…{% endwn_button %}, etc.
│ └── static/whynot_design/
│ └── colors_and_type.css ← vendored from @whynot/design (synced)
├── tests/
│ ├── test_components.py ← Django test client, asserts rendered markup matches expected shape
│ └── parity/ ← Playwright comparison of Django render vs. React render
└── examples/ ← a Django demo project rendering every component
```
4. **Token sync:** a `scripts/sync-from-whynot-design.py` that copies
`colors_and_type.css` and `tokens/*.json` from a pinned whynot-design ref into
the static dir + generates a Django settings constant for tokens. Run on every
whynot-design version bump.
5. **Underlying component mechanism:** plain `{% include %}` partials (simplest, no
new dep), `django-cotton` (HTML-like syntax, very ergonomic, active community),
or `django-components` (most powerful, heaviest). My lean: **`django-cotton`** —
its `<c-button variant="primary">…</c-button>` syntax parallels React JSX best,
so the parity contract is more obvious.
### What changes in the workflow
Before:
```
claude.ai atelier ──► whynot-design ──► consumer
```
After:
```
claude.ai atelier ──► whynot-design ──┬─► whynot-design-django ──► vergabe-teilnahme (+ future Django apps)
├─► future stacks: whynot-design-jinja / -svelte / -vue / …
└─► tokens flow into all child repos via a sync script
```
Concretely, this adds three new ongoing obligations:
1. **Token sync gate.** When whynot-design ships a token change, the Django repo's
sync script must run before its next release. CI check: the static CSS in
`whynot-design-django` must hash-match the CSS at the pinned upstream tag.
2. **Component parity gate.** When whynot-design adds or modifies a component, an
issue is opened automatically in `whynot-design-django` referencing the upstream
PR. A release is blocked until the issue closes. Encode in `CONTRIBUTING.md`
first; later, a CI cross-repo check.
3. **Conformance tests.** Each component's Django partial has a Playwright snapshot
that's compared against the same component rendered in whynot-design's
`examples/whynot-control/`. Drift = failing build.
Vergabe-teilnahme's workplan therefore depends on **whynot-design-django** existing
first. The vergabe workplan becomes: install the new package, replace partials with
cotton/include tags, retheme.
## 6. Component-level inventory — whynot-design vs. vergabe-teilnahme
| whynot-design (React) | vergabe-teilnahme (Django) | Match | Gap action |
|---|---|---|---|
| `Eyebrow` (mono uppercase label, fg-3) | — | Missing | New partial `atoms/eyebrow.html` |
| `Tag` (mono uppercase pill: default / active / draft) | `status_badge.html` (different semantics — domain statuses) | Partial | Replace `status_badge` content with `Tag` variants; keep status→variant mapping |
| `Button` (3 variants: primary / secondary / ghost; lucide icon support) | CSS classes `.btn-primary`, `.btn-secondary`, `.btn-danger`, `.btn-ghost` | Strong overlap; `btn-danger` not upstream | Adopt 3 base variants; propose `danger` upstream OR retire it (whynot's voice avoids red/destructive emphasis) |
| `StageDot` (S0S4 signal levels with grey-ramp + yellow S4) | `phase_nav.html` (numbered phase circles: todo/active/done/warn) | Semantically different — *whynot stages* ≠ *vergabe phases* | Keep both as distinct atoms. Propose `PhaseDot` upstream OR keep vergabe-local |
| `Stamp` (yellow rotated "DRAFT") | — | Missing | New partial; useful for unfreigegeben items |
| `Icon` (lucide via `data-lucide`) | inline SVGs / class-based | Missing as a component | New partial that wraps lucide; pull lucide JS into base.html |
| `TopNav` | `topbar.html` | Both exist; styling and density differ significantly | Reskin vergabe's topbar to whynot's structure (search box + primary action right-aligned) |
| `Sidebar` | `sidebar.html` | Both exist; nav model differs (vergabe has phase grouping; whynot has Work / Control docs grouping) | Adopt whynot's chrome (item style, eyebrow section headers) but keep vergabe's nav items |
| `PageHeader` (eyebrow + h1 + lede + actions) | — (inline per template) | Missing | New partial; refactor all page templates to use it |
| `PipelineStrip` (5-stage horizontal indicator) | `phase_nav.html` (vertical / different) | Semantically related, visually different | Decide: align on whynot's strip OR propose `PipelineStrip` variant upstream |
| — | `field_row.html` (vergabe-specific) | n/a | Propose upstream — it's a generic key/value display row |
| — | `breadcrumb.html` | n/a | Propose upstream as `Breadcrumb` |
| — | `feedback_button.html`, `feedback_modal.html`, `feedback_success.html` | n/a (vergabe-specific UX flow) | Keep vergabe-local; product UI, not DS atoms |
| — | `freigabe_modal.html`, `freigabe_success.html` | n/a (vergabe-specific) | Keep vergabe-local |
| — | `search_results.html` (HTMX results target) | n/a | Probably vergabe-local, but a `Listbox`/`Combobox` atom could live upstream — defer |
### Gap summary
- **5 net-new atoms** in vergabe (Eyebrow, Stamp, PageHeader, Icon wrapper, PipelineStrip).
- **4 atoms to align** (Tag ↔ status_badge, Button variants, TopNav, Sidebar).
- **2 atoms to propose upstream** (Breadcrumb, FieldRow).
- **1 semantic mismatch to resolve** (Stage vs Phase — they're different concepts).
- **All vergabe-specific flow UI** (feedback, freigabe, search_results) stays
vergabe-local — that's correct, those are product UI, not design system.
### Components missing from whynot-design — full gap list
These are components that are absent from `whynot-design` today and would need to
land there (or in `whynot-design-django`) before vergabe-teilnahme can fully adopt
the system. Grouped by origin.
#### Already exists in vergabe-teilnahme — candidates to promote upstream
1. **`Breadcrumb`** — vergabe's `breadcrumb.html`. Trivially generic; no
vergabe-specific semantics.
2. **`FieldRow`** — vergabe's `field_row.html` (label + value, 3-column grid). The
pattern recurs across detail pages; clearly a DS atom.
3. **`PhaseDot` / `PhaseNav`** *(name TBD)* — vergabe's `phase_nav.html`.
Semantically distinct from whynot's `StageDot` (numbered phases with
todo / active / done / warn states, not S0S4 signal levels). Either upstream
as a sibling component, or kept vergabe-local. **Open decision.**
#### Doesn't exist anywhere yet, but the DS clearly needs them
These are components that vergabe-teilnahme has as raw Tailwind classes or ad-hoc
markup, and whynot-design has no equivalent — but any non-trivial app needs them,
so they're real DS gaps:
4. **`Card`** — whynot's house rules reference cards ("no shadows on cards,"
"04px radii for cards/sheets") but there is no `Card` JSX component. Vergabe
has `.card` as a Tailwind class. **Surprising omission given the explicit
design rules.**
5. **`Input` / `Textarea` / `Select`** — whynot ships no form primitives at all.
Vergabe has `.form-input` and `.form-label` CSS classes. Required before any
form-based whynot artefact ships.
6. **`Modal` / `Dialog`** — whynot ships none. Vergabe has two (`freigabe_modal`,
`feedback_modal`). Whynot's house rules even prescribe modal radius (8px),
confirming the intent — just the component is missing.
7. **`Table`** — whynot ships none. Vergabe has `.table-base`, `.table-header`,
`.table-row` classes. Any data-heavy view (likely most of vergabe) needs it.
8. **`Toast` / inline success banner** *(name TBD)* — whynot ships none. Vergabe
has `freigabe_success.html` and `feedback_success.html` as ad-hoc partials.
Even with whynot's quiet voice, success / error states need a defined visual.
#### Worth flagging but defer until first real need
9. **`SearchInput`** — currently inlined inside whynot's `TopNav`. Pulling it out
as a standalone atom would be useful (vergabe's `search_results.html` is a
target for an HTMX-driven search box).
10. **`EmptyState`** — neither codebase has one; vergabe will need it for empty
list views. Worth designing once before either side fills the gap locally.
11. **`Tabs`, `Dropdown`, `Tooltip`, `Pagination`, `Avatar`** — common DS atoms;
neither side has them. Don't add speculatively; add when the first concrete
use case appears.
**Count:** 3 vergabe-existing to promote, 5 genuine DS-level gaps, 5 deferred.
The 5 "genuine DS-level gaps" — especially `Card`, `Input`, `Modal`, `Table` — are
**blockers for adoption**: vergabe can't replace its current chrome without them
existing somewhere shared.
### Stylistic gaps to address before "done"
- Vergabe currently uses Tailwind utility classes pervasively
(`.btn-primary { @apply … }`); whynot uses inline-style CSS-variable assignment.
The Django port should commit to one — recommend CSS variable + utility classes
(re-derive whynot's component styles as Tailwind `@apply` blocks that read from
CSS vars), so HTMX-injected fragments don't need their own style scaffolding.
- Lucide icons aren't wired into vergabe. Need a base.html script tag + an icon partial.
- Font stack mismatch: vergabe is `ui-sans-serif`; whynot is IBM Plex Sans / Mono /
Serif. Importing whynot's CSS handles this — but it pulls Google Fonts at runtime.
Decide self-host vs. CDN.
- Vergabe's brand blue (`#3b5bdb`) has to go entirely. No mid-state — the whynot
aesthetic forbids it.
## 7. Recommendation and open decision
**Recommendation:** Strategy B (parallel `whynot-design-django` repo) as the
immediate move, with the README explicitly framing it as a **bridge** that becomes
obsolete if whynot-design eventually pivots to Lit web components. Concretely this
means **three workplans**, not one:
1. **`whynot-design`**: small additions — tag v0.1.0, add upstream candidates
(`Breadcrumb`, `FieldRow`), document the parity contract for downstream Django port.
2. **`whynot-design-django`** (new repo): bootstrap, ship v0.1.0 with the 11
components + 2 vergabe-contributed atoms, set up token-sync + parity tests.
3. **`vergabe-teilnahme`** (`WP-0017`): consume `whynot-design-django`, retheme,
pilot on one page first, then full sweep.
**Open decision (blocks workplan drafting):**
Strategy A (pivot to Lit immediately) vs. Strategy B (parallel Django repo) vs.
"B now, A later" — Bernd to choose. If "B now, A later," add a workplan stub for
the Lit pivot in `whynot-design` itself so it doesn't get forgotten. If "A from the
start," the Django repo doesn't exist and the workplans collapse to two.
## Sources
- [Shoelace / Web Awesome](https://shoelace.style/) — framework-agnostic web components DS
- [Lit + Web Components for cross-framework UIs](https://thenewstack.io/how-to-build-framework-agnostic-uis-with-web-components/)
- [HTMX + Shoelace example](https://binaryigor.com/htmx-with-shoelace-framework-agnostic-components-in-an-example-app.html)
- [W3C Design Tokens spec first stable version (Oct 2025)](https://www.w3.org/community/design-tokens/2025/10/28/design-tokens-specification-reaches-first-stable-version/)
- [Style Dictionary cross-platform tokens](https://styledictionary.com/info/tokens/)
- [Django Cotton — HTML-like component syntax](https://django-cotton.com/)
- [django-bird](https://github.com/joshuadavidthomas/django-bird)
- [Salesforce — Beyond Components: multi-framework DS](https://medium.com/salesforce-ux/beyond-components-a-design-system-to-support-multiple-frameworks-cb1e4d511f66)
- [AgnosticUI post-mortem (rewrite to Lit)](https://frontendmasters.com/blog/post-mortem-rewriting-agnosticui-with-lit-web-components/)

12
registry/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Capability Registry
Markdown-first capability index for federation and reuse planning.
## Authoring
1. Copy a capability entry template (see reuse-surface `templates/capability-entry.template.md`).
2. Add the row to `indexes/capabilities.yaml`.
3. Run `reuse-surface validate` from a checkout with the CLI installed.
4. Merge to `main` and verify publish with `reuse-surface establish --publish-check`.
Federation contract: reuse-surface `docs/RegistryFederation.md`.

View File

View File

@@ -0,0 +1,147 @@
---
id: capability.procurement.vergabe-teilnahme
name: Vergabe Teilnahme (Public Procurement Participation) Application
summary: Django application (with a Vite/Tailwind frontend) for managing German public-procurement (Vergabe)
tender participation — Ausschreibungs- und Teilnahme-Management-System.
owner: vergabe-teilnahme
status: draft
domain: communication
tags:
- procurement
- django
- vergabe
maturity:
discovery:
current: D3
target: D4
confidence: medium
rationale: SCOPE.md filled 2026-07-07 with in/out scope, current state, terminology, and orientation
pointers; supported by wiki/ProductRequirementsDocument.md (German product definition) and
wiki/ArchitectureBlueprint.md. README.md remains a stale repo-seed leftover — SCOPE and wiki
are authoritative for discovery.
availability:
current: A1
target: A3
confidence: medium
rationale: Substantial Django application (12 domain apps, WP-0001WP-0017 implemented) with
Vite/Tailwind frontend and docker-compose dev/test; SCOPE.md describes internal collaboration
use only — no hosted production deployment confirmed in this review.
external_evidence:
completeness:
level: C1
confidence: medium
basis: scope_vs_intent_and_consumer_expectations
satisfied_expectations:
- in-scope/out-of-scope boundaries documented in SCOPE.md
- 8-phase tender participation model described with operational/management/strategic levels
- manual-entry-first v1 constraint and external-portal exclusion explicit
broken_expectations: []
out_of_scope_expectations: []
reliability:
level: R1
confidence: low
basis: consumer_quality_signals
known_reliability_risks:
- no CI workflow configured yet (per SCOPE.md current state)
- no production consumer telemetry (REUSE-WP-0019)
- README.md still stale repo-seed text — fix separately
discovery:
intent: Guide a team through the full lifecycle of bidding on public or private tenders (Ausschreibungen)
— from research and participate/no-bid decisions through submission and post-award retrospective.
includes:
- tender (Ausschreibung) and lot (Los) tracking across 8 navigable phases
- requirements analysis and bidder-question (Bieterfragen) handling
- tasks (Aufgaben), documents (Dokumente), pricing (Preise), submission/retrospective (Abgabe/Nachbetrachtung)
- partner/reference library (Bibliothek) and competitor observation (Marktbegleiter)
- issue-core task facade integration (not a UI replacement)
excludes:
- external partner/subcontractor portal accounts (data objects only in v1)
- multi-tenancy
- automated ingestion from tender platforms, SharePoint, Teams, CRM, ERP, email, or calendars
assumptions: []
use_cases: []
research_memos: []
availability:
current_level: A1
target_level: A3
current_artifacts:
- Django application (`vergabe_teilnahme`, 12 apps)
- Vite/Tailwind frontend (`static/`)
- docker-compose.dev.yml and docker-compose.test.yml
target_artifacts: []
consumption_modes:
- application (Django + Vite, local dev via docker-compose)
relations:
depends_on:
- capability.infotech.issue-tracking
supports: []
related_to:
- capability.design.whynot-system
evidence:
documentation:
- SCOPE.md
- wiki/ProductRequirementsDocument.md
- wiki/ArchitectureBlueprint.md
- pyproject.toml
tests:
- vergabe_teilnahme/apps/*/tests.py (pytest-django suite across domain apps)
- docker-compose.test.yml
consumer_feedback: []
bug_reports: []
incidents: []
consumer_guidance:
recommended_for:
- teams needing structured German tender-participation workflow tracking (manual-entry-first v1)
- extending or deploying this specific Django application
not_recommended_for:
- automated tender-platform ingestion (out of scope for v1)
- external bidder/partner self-service portals (not built)
known_limitations:
- manual data entry only in v1 — no platform integration
- README.md is still repo-seed boilerplate; use SCOPE.md and wiki/ for orientation
promotion_history:
- date: "2026-07-07"
dimension: discovery
from: D1
to: D3
rationale: SCOPE.md filled with full in/out scope and current-state documentation (REUSE-WP-0017-T05 entry-2 review).
author: grok
- date: "2026-07-07"
dimension: completeness
from: C0
to: C1
rationale: Consumer scope expectations now documented in SCOPE.md and wiki PRD.
author: grok
---
# Vergabe Teilnahme (Public Procurement Participation) Application
## Overview
`vergabe-teilnahme` is a Django + Vite/Tailwind application for managing German public-procurement (Vergabe) tender participation end-to-end. SCOPE.md (filled 2026-07-07) documents the 8-phase, manual-entry-first v1 model across 12 Django apps.
## Assessment notes
### Discovery
SCOPE.md now provides authoritative in/out scope, current state, terminology, and pointers to the German-language wiki PRD and architecture blueprint. Discovery promoted from D1 to D3; README cleanup remains a separate hygiene item.
### Availability
Substantial implemented application (WP-0001WP-0017) with local docker-compose paths. Held at A1 until a hosted deployment path is confirmed (railiance-apps is named as deployment target in SCOPE.md).
### Completeness
Scope-vs-intent expectations are now documented (C1). Feature completeness against the full PRD is not independently verified in this review.
### Reliability
Pytest-django suite exists across domain apps (structural R1). No CI workflow or production telemetry yet.
## Promotion checklist
- [x] ID follows `capability.<domain>.<name>` pattern
- [x] Maturity enums match `specs/CapabilityMaturityStandard.md`
- [x] `external_evidence` is populated separately from `maturity`
- [x] Relations reference valid capability IDs
- [x] Index entry added in `registry/indexes/capabilities.yaml`

View File

@@ -0,0 +1,19 @@
version: 1
updated: '2026-07-07'
domain: helix_forge
capabilities:
- id: capability.procurement.vergabe-teilnahme
name: Vergabe Teilnahme (Public Procurement Participation) Application
summary: Django application (with a Vite/Tailwind frontend) for managing German public-procurement (Vergabe)
tender participation — Ausschreibungs- und Teilnahme-Management-System.
vector: D3 / A1 / C1 / R1
domain: communication
status: draft
owner: vergabe-teilnahme
path: registry/capabilities/capability.procurement.vergabe-teilnahme.md
tags:
- procurement
- django
- vergabe
consumption_modes:
- application (Django + Vite, local dev via docker-compose)

39
scripts/sync-whynot-design.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Synchronises the vendored copy of the whynot-design system from a pinned
# upstream commit. Source: ~/whynot-design (worktree) or a clone from gitea.
#
# Usage: ./scripts/sync-whynot-design.sh [<commit-or-ref>]
# Default: reads .whynot-design-ref from the vendor directory.
#
# See workplans/WP-0017-whynot-design-tokens.md for the adoption strategy.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VENDOR_DIR="$ROOT/static/src/vendor/whynot-design"
REF_FILE="$VENDOR_DIR/.whynot-design-ref"
SRC_REPO="${WHYNOT_DESIGN_SRC:-$HOME/whynot-design}"
REF="${1:-}"
if [[ -z "$REF" && -f "$REF_FILE" ]]; then
REF="$(cat "$REF_FILE")"
fi
if [[ -z "$REF" ]]; then
echo "Usage: $0 <commit-or-ref> (or write a ref to $REF_FILE)" >&2
exit 2
fi
if [[ ! -d "$SRC_REPO/.git" ]]; then
echo "Source not found: $SRC_REPO" >&2
echo "Set WHYNOT_DESIGN_SRC or clone gitea:whynot/whynot-design there." >&2
exit 1
fi
mkdir -p "$VENDOR_DIR/tokens"
git -C "$SRC_REPO" show "$REF:src/styles/colors_and_type.css" \
> "$VENDOR_DIR/colors_and_type.css"
for f in colors.json type.json spacing.json index.json; do
git -C "$SRC_REPO" show "$REF:tokens/$f" > "$VENDOR_DIR/tokens/$f"
done
git -C "$SRC_REPO" rev-parse "$REF" > "$REF_FILE"
echo "Vendor synced → $VENDOR_DIR (ref: $(cat "$REF_FILE"))"

View File

@@ -1,3 +1,9 @@
/* whynot-design tokens & semantic element styles (pinned via
scripts/sync-whynot-design.sh; see .whynot-design-ref).
Must precede the Tailwind import so the @import url(...) for IBM Plex
ends up at the top of the generated bundle. */
@import "./vendor/whynot-design/colors_and_type.css";
@import "tailwindcss"; @import "tailwindcss";
/* Explicit content sources. Without these, Tailwind's automatic detection /* Explicit content sources. Without these, Tailwind's automatic detection
@@ -7,44 +13,88 @@
template dirs copied in the Dockerfile `assets` stage. */ template dirs copied in the Dockerfile `assets` stage. */
@source "../../vergabe_teilnahme/templates"; @source "../../vergabe_teilnahme/templates";
/* whynot tokens → Tailwind theme. Exposes utilities like bg-paper, text-ink,
border-line, bg-paper-2, text-ink-3, … */
@theme { @theme {
--color-brand-50: #f0f4ff; --color-ink: #0A0A0A;
--color-brand-100: #dce7ff; --color-ink-2: #1F1F1F;
--color-brand-500: #3b5bdb; --color-ink-3: #5C5C5C;
--color-brand-600: #2f4ac7; --color-ink-4: #8A8A8A;
--color-brand-700: #2541b2; --color-ink-5: #B5B5B3;
--color-brand-900: #152d99; --color-line: #E5E5E2;
--color-line-strong: #C9C9C5;
--color-line-soft: #F0F0EC;
--color-paper: #FFFFFF;
--color-paper-2: #FAFAF7;
--color-paper-3: #F4F4EF;
--color-hi: #FFE14A;
--color-hi-2: #FFD400;
--color-hi-ink: #1A1500;
/* Backwards-compat aliases for legacy `brand-*` utility usage in templates.
Keeps Phase 1 a tokens-only swap; templates can migrate to ink/paper at
leisure. Map blue-brand scale onto the whynot ink ramp. */
--color-brand-50: #FAFAF7;
--color-brand-100: #F4F4EF;
--color-brand-500: #0A0A0A;
--color-brand-600: #1F1F1F;
--color-brand-700: #0A0A0A;
--color-brand-900: #0A0A0A;
}
/* Off-spec — vergabe-local until whynot-design defines a canonical
destructive color. See history/2026-05-23-whynot-design-cross-framework-analysis.md
§4 for context. */
:root {
--danger: #B22222;
--danger-fg: #FFFFFF;
} }
@layer base { @layer base {
/* German-app base resets */
html { html {
font-family: ui-sans-serif, system-ui, sans-serif; font-family: var(--ff-sans, ui-sans-serif), system-ui, sans-serif;
} }
} }
@layer components { @layer components {
.card { @apply bg-white rounded-xl border border-slate-200 shadow-sm p-6; } /* Cards / sheets — whynot: no shadow, hairline border */
.btn-primary { @apply bg-brand-500 text-white px-4 py-2 rounded-lg hover:bg-brand-600 transition-colors; } .card { @apply bg-paper rounded border border-line p-6; }
.btn-secondary { @apply bg-white text-slate-700 border border-slate-300 px-4 py-2 rounded-lg hover:bg-slate-50; }
.btn-danger { @apply bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700; } /* Buttons — whynot: 3 variants + off-spec danger */
.btn-ghost { @apply text-slate-600 px-3 py-2 rounded-lg hover:bg-slate-100; } .btn-primary { @apply bg-ink text-paper px-4 py-2 rounded hover:bg-ink-2 transition-colors; }
.field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-slate-100 last:border-0; } .btn-secondary { @apply bg-paper text-ink border border-line px-4 py-2 rounded hover:bg-paper-2 transition-colors; }
.field-label { @apply text-sm font-medium text-slate-500 col-span-1; } .btn-ghost { @apply text-ink-3 px-3 py-2 rounded hover:bg-paper-2; }
.field-value { @apply text-sm text-slate-900 col-span-2; } .btn-danger { background: var(--danger); color: var(--danger-fg); @apply px-4 py-2 rounded transition-colors; }
.phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } .btn-danger:hover { filter: brightness(0.92); }
.phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-slate-200 text-slate-500; }
.phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-brand-500 text-white; } /* Field-row — label/value grid */
.phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-green-500 text-white; } .field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-line-soft last:border-0; }
.phase-warn { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-amber-400 text-amber-900; } .field-label { @apply text-sm font-medium text-ink-3 col-span-1; }
.section-title { @apply text-base font-semibold text-slate-900 mb-4; } .field-value { @apply text-sm text-ink col-span-2; }
.page-title { @apply text-2xl font-bold text-slate-900; }
.form-input { @apply w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent; } /* Phase indicators — vergabe semantics (todo/active/done/warn), translated
.form-label { @apply block text-sm font-medium text-slate-700 mb-1; } into whynot palette. `phase-warn` uses --hi (annotation yellow). */
.table-base { @apply w-full text-sm text-left; } .phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; }
.table-header { @apply bg-slate-50 text-slate-500 font-medium text-xs uppercase tracking-wide; } .phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-paper-3 text-ink-4; }
.table-row { @apply border-t border-slate-100 hover:bg-slate-50 transition-colors; } .phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink text-paper; }
.sidebar-link { @apply flex items-center px-3 py-2 rounded-lg text-sm text-slate-700 hover:bg-slate-100 transition-colors; } .phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink-3 text-paper; }
.sidebar-link-active { @apply bg-brand-50 text-brand-700 font-medium; } .phase-warn { background: var(--hi); color: var(--hi-ink); @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; }
.sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-slate-500 uppercase tracking-wide hover:text-slate-700; }
/* Titles / sections */
.section-title { @apply text-base font-semibold text-ink mb-4; }
.page-title { @apply text-2xl font-medium text-ink tracking-tight; }
/* Forms */
.form-input { @apply w-full rounded border border-line px-3 py-2 text-sm bg-paper focus:outline-none focus:border-ink transition-colors; }
.form-label { @apply block text-sm font-medium text-ink-2 mb-1; }
/* Tables */
.table-base { @apply w-full text-sm text-left; }
.table-header { @apply bg-paper-2 text-ink-3 font-medium text-xs uppercase tracking-wide; }
.table-row { @apply border-t border-line-soft hover:bg-paper-2 transition-colors; }
/* Sidebar */
.sidebar-link { @apply flex items-center px-3 py-2 rounded text-sm text-ink-2 hover:bg-paper-2 transition-colors; }
.sidebar-link-active { @apply bg-paper text-ink font-medium; box-shadow: inset 0 0 0 1px var(--line); }
.sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-ink-4 uppercase tracking-wide hover:text-ink-2; }
} }

View File

@@ -0,0 +1 @@
9419f166ce395858f55b10a5c72268a1fe9fc9d2

View File

@@ -0,0 +1,273 @@
/* ============================================================
WhyNot Design System — Colors & Type
------------------------------------------------------------
Neutral, mostly black/white. Color is used SPARINGLY — only
one warm accent (annotation yellow) borrowed from the LEGO
brick in the logo. The system favours light grey wireframe
artefacts over heavy fills.
============================================================ */
/* ---------- Webfonts (Google Fonts, see /fonts for offline) ---------- */
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:ital,wght@0,400;0,500;1,400&display=swap");
:root {
/* ---------- Base palette: neutrals ---------- */
--ink: #0A0A0A; /* near-black, the only "fill" most of the time */
--ink-2: #1F1F1F;
--ink-3: #5C5C5C;
--ink-4: #8A8A8A;
--ink-5: #B5B5B3; /* placeholder text, wireframe labels */
--line: #E5E5E2; /* default 1px wireframe rule */
--line-strong: #C9C9C5; /* dividers between sections */
--line-soft: #F0F0EC; /* hairline within a card */
--paper: #FFFFFF; /* canvas */
--paper-2: #FAFAF7; /* sheet, dim canvas */
--paper-3: #F4F4EF; /* recessed surface, code block bg */
/* ---------- Foreground / background semantic ---------- */
--fg-1: var(--ink);
--fg-2: var(--ink-3);
--fg-3: var(--ink-4);
--fg-mute: var(--ink-5);
--fg-on-dark: #FAFAF7;
--bg-1: var(--paper);
--bg-2: var(--paper-2);
--bg-3: var(--paper-3);
--bg-invert: var(--ink);
--border: var(--line);
--border-strong: var(--line-strong);
--border-soft: var(--line-soft);
/* ---------- The single accent: annotation yellow ---------- */
/* Lifted from the LEGO brick. Used as highlighter, "draft"
stamp, signal-marker. Never as a button fill. */
--hi: #FFE14A;
--hi-2: #FFD400;
--hi-ink: #1A1500; /* text on yellow */
/* ---------- Status (for prototype lifecycle, signal strength) ---------- */
/* Kept deliberately desaturated so they read as labels, not UI. */
--status-raw: #B5B5B3; /* S0 — no signal */
--status-weak: #8A8A8A; /* S1 — weak signal */
--status-medium: #5C5C5C; /* S2 — medium signal */
--status-strong: #0A0A0A; /* S3 — strong signal */
--status-commercial: #FFD400; /* S4 — commercial */
/* ---------- Type families ---------- */
--ff-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
--ff-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
--ff-serif: "IBM Plex Serif", "Iowan Old Style", Georgia, serif;
/* ---------- Type scale (modular, ~1.2) ---------- */
--fs-xs: 11px;
--fs-sm: 13px;
--fs-base: 15px;
--fs-md: 17px;
--fs-lg: 20px;
--fs-xl: 24px;
--fs-2xl: 32px;
--fs-3xl: 44px;
--fs-4xl: 64px;
--fs-5xl: 96px;
--lh-tight: 1.05;
--lh-snug: 1.25;
--lh-base: 1.5;
--lh-loose: 1.7;
--tr-tight: -0.02em;
--tr-snug: -0.01em;
--tr-base: 0em;
--tr-mono: 0.02em;
--tr-label: 0.08em; /* uppercase eyebrow labels */
/* ---------- Spacing (4px base) ---------- */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 24px;
--sp-6: 32px;
--sp-7: 48px;
--sp-8: 64px;
--sp-9: 96px;
--sp-10: 128px;
/* ---------- Radii — small, mostly square ---------- */
--r-0: 0px;
--r-1: 2px;
--r-2: 4px;
--r-3: 8px;
--r-pill: 999px;
/* ---------- Elevation — almost none. This is a wireframe system. ---------- */
--shadow-0: none;
--shadow-1: 0 1px 0 var(--line);
--shadow-2: 0 1px 0 var(--line-strong);
--shadow-3: 0 4px 12px -6px rgba(10,10,10,0.10);
}
/* ============================================================
Semantic element styles
============================================================ */
html {
font-family: var(--ff-sans);
font-size: var(--fs-base);
line-height: var(--lh-base);
color: var(--fg-1);
background: var(--bg-1);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-feature-settings: "ss01", "cv11";
text-wrap: pretty;
}
/* ---------- Headings ---------- */
h1, .h1 {
font: 600 var(--fs-3xl)/var(--lh-tight) var(--ff-sans);
letter-spacing: var(--tr-tight);
margin: 0 0 var(--sp-5);
color: var(--fg-1);
}
h2, .h2 {
font: 500 var(--fs-2xl)/var(--lh-snug) var(--ff-sans);
letter-spacing: var(--tr-snug);
margin: 0 0 var(--sp-4);
}
h3, .h3 {
font: 500 var(--fs-xl)/var(--lh-snug) var(--ff-sans);
letter-spacing: var(--tr-snug);
margin: 0 0 var(--sp-3);
}
h4, .h4 {
font: 500 var(--fs-lg)/var(--lh-snug) var(--ff-sans);
margin: 0 0 var(--sp-2);
}
h5, .h5 {
font: 500 var(--fs-md)/var(--lh-snug) var(--ff-sans);
margin: 0 0 var(--sp-2);
}
/* ---------- Display (for hero / title slides) ---------- */
.display-1 {
font: 300 var(--fs-5xl)/0.95 var(--ff-sans);
letter-spacing: -0.035em;
color: var(--fg-1);
}
.display-2 {
font: 400 var(--fs-4xl)/1.0 var(--ff-sans);
letter-spacing: var(--tr-tight);
}
/* ---------- Body ---------- */
p {
margin: 0 0 var(--sp-4);
line-height: var(--lh-base);
color: var(--fg-1);
}
.lead {
font-size: var(--fs-md);
line-height: 1.55;
color: var(--fg-2);
}
small, .small {
font-size: var(--fs-sm);
color: var(--fg-2);
}
/* ---------- Eyebrow / uppercase labels (very common in this system) ---------- */
.eyebrow,
.label {
font: 500 var(--fs-xs)/1.2 var(--ff-mono);
letter-spacing: var(--tr-label);
text-transform: uppercase;
color: var(--fg-3);
}
/* ---------- Code / mono ---------- */
code, kbd, samp, pre, .mono {
font-family: var(--ff-mono);
font-size: 0.92em;
letter-spacing: var(--tr-mono);
}
code {
background: var(--bg-3);
padding: 1px 6px;
border-radius: var(--r-1);
color: var(--ink-2);
}
pre {
background: var(--bg-3);
border: 1px solid var(--border);
padding: var(--sp-4);
overflow-x: auto;
border-radius: var(--r-2);
font-size: var(--fs-sm);
line-height: var(--lh-snug);
}
pre code { background: none; padding: 0; }
/* ---------- Editorial serif moments ---------- */
.serif { font-family: var(--ff-serif); }
.serif-quote {
font: 400 italic var(--fs-xl)/1.4 var(--ff-serif);
color: var(--fg-2);
}
/* ---------- Links ---------- */
a {
color: var(--fg-1);
text-decoration: underline;
text-decoration-color: var(--border-strong);
text-underline-offset: 3px;
text-decoration-thickness: 1px;
transition: text-decoration-color 120ms ease, color 120ms ease;
}
a:hover {
text-decoration-color: var(--fg-1);
}
/* ---------- HR ---------- */
hr {
border: 0;
border-top: 1px solid var(--border);
margin: var(--sp-5) 0;
}
/* ---------- Highlighter (the one place yellow appears in body copy) ---------- */
mark, .mark {
background: var(--hi);
color: var(--hi-ink);
padding: 0 2px;
}
/* ---------- Tables (used in templates) ---------- */
table {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-sm);
}
th, td {
text-align: left;
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--border);
}
th {
font-weight: 500;
color: var(--fg-2);
font-family: var(--ff-mono);
font-size: var(--fs-xs);
letter-spacing: var(--tr-label);
text-transform: uppercase;
}
/* ---------- Selection ---------- */
::selection { background: var(--hi); color: var(--hi-ink); }

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://design-tokens.github.io/community-group/format/",
"ink": { "value": "#0A0A0A", "type": "color", "comment": "Near-black. The only fill most of the time." },
"ink-2": { "value": "#1F1F1F", "type": "color" },
"ink-3": { "value": "#5C5C5C", "type": "color" },
"ink-4": { "value": "#8A8A8A", "type": "color" },
"ink-5": { "value": "#B5B5B3", "type": "color", "comment": "Placeholder text, wireframe labels." },
"line": { "value": "#E5E5E2", "type": "color", "comment": "Default 1px wireframe rule." },
"line-strong": { "value": "#C9C9C5", "type": "color" },
"line-soft": { "value": "#F0F0EC", "type": "color" },
"paper": { "value": "#FFFFFF", "type": "color" },
"paper-2": { "value": "#FAFAF7", "type": "color" },
"paper-3": { "value": "#F4F4EF", "type": "color" },
"hi": { "value": "#FFE14A", "type": "color", "comment": "Annotation yellow. Highlighter only, never a button fill." },
"hi-2": { "value": "#FFD400", "type": "color" },
"hi-ink": { "value": "#1A1500", "type": "color", "comment": "Text on yellow." },
"status-raw": { "value": "#B5B5B3", "type": "color", "comment": "S0 — no signal" },
"status-weak": { "value": "#8A8A8A", "type": "color", "comment": "S1 — weak signal" },
"status-medium": { "value": "#5C5C5C", "type": "color", "comment": "S2 — medium signal" },
"status-strong": { "value": "#0A0A0A", "type": "color", "comment": "S3 — strong signal" },
"status-commercial": { "value": "#FFD400", "type": "color", "comment": "S4 — commercial" }
}

View File

@@ -0,0 +1,6 @@
{
"comment": "Manifest pointing at the three token files. Source-of-truth for any future Style Dictionary build.",
"colors": "./colors.json",
"type": "./type.json",
"spacing": "./spacing.json"
}

View File

@@ -0,0 +1,28 @@
{
"$schema": "https://design-tokens.github.io/community-group/format/",
"spacing": {
"1": { "value": "4px", "type": "dimension" },
"2": { "value": "8px", "type": "dimension" },
"3": { "value": "12px", "type": "dimension" },
"4": { "value": "16px", "type": "dimension" },
"5": { "value": "24px", "type": "dimension" },
"6": { "value": "32px", "type": "dimension" },
"7": { "value": "48px", "type": "dimension" },
"8": { "value": "64px", "type": "dimension" },
"9": { "value": "96px", "type": "dimension" },
"10": { "value": "128px", "type": "dimension" }
},
"radius": {
"0": { "value": "0px", "type": "dimension" },
"1": { "value": "2px", "type": "dimension" },
"2": { "value": "4px", "type": "dimension" },
"3": { "value": "8px", "type": "dimension" },
"pill": { "value": "999px", "type": "dimension" }
},
"shadow": {
"0": { "value": "none", "type": "shadow" },
"1": { "value": "0 1px 0 #E5E5E2", "type": "shadow" },
"2": { "value": "0 1px 0 #C9C9C5", "type": "shadow" },
"3": { "value": "0 4px 12px -6px rgba(10,10,10,0.10)", "type": "shadow", "comment": "Floating elements only." }
}
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "https://design-tokens.github.io/community-group/format/",
"family": {
"sans": { "value": "\"IBM Plex Sans\", ui-sans-serif, system-ui, sans-serif", "type": "fontFamily" },
"mono": { "value": "\"IBM Plex Mono\", ui-monospace, \"SF Mono\", Menlo, monospace", "type": "fontFamily" },
"serif": { "value": "\"IBM Plex Serif\", \"Iowan Old Style\", Georgia, serif", "type": "fontFamily" }
},
"size": {
"xs": { "value": "11px", "type": "dimension" },
"sm": { "value": "13px", "type": "dimension" },
"base": { "value": "15px", "type": "dimension" },
"md": { "value": "17px", "type": "dimension" },
"lg": { "value": "20px", "type": "dimension" },
"xl": { "value": "24px", "type": "dimension" },
"2xl": { "value": "32px", "type": "dimension" },
"3xl": { "value": "44px", "type": "dimension" },
"4xl": { "value": "64px", "type": "dimension" },
"5xl": { "value": "96px", "type": "dimension" }
},
"lineHeight": {
"tight": { "value": 1.05, "type": "number" },
"snug": { "value": 1.25, "type": "number" },
"base": { "value": 1.5, "type": "number" },
"loose": { "value": 1.7, "type": "number" }
},
"tracking": {
"tight": { "value": "-0.02em", "type": "dimension" },
"snug": { "value": "-0.01em", "type": "dimension" },
"base": { "value": "0em", "type": "dimension" },
"mono": { "value": "0.02em", "type": "dimension" },
"label": { "value": "0.08em", "type": "dimension", "comment": "Uppercase eyebrow labels." }
}
}

33
uv.lock generated
View File

@@ -362,38 +362,15 @@ wheels = [
[[package]] [[package]]
name = "issue-core" name = "issue-core"
version = "0.2.0" version = "0.2.0"
source = { directory = "../issue-core" } source = { registry = "https://gitea.coulomb.social/api/packages/coulomb/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "requests" }, { name = "requests" },
] ]
sdist = { url = "http://gitea.coulomb.social/api/packages/coulomb/pypi/files/issue-core/0.2.0/issue_core-0.2.0.tar.gz", hash = "sha256:cd456ccafdf540f02f7f8b1326e28f8acebcfa0476f3ea2245bd2a5230d85a8d" }
[package.metadata] wheels = [
requires-dist = [ { url = "http://gitea.coulomb.social/api/packages/coulomb/pypi/files/issue-core/0.2.0/issue_core-0.2.0-py3-none-any.whl", hash = "sha256:f98b90a13c787095834dbc7ff8b14057718884e01a51c6b13206e438ba1d5f90" },
{ name = "black", marker = "extra == 'dev'", specifier = ">=22.0" },
{ name = "click", specifier = ">=8.0.0" },
{ name = "fastapi", marker = "extra == 'api'", specifier = ">=0.110,<1.0" },
{ name = "fastapi", marker = "extra == 'dev'", specifier = ">=0.110,<1.0" },
{ name = "flake8", marker = "extra == 'dev'", specifier = ">=4.0" },
{ name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27" },
{ name = "isort", marker = "extra == 'dev'", specifier = ">=5.0" },
{ name = "jira", marker = "extra == 'jira'", specifier = ">=3.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=0.900" },
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=2.0" },
{ name = "pydantic", marker = "extra == 'api'", specifier = ">=2.0,<3.0" },
{ name = "pydantic", marker = "extra == 'dev'", specifier = ">=2.0,<3.0" },
{ name = "pygithub", marker = "extra == 'github'", specifier = ">=1.55" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=6.0" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=2.0" },
{ name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.0" },
{ name = "python-dateutil", specifier = ">=2.8.0" },
{ name = "requests", specifier = ">=2.25.0" },
{ name = "requests", marker = "extra == 'gitea'", specifier = ">=2.25.0" },
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">=4.0" },
{ name = "sphinx-click", marker = "extra == 'docs'", specifier = ">=3.0" },
{ name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = ">=1.0" },
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'api'", specifier = ">=0.27,<1.0" },
] ]
[[package]] [[package]]
@@ -791,7 +768,7 @@ requires-dist = [
{ name = "django", specifier = ">=5.2" }, { name = "django", specifier = ">=5.2" },
{ name = "django-storages", specifier = ">=1.14" }, { name = "django-storages", specifier = ">=1.14" },
{ name = "gunicorn", specifier = ">=22.0" }, { name = "gunicorn", specifier = ">=22.0" },
{ name = "issue-core", directory = "../issue-core" }, { name = "issue-core", specifier = ">=0.2,<0.3", index = "https://gitea.coulomb.social/api/packages/coulomb/pypi/simple/" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2" },
{ name = "python-decouple", specifier = ">=3.8" }, { name = "python-decouple", specifier = ">=3.8" },
{ name = "whitenoise", specifier = ">=6.7" }, { name = "whitenoise", specifier = ">=6.7" },

View File

@@ -8,7 +8,7 @@
<link rel="stylesheet" href="{% static 'dist/main.css' %}"> <link rel="stylesheet" href="{% static 'dist/main.css' %}">
<script src="{% static 'vendor/alpinejs/alpine.min.js' %}" defer></script> <script src="{% static 'vendor/alpinejs/alpine.min.js' %}" defer></script>
</head> </head>
<body class="bg-slate-50 min-h-screen"> <body class="bg-paper-2 min-h-screen text-ink">
{% include "partials/topbar.html" %} {% include "partials/topbar.html" %}
<div class="flex h-[calc(100vh-56px)]"> <div class="flex h-[calc(100vh-56px)]">

50
wiki/DesignSystem.md Normal file
View File

@@ -0,0 +1,50 @@
# Design System
vergabe-teilnahme nutzt das `whynot-design`-System (gitea
`whynot/whynot-design`) als visuelle Basis.
## Phase 1 — Tokens + CSS (aktiv, ab WP-0017)
- Vendored CSS unter `static/src/vendor/whynot-design/`.
- Sync via `make sync-whynot-design` (Skript: `scripts/sync-whynot-design.sh`).
- Gepinnter Commit steht in `static/src/vendor/whynot-design/.whynot-design-ref`.
- `static/src/main.css` importiert die Vendor-CSS und mappt die whynot-Tokens
in den Tailwind-`@theme`-Block: `bg-ink`, `bg-paper`, `text-ink-3`,
`border-line` usw. sind als Utility-Klassen verfügbar.
- Legacy `bg-brand-*` / `text-brand-*` Utilities sind weiterhin nutzbar; sie
sind als Aliasse auf die ink/paper-Skala gemappt, damit Page-Templates
nicht in einer großen Migration mitgezogen werden müssen.
## Phase 2 — Komponenten (offen)
Adoption der whynot-Komponenten erfolgt sobald upstream Lit Web Components
und die fehlenden Atome (`Card`, `Modal`, `Input`, `Table`, `Toast`)
ausliefert. Eigener Workplan wird zu diesem Zeitpunkt angelegt.
## Lokale Abweichungen vom whynot-System
Dokumentiert direkt in `static/src/main.css`:
- **`.btn-danger`** nutzt ein Off-Spec-Rot (`#B22222`, `--danger`-Variable).
whynot definiert aktuell keine destruktive Farbe; vergabe-Nutzung erfordert
sie für Löschen-Aktionen. Wird zurückgebaut, sobald upstream eine
kanonische Lösung definiert.
## Visuelle Hausregeln aus whynot übernommen
- Mostly Black & White; gelber Akzent (`--hi: #FFE14A`) nur als Highlighter /
Stamp / S4-Signal — nie als Button-Fill oder Hero-Hintergrund.
- 1px-Hairlines (`var(--line)` / `border-line`), großzügiger Weißraum,
Monospace-Eyebrow-Labels.
- Keine Schatten auf Cards; nur Popovers bekommen einen weichen 412px-Shadow.
- 04px Border-Radius für Cards/Sheets; 8px nur für große Modale; Pill nur
für Tag-Capsules.
- IBM Plex Sans / Mono / Serif via Google-Fonts (`@import url(...)` in der
Vendor-CSS). Build-Container und Browser brauchen Internet-Zugriff zu
Google Fonts. Air-gapped Deployment würde self-hosting erfordern.
## Hintergrund
- Strategie-Analyse + Komponenten-Lücken-Inventar:
`history/2026-05-23-whynot-design-cross-framework-analysis.md`.
- Adoption-Workplan: `workplans/WP-0017-whynot-design-tokens.md`.

View File

@@ -4,6 +4,7 @@ title: Projektgerüst — Django-Setup, Tailwind, Dev-Stack
status: done status: done
phase: 1-of-12 phase: 1-of-12
created: "2026-05-08" created: "2026-05-08"
domain: communication
--- ---
# WP-0001 — Projektgerüst # WP-0001 — Projektgerüst

View File

@@ -5,6 +5,7 @@ status: done
phase: 2-of-12 phase: 2-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0001 depends_on: WP-0001
domain: communication
--- ---
# WP-0002 — Fachmodelle # WP-0002 — Fachmodelle

View File

@@ -5,6 +5,7 @@ status: done
phase: 3-of-12 phase: 3-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0002 depends_on: WP-0002
domain: communication
--- ---
# WP-0003 — Basis-UI # WP-0003 — Basis-UI

View File

@@ -5,6 +5,7 @@ status: done
phase: 4-of-12 phase: 4-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0003 depends_on: WP-0003
domain: communication
--- ---
# WP-0004 — Dashboard und Ausschreibungen-CRUD # WP-0004 — Dashboard und Ausschreibungen-CRUD

View File

@@ -5,6 +5,7 @@ status: done
phase: 5-of-12 phase: 5-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0004 depends_on: WP-0004
domain: communication
--- ---
# WP-0005 — Lose und Anforderungen # WP-0005 — Lose und Anforderungen

View File

@@ -5,6 +5,7 @@ status: done
phase: 6-of-12 phase: 6-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0005 depends_on: WP-0005
domain: communication
--- ---
# WP-0006 — Aufgaben und Bieterfragen # WP-0006 — Aufgaben und Bieterfragen

View File

@@ -5,6 +5,7 @@ status: done
phase: 7-of-12 phase: 7-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0006 depends_on: WP-0006
domain: communication
--- ---
# WP-0007 — Dokumentenmanagement # WP-0007 — Dokumentenmanagement

View File

@@ -5,6 +5,7 @@ status: done
phase: 8-of-12 phase: 8-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0007 depends_on: WP-0007
domain: communication
--- ---
# WP-0008 — Preise und Marktpreisauswertung # WP-0008 — Preise und Marktpreisauswertung

View File

@@ -5,6 +5,7 @@ status: done
phase: 9-of-12 phase: 9-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0008 depends_on: WP-0008
domain: communication
--- ---
# WP-0009 — Abgabe (Phase 6/7) und Nachbetrachtung (Phase 8) # WP-0009 — Abgabe (Phase 6/7) und Nachbetrachtung (Phase 8)

View File

@@ -5,6 +5,7 @@ status: done
phase: 10-of-12 phase: 10-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0009 depends_on: WP-0009
domain: communication
--- ---
# WP-0010 — Subunternehmer, Partner und Bibliothek # WP-0010 — Subunternehmer, Partner und Bibliothek

View File

@@ -5,6 +5,7 @@ status: done
phase: 11-of-12 phase: 11-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0010 depends_on: WP-0010
domain: communication
--- ---
# WP-0011 — Marktbegleiter-Analyse # WP-0011 — Marktbegleiter-Analyse

View File

@@ -5,6 +5,7 @@ status: done
phase: 12-of-12 phase: 12-of-12
created: "2026-05-08" created: "2026-05-08"
depends_on: WP-0011 depends_on: WP-0011
domain: communication
--- ---
# WP-0012 — Querschnitt # WP-0012 — Querschnitt

View File

@@ -5,6 +5,7 @@ status: done
phase: 13-of-13 phase: 13-of-13
created: "2026-05-14" created: "2026-05-14"
depends_on: WP-0012 depends_on: WP-0012
domain: communication
--- ---
# WP-0013 — Feedback-Bugs # WP-0013 — Feedback-Bugs

View File

@@ -5,6 +5,7 @@ status: done
phase: 14-of-n phase: 14-of-n
created: "2026-05-14" created: "2026-05-14"
depends_on: WP-0013 depends_on: WP-0013
domain: communication
--- ---
# WP-0014 — Aufgaben-Phasenzuordnung und Scores # WP-0014 — Aufgaben-Phasenzuordnung und Scores

View File

@@ -5,6 +5,7 @@ status: done
phase: 15-of-n phase: 15-of-n
created: "2026-05-14" created: "2026-05-14"
depends_on: WP-0014 depends_on: WP-0014
domain: communication
--- ---
# WP-0015 — Aufgaben: Verknüpfungen, implizite Fälligkeit, Issue-Facade # WP-0015 — Aufgaben: Verknüpfungen, implizite Fälligkeit, Issue-Facade

View File

@@ -5,6 +5,7 @@ status: done
phase: 16-of-n phase: 16-of-n
created: "2026-05-14" created: "2026-05-14"
depends_on: WP-0015 depends_on: WP-0015
domain: communication
--- ---
# WP-0016 — Issue-Facade Integration # WP-0016 — Issue-Facade Integration

View File

@@ -0,0 +1,399 @@
---
id: WP-0017
title: whynot-design Adoption — Phase 1 (Tokens + CSS)
status: finished
phase: 17-of-n
created: "2026-05-23"
depends_on: WP-0016
domain: communication
---
# WP-0017 — whynot-design Adoption · Phase 1 (Tokens + CSS)
Übernahme des `whynot-design`-Systems
(`~/whynot-design`, gitea `whynot/whynot-design`) in vergabe-teilnahme in
**Phase 1: Tokens + CSS-Variablen**. Keine Komponenten-Portierung. Bestehende
Django-Partials und Tailwind-Komponentenklassen bleiben markup-seitig unverändert
und werden durch den CSS-Variablen-Tausch automatisch retoniert.
**Strategiebeschluss vom 2026-05-23:** zweistufige Adoption.
- Phase 1 (dieser Workplan): nur Tokens + CSS. whynot-spezifische Komponenten
(Lit Web Components) sind upstream in Arbeit und werden separat adoptiert.
- Phase 2 (eigener Workplan, kommt später): Komponenten-Adoption sobald
whynot-design die fehlenden Atome (`Card`, `Modal`, `Input`, `Table`, `Toast`)
als Lit Web Components ausliefert.
**Hintergrund:** ausführliche Analyse der Strategie-Optionen, Komponenten-
Inventar und Lücken-Liste in
`history/2026-05-23-whynot-design-cross-framework-analysis.md`.
**Designentscheidungen für Phase 1:**
- Distribution: **Vendoring** über Sync-Skript (kein npm/gitea SSH im Docker-Build).
- Rollout: **Big-Bang** — eine PR ersetzt die komplette `brand-*`-Palette
vergabe-weit. Markup-Änderungen sind nicht erforderlich.
- `btn-danger`: **Off-Spec-Rot** behalten (lokale `--danger`-Variable), bis
whynot-design eine kanonische Lösung definiert.
**Pinned upstream:** commit `9419f166ce395858f55b10a5c72268a1fe9fc9d2`
(Stand 2026-05-23; einziger Commit im whynot-design-Repo).
---
```task
id: WP-0017-T01
title: Vendor-Sync-Skript + initiale Vendor-Übernahme
status: done
Ziel: deterministisches Pull der whynot-design CSS-/Token-Quellen aus einem
gepinnten Commit nach `static/src/vendor/whynot-design/`, ohne Docker-Build
SSH-Zugang zu gitea zu geben.
**`scripts/sync-whynot-design.sh`** — neu anlegen:
```bash
#!/usr/bin/env bash
# Synchronisiert die Vendor-Kopie des whynot-Design-Systems aus einem gepinnten
# Upstream-Commit. Quelle: ~/whynot-design (Worktree) oder Klone aus gitea.
#
# Aufruf: ./scripts/sync-whynot-design.sh [<commit-or-ref>]
# Default: liest .whynot-design-ref aus dem Vendor-Verzeichnis.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VENDOR_DIR="$ROOT/static/src/vendor/whynot-design"
REF_FILE="$VENDOR_DIR/.whynot-design-ref"
SRC_REPO="${WHYNOT_DESIGN_SRC:-$HOME/whynot-design}"
REF="${1:-}"
if [[ -z "$REF" && -f "$REF_FILE" ]]; then
REF="$(cat "$REF_FILE")"
fi
if [[ -z "$REF" ]]; then
echo "Usage: $0 <commit-or-ref> (or write a ref to $REF_FILE)" >&2
exit 2
fi
if [[ ! -d "$SRC_REPO/.git" ]]; then
echo "Quelle nicht gefunden: $SRC_REPO" >&2
echo "Setze WHYNOT_DESIGN_SRC oder klone gitea:whynot/whynot-design dorthin." >&2
exit 1
fi
mkdir -p "$VENDOR_DIR/tokens"
git -C "$SRC_REPO" show "$REF:src/styles/colors_and_type.css" \
> "$VENDOR_DIR/colors_and_type.css"
for f in colors.json type.json spacing.json index.json; do
git -C "$SRC_REPO" show "$REF:tokens/$f" > "$VENDOR_DIR/tokens/$f"
done
git -C "$SRC_REPO" rev-parse "$REF" > "$REF_FILE"
echo "Vendor synced → $VENDOR_DIR (ref: $(cat "$REF_FILE"))"
```
Ausführbar machen + initial syncen:
```bash
chmod +x scripts/sync-whynot-design.sh
./scripts/sync-whynot-design.sh 9419f166ce395858f55b10a5c72268a1fe9fc9d2
```
Erwartetes Resultat: `static/src/vendor/whynot-design/colors_and_type.css`,
`tokens/*.json`, `.whynot-design-ref` mit dem Commit-Hash.
**`Makefile`** — Target ergänzen:
```make
.PHONY: sync-whynot-design
sync-whynot-design:
./scripts/sync-whynot-design.sh
```
Commit-Hygiene: der Vendor-Inhalt wird **eingecheckt** (kein `.gitignore`).
Diffs gegen den Vendor sind Teil des Review-Surfaces beim nächsten Bump.
```
```task
id: WP-0017-T02
title: CSS-Integration in static/src/main.css
status: done
Ziel: whynot-Tokens werden global verfügbar, Tailwind-`@theme`-Mapping
exponiert sie als Utility-Klassen, vergabe-spezifisches Brand-Blau entfällt.
**`static/src/main.css`** — vollständig ersetzen (Reihenfolge ist wichtig:
whynot-CSS vor Tailwind, damit `@import url(...)` für IBM Plex am Anfang der
generierten Datei landet):
```css
/* whynot-design Tokens & semantische Element-Styles (gepinnt via
scripts/sync-whynot-design.sh; siehe .whynot-design-ref). */
@import "./vendor/whynot-design/colors_and_type.css";
@import "tailwindcss";
/* Tailwind v4 scannt diese Pfade für Utility-Klassen. Muss synchron mit
der Dockerfile `assets`-Stage bleiben. */
@source "../../vergabe_teilnahme/templates";
/* whynot-Tokens → Tailwind-Theme.
Erlaubt: bg-paper, text-ink, border-line, bg-paper-2, text-ink-3, … */
@theme {
--color-ink: var(--ink);
--color-ink-2: var(--ink-2);
--color-ink-3: var(--ink-3);
--color-ink-4: var(--ink-4);
--color-ink-5: var(--ink-5);
--color-line: var(--line);
--color-line-strong: var(--line-strong);
--color-line-soft: var(--line-soft);
--color-paper: var(--paper);
--color-paper-2: var(--paper-2);
--color-paper-3: var(--paper-3);
--color-hi: var(--hi);
--color-hi-2: var(--hi-2);
--color-hi-ink: var(--hi-ink);
}
/* Off-Spec — vergabe-lokal, bis whynot-design eine kanonische destruktive
Farbe definiert. Siehe history/2026-05-23-whynot-design-cross-framework-analysis.md
§4 "btn-danger". */
:root {
--danger: #B22222;
--danger-fg: #FFFFFF;
}
@layer base {
html {
font-family: var(--ff-sans, ui-sans-serif), system-ui, sans-serif;
}
}
@layer components {
/* Karten / Sheets — whynot: ohne Shadow, hairline border */
.card { @apply bg-paper rounded border border-line p-6; }
/* Buttons — whynot: 3 Varianten + off-spec danger */
.btn-primary { @apply bg-ink text-paper px-4 py-2 rounded hover:bg-ink-2 transition-colors; }
.btn-secondary { @apply bg-paper text-ink border border-line px-4 py-2 rounded hover:bg-paper-2 transition-colors; }
.btn-ghost { @apply text-ink-3 px-3 py-2 rounded hover:bg-paper-2; }
.btn-danger { background: var(--danger); color: var(--danger-fg); @apply px-4 py-2 rounded transition-colors; }
.btn-danger:hover { filter: brightness(0.92); }
/* Field-Row — Label/Value Grid */
.field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-line-soft last:border-0; }
.field-label { @apply text-sm font-medium text-ink-3 col-span-1; }
.field-value { @apply text-sm text-ink col-span-2; }
/* Phasen-Indikatoren — vergabe-Semantik (todo/active/done/warn), in
whynot-Palette übersetzt. `phase-warn` nutzt `--hi` (annotation yellow). */
.phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; }
.phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-paper-3 text-ink-4; }
.phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink text-paper; }
.phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink-3 text-paper; }
.phase-warn { background: var(--hi); color: var(--hi-ink); @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; }
/* Titel / Sektionen */
.section-title { @apply text-base font-semibold text-ink mb-4; }
.page-title { @apply text-2xl font-medium text-ink tracking-tight; }
/* Formulare */
.form-input { @apply w-full rounded border border-line px-3 py-2 text-sm bg-paper focus:outline-none focus:border-ink transition-colors; }
.form-label { @apply block text-sm font-medium text-ink-2 mb-1; }
/* Tabellen */
.table-base { @apply w-full text-sm text-left; }
.table-header { @apply bg-paper-2 text-ink-3 font-medium text-xs uppercase tracking-wide; }
.table-row { @apply border-t border-line-soft hover:bg-paper-2 transition-colors; }
/* Sidebar */
.sidebar-link { @apply flex items-center px-3 py-2 rounded text-sm text-ink-2 hover:bg-paper-2 transition-colors; }
.sidebar-link-active { @apply bg-paper text-ink font-medium; box-shadow: inset 0 0 0 1px var(--line); }
.sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-ink-4 uppercase tracking-wide hover:text-ink-2; }
}
```
**Kommentar zu Border-Radii:** whynot-Hausregel verlangt 04px für Cards/Sheets,
8px nur für große Modale. Tailwind `rounded` (= 4px) erfüllt das. Keine
`rounded-xl`/`rounded-lg` mehr in den Komponenten-Klassen.
**Kommentar zu Shadows:** whynot-Regel "no shadows on cards". `shadow-sm` wurde
aus `.card` entfernt; Sidebar-Active nutzt `inset` border statt Schatten.
**Kommentar zu Fonts:** `colors_and_type.css` zieht IBM Plex via
`@import url("https://fonts.googleapis.com/...")`. Build-Container und Browser
brauchen Internet-Zugriff zu Google Fonts. Für air-gapped Deployment muss das
später durch self-hosting ersetzt werden — ist heute nicht relevant
(coulombcore k3s hat Internet-Egress).
```
```task
id: WP-0017-T03
title: Base-Template — Body-Hintergrund auf whynot-Palette
status: done
**`vergabe_teilnahme/templates/base.html`** — Body-Klasse anpassen:
```diff
- <body class="bg-slate-50 min-h-screen">
+ <body class="bg-paper-2 min-h-screen text-ink">
```
Damit der Default-Text vom whynot-Ink-Ton kommt und der Canvas auf Sheet-Beige
(`#FAFAF7`) liegt, nicht auf Slate-Blau-50.
Keine weiteren Markup-Änderungen in Phase 1. Alle `bg-slate-*`/`text-slate-*`
Utilities in den Page-Templates werden in T05 (Smoke-Test) gesichtet und nur
dann gepatcht, wenn sie visuell brechen — Tailwinds Slate-Skala existiert
weiterhin parallel zur whynot-Palette.
```
```task
id: WP-0017-T04
title: Build + Static-Asset-Prüfung
status: done
Lokaler Build:
```bash
npm ci
npm run build
ls -lh static/dist/main.css
```
Erwartet: `main.css` enthält die whynot-CSS-Variablen am Anfang, gefolgt von
generierten Tailwind-Utilities. Größe steigt moderat (whynot-CSS ≈ 8 KB
unkompressed).
**Sanity-Check** der generierten Utilities:
```bash
grep -c "bg-ink\|bg-paper\|text-ink\|border-line" static/dist/main.css
# Erwartet: > 0 (Beweis, dass @theme-Mapping aktiv ist)
grep -c "color-brand-500\|3b5bdb" static/dist/main.css
# Erwartet: 0 (Alt-Palette komplett raus)
```
Falls eine vergabe-Template-Datei noch `bg-brand-*` o.ä. nutzt: in T05
adressieren (visueller Bruch wird dort sichtbar).
```
```task
id: WP-0017-T05
title: Big-Bang Smoke-Test — visueller Durchlauf aller Hauptseiten
status: done
Dev-Server starten und durch die wichtigsten Views klicken. Bei jedem visuellen
Bruch (Kontrast, weiße Schrift auf weißem Grund, harte Farb-Fremdkörper) eine
kurze Notiz machen und im selben Task patchen.
```bash
docker-compose -f docker-compose.dev.yml up -d
# oder: uv run python manage.py runserver
```
Zu prüfende Views (Mindestliste — Anordnung folgt der Sidebar):
- [ ] Dashboard / Ausschreibungs-Übersicht
- [ ] Ausschreibung Detail (Phasen-Nav sichtbar)
- [ ] Lose-Liste + Detail
- [ ] Aufgaben-Liste
- [ ] Dokumente-Übersicht
- [ ] Preise-Auswertung
- [ ] Partner-Bibliothek
- [ ] Marktbegleiter
- [ ] Nachbetrachtung
- [ ] Feedback-Modal (Trigger via Bug-Button)
- [ ] Freigabe-Modal (auf einem freigabefähigen Objekt)
- [ ] Search-Results (HTMX-Suche in der Top-Bar)
**Typische Brüche, die zu erwarten sind:**
1. Direkt im Template inline gesetzte `bg-blue-*`, `text-blue-*`, `bg-brand-*`
in den whynot-Äquivalenten ersetzen (`bg-ink`, `text-ink`, `bg-hi`).
2. `shadow-sm`/`shadow-md` auf Cards — entfernen (whynot-Regel: keine Shadows
auf Cards, nur auf Popovers).
3. `rounded-lg`/`rounded-xl` auf nicht-Modal-Elementen — auf `rounded` (=4px)
reduzieren.
4. Fokus-Ringe in Brand-Blau — der `.form-input`-Style nutzt jetzt Border-Ink;
Tailwind-Default-Ring kann noch blau sein, ggf. globalen Ring-Reset
ergänzen.
Screenshots der Hauptseiten **vor und nach** dem Swap in
`history/2026-05-23-whynot-design-phase1-screenshots/` ablegen, als
Sichtprüfungs-Beleg.
```
```task
id: WP-0017-T06
title: Doku-Update und Phase-2-Pflock
status: done
**`wiki/`** — neue Datei `wiki/DesignSystem.md` mit knappem Inhalt:
```markdown
# Design System
vergabe-teilnahme nutzt das `whynot-design`-System
(gitea `whynot/whynot-design`) als visuelle Basis.
**Phase 1 (aktuell, ab WP-0017):** nur Tokens + CSS-Variablen, vendoring nach
`static/src/vendor/whynot-design/`. Sync via `make sync-whynot-design`. Aktuell
gepinnter Commit: siehe `static/src/vendor/whynot-design/.whynot-design-ref`.
**Phase 2 (offen):** Komponenten-Adoption sobald upstream Lit Web Components
und die fehlenden Atome (Card, Modal, Input, Table, Toast) ausliefert. Eigener
Workplan wird zu diesem Zeitpunkt angelegt.
**Lokale Abweichungen** vom whynot-System (dokumentiert in `main.css`):
- `btn-danger` mit Off-Spec-Rot (`#B22222`) — whynot definiert keine
destruktive Farbe; vergabe-Nutzung erfordert sie für Löschen-Aktionen.
Wird zurückgebaut, sobald upstream eine kanonische Lösung definiert.
**Hintergrund:** Strategie-Analyse + Komponenten-Lücken in
`history/2026-05-23-whynot-design-cross-framework-analysis.md`.
```
**`CLAUDE.md` / `.claude/rules/stack-and-commands.md`** — Stack-Eintrag
ergänzen um `whynot-design` und den Sync-Befehl:
```markdown
## Stack
- **Language:** Python 3.12 (Django 6), Node 22 (Vite/Tailwind v4)
- **Key deps:** Django, htmx, Alpine.js, Tailwind v4, whynot-design (vendored)
## Dev Commands
# Design-System-Vendor aktualisieren (whynot-design Pin bumpen)
make sync-whynot-design
```
**Workplan-Index** in `workplans/README.md` ist bereits 12-Workplan-zentriert
und veraltet (es gibt jetzt 17). Nicht in diesem WP anpassen — eigener
Aufräum-WP, falls der Index wieder verbindlich werden soll.
```
---
## Nicht in diesem Workplan
- Komponenten-Portierung (React-JSX → Django-Partials) — entfällt; wird durch
upstream Lit Web Components in Phase 2 obsolet.
- `whynot-design-django`-Repo — entfällt aus dem gleichen Grund.
- Neue Atome (Card, Modal, Input, Table, Toast als DS-Komponenten) — kommen
upstream; bis dahin vergabe-lokal über Tailwind-Klassen.
- Visuelle Regression-Tests (Playwright) — wäre sinnvoll, aber nicht
Voraussetzung für Phase 1.
- Markup-Änderungen in Page-Templates über das in T05 Notwendige hinaus.
## Definition of Done
- `static/src/vendor/whynot-design/colors_and_type.css` vorhanden,
`.whynot-design-ref` enthält gepinnten Commit-Hash.
- `npm run build` erfolgreich, `static/dist/main.css` ohne `#3b5bdb`
(Alt-Brand-Blau) und mit aktiven `bg-ink`/`bg-paper`-Utilities.
- Alle 12 Smoke-Test-Views in T05 visuell durchgegangen, Brüche gepatcht,
Screenshots in `history/` abgelegt.
- `wiki/DesignSystem.md` existiert und referenziert History-Artefakt +
Phase-2-Bedingung.