22 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, estimate, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | estimate | must_haves | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-eigene-ausschreibungs-quellen-je-nutzer | 03 | execute | 3 |
|
|
true |
|
|
|
Nach diesem Plan gibt es zwei Seiten:
- Meine Quellen (
/modules/tender-radar/my-sources, fuer jeden mit Modulzugang): mein Postfach, meine Feeds, mein Benachrichtigungsintervall. - Einstellungen (
/modules/tender-radar/settings, nur Administration): Abrufintervall der oeffentlichen Quelle und die plattformweiten Feeds.
Purpose: D-03, D-04 und den urspruenglichen Anlass des Backlog-Punkts abschliessen. Output: Vollstaendige Nutzerseite, reduzierte und rollengepruefte Administrationsseite, angepasstes Zahnrad, Beschriftungen in beiden Sprachen.
Entscheidung zum offenen Punkt 5 (Rollenpruefung). Die Administrationsseite prueft die Rolle des angemeldeten Kontos aus dem bereits vorhandenen Anmelde-Speicher im Browser und zeigt einem normalen Nutzer statt der Bedienelemente einen kurzen Satz mit einem Verweis auf "Meine Quellen". Begruendung: Die verbindliche Absicherung liegt und bleibt im Server — die Endpunkte fuer das Abrufintervall und fuer plattformweite Feeds sind dort rollengeprueft. Die Pruefung im Browser ist ausschliesslich eine Anzeigefrage: sie verhindert, dass jemand Knoepfe sieht, die beim Speichern in eine Fehlermeldung laufen. Genau das war der Anlass des Backlog-Punkts.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md @.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-01-SUMMARY.md @.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-02-SUMMARY.md @.planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md@apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx @apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx @apps/web/src/lib/tender-radar-api.ts @apps/web/src/lib/stores/auth-store.ts
Task 1: "Meine Quellen" wird vollstaendig — Postfach, eigene Feeds, Benachrichtigung apps/web/src/lib/tender-radar-api.ts, apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx, apps/web/src/lib/tender-radar-api.ts (Zeilen 370-560) - Auf "Meine Quellen" stehen drei Abschnitte untereinander: Mein Postfach, Meine Feeds, Benachrichtigung. - Die Feed-Liste dort zeigt ausschliesslich die eigenen Feeds; plattformweite Feeds erscheinen darunter als kurze, nicht bedienbare Aufzaehlung mit dem Hinweis, dass die Administration sie pflegt. - Ein neu angelegter Feed erscheint sofort in der eigenen Liste. - Zu einem plattformweiten Feed gibt es auf dieser Seite keinen Entfernen-Knopf. - Die Auswahl des Benachrichtigungsintervalls speichert und quittiert wie bisher. **Zugriffsschicht (`lib/tender-radar-api.ts`).** Der Typ `RssFeedSource` bekommt das vom Server gelieferte Kennzeichen, ob der Eintrag plattformweit ist. `createRssFeed` bekommt einen zweiten Parameter fuer den Wirkungsbereich (`personal` oder `platform`, Vorgabe `personal`), der in den Rumpf der Anfrage wandert. `listRssFeeds` und `deleteRssFeed` bleiben in ihrer Signatur, ihr Verhalten aendert sich serverseitig.Feed-Liste (RssFeedListForm.tsx). Die Komponente bekommt eine
Eigenschaft scope mit den Werten personal und platform. Sie laedt
weiterhin die eine Liste vom Server und teilt sie selbst auf:
scope="personal": bearbeitbar sind die eigenen Eintraege (anlegen, entfernen). Die plattformweiten Eintraege erscheinen darunter als schlichte Aufzaehlung ohne Knoepfe, mit einem Satz, dass die Administration sie pflegt — so ist auf einen Blick klar, dass niemand bei null anfaengt.scope="platform": bearbeitbar sind die plattformweiten Eintraege; angelegt wird mit dem entsprechenden Wirkungsbereich. Eigene Eintraege anderer Nutzer tauchen hier gar nicht auf, weil der Server sie nicht liefert. Die Fehleranzeige bleibt wie sie ist: eine abgelehnte Adresse zeigt weiterhin die ausfuehrliche Meldung des Servers, nicht einen selbstgebauten Ersatztext. Die Ablehnung wegen erreichter Obergrenze (aus Plan 17-02) laeuft ueber denselben Weg und braucht keine Sonderbehandlung.
Benachrichtigungsintervall (DigestIntervalForm.tsx). Den heute in der
Einstellungsseite eingebetteten Block (Laden beim Aufbau, Auswahlfeld
Taeglich/Woechentlich/Aus, Speichern bei Aenderung, Erfolgs- und Fehlermeldung)
unveraendert in eine eigene Komponente herausloesen. Kein Verhalten aendern,
keine neuen Beschriftungsschluessel — nur verschieben, damit beide Seiten
dieselbe Umsetzung benutzen koennen und die Einstellungsseite in Task 2 sauber
kleiner wird.
Seite (my-sources/page.tsx). Die in Plan 17-01 angelegte Seite um zwei
Abschnitte erweitern: unter "Mein Postfach" folgt "Meine Feeds"
(RssFeedListForm mit scope="personal"), darunter "Benachrichtigung"
(DigestIntervalForm). Aufbau, Abstaende und Trennlinien wie auf der heutigen
Einstellungsseite uebernehmen, damit nichts fremd wirkt. Fuer Konten mit
Administratorrolle unten ein Verweis auf die Administrationsseite; fuer alle
anderen erscheint dieser Verweis nicht. Die Rolle kommt aus dem vorhandenen
Anmelde-Speicher; solange dieser noch nicht befuellt ist, den Verweis
weglassen statt kurz aufblitzen zu lassen.
Beschriftungen (messages/de.json, messages/en.json). Alle neuen
Schluessel im Namensraum tenderRadar unter mySources anlegen und in beiden
Dateien pflegen: Seitentitel, die drei Abschnittsueberschriften, der ehrliche
Hinweis zu D-05 (Ausschreibungen aus den eigenen Quellen erscheinen auch bei
Kollegen desselben Mandanten), der Satz zu den von der Administration
gepflegten Feeds, der Verweis auf die Administrationsseite. Keine Beschriftung
nur auf Deutsch anlegen.
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web type-check
cd /home/vicolab/projects/tessera-ctl && node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const walk=(o,p='')=>Object.entries(o).flatMap(([k,v])=>typeof v==='object'&&v?walk(v,p+k+'.'):[p+k]);const d=walk(de.tenderRadar),e=walk(en.tenderRadar);const miss=d.filter(k=>!e.includes(k)).concat(e.filter(k=>!d.includes(k)));if(miss.length){console.error('Fehlende Uebersetzungen:',miss);process.exit(1)}console.log('i18n vollstaendig:',d.length,'Schluessel')"
Als normaler Nutzer /modules/tender-radar/my-sources oeffnen: drei Abschnitte sind sichtbar. Einen eigenen RSS-Feed anlegen — er erscheint in der eigenen Liste, der plattformweite service.bund.de-Feed steht darunter ohne Entfernen-Knopf. Das Benachrichtigungsintervall auf "Woechentlich" stellen, Seite neu laden, Wert steht noch. Kein Verweis auf die Administrationsseite sichtbar.
Die Typpruefung laeuft fehlerfrei, beide Sprachdateien decken denselben
Schluesselsatz ab. Ein normaler Nutzer kann auf einer Seite Postfach, eigene
Feeds und Benachrichtigungsintervall pflegen und sieht keinen Knopf, der ihm
verwehrt ist.
Modulseite (page.tsx). Das Zahnrad zeigt heute auf die
Administrationsseite. Es zeigt kuenftig auf /modules/tender-radar/my-sources —
das ist die Seite, die jeder Nutzer aufrufen darf, und Administratoren gehen von
dort mit einem Klick weiter. Beschriftung und Vorlesetext des Knopfes
entsprechend anpassen (neuer Schluessel, alter Schluessel bleibt fuer den Verweis
auf der Nutzerseite in Gebrauch, falls passend — sonst entfernen). Den
vorhandenen Kommentar zum absoluten Verweis (die dynamische Einstellungsroute
greift fuer dieses Modul nicht) beibehalten und auf den neuen Zielpfad
anpassen.
Beschriftungen. Neue Schluessel in beiden Sprachdateien anlegen: der
Hinweistext fuer Nutzer ohne Administratorrolle, der Verweistext auf "Meine
Quellen", der Zusatzsatz beim Abschnitt der plattformweiten Feeds, die neue
Zahnrad-Beschriftung. Nicht mehr benutzte Schluessel (settings.emailSectionTitle,
settings.emailSectionBody, settings.notificationsSectionTitle und die
Digest-Schluessel, sofern sie auf die Nutzerseite umziehen) erst entfernen,
nachdem im gesamten apps/web/src nachgesehen wurde, dass sie nirgends mehr
verwendet werden.
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web type-check
cd /home/vicolab/projects/tessera-ctl && node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const walk=(o,p='')=>Object.entries(o).flatMap(([k,v])=>typeof v==='object'&&v?walk(v,p+k+'.'):[p+k]);const d=walk(de.tenderRadar),e=walk(en.tenderRadar);const miss=d.filter(k=>!e.includes(k)).concat(e.filter(k=>!d.includes(k)));if(miss.length){console.error('Fehlende Uebersetzungen:',miss);process.exit(1)}console.log('i18n vollstaendig:',d.length,'Schluessel')"
Als Konto mit Rolle USER /modules/tender-radar/settings direkt in der Adresszeile aufrufen: es erscheinen keine Bedienelemente, sondern der Hinweis samt Verweis. Danach als Administrator dieselbe Adresse: Abrufintervall und plattformweite Feeds sind da, Postfach und Benachrichtigungsintervall nicht mehr. Auf der Modulseite fuehrt das Zahnrad in beiden Faellen nach "Meine Quellen".
Die Einstellungsseite traegt nur noch Abrufintervall und plattformweite Feeds
und ist fuer Konten ohne Administratorrolle leer bis auf Hinweis und Verweis.
Das Zahnrad fuehrt jeden Nutzer auf eine Seite, auf der er etwas einstellen darf.
Beide Sprachdateien decken denselben Schluesselsatz ab.
Wichtig zur Aussagekraft. Die Pruefungen muessen an dem haengen, was der Nutzer sieht: vorhandene beziehungsweise fehlende Beschriftungen und Knoepfe im gerenderten Ergebnis. Nicht pruefen, ob eine Komponente "aufgerufen wurde" — und die erwarteten Texte im Test woertlich hinschreiben, nicht aus derselben Zuordnung ableiten, die auch die Komponente benutzt. Ein Test, der seine Erwartung mit dem Pruefling selbst baut, beweist nichts.
RssFeedListForm.test.tsx. Bestehende Faelle auf die neue Eigenschaft
scope nachziehen (Vorgabe im Test ausdruecklich setzen, nicht raten) und die
drei Faelle aus dem Abschnitt "behavior" ergaenzen. Der bestehende Fall, dass die
ausfuehrliche Servermeldung bei einer gesperrten Adresse angezeigt wird, muss
unveraendert weiterlaufen.
settings-roles.test.tsx (neu). Die drei Rollenfaelle der
Einstellungsseite. Zusaetzlich ein Fall, der belegt, dass die Ueberschriften der
entfernten Abschnitte (Postfach, Benachrichtigung) auf dieser Seite nicht mehr
vorkommen.
my-sources.test.tsx (neu). Die drei Abschnitte sind da; der Verweis auf
die Administrationsseite erscheint nur bei Administratorrolle.
Backlog-Punkt. 2026-08-11-tender-radar-einstellungen-mischen-rollen.md
nach .planning/todos/completed/ verschieben und im Kopf einen kurzen Abschnitt
ergaenzen, wie er geloest wurde: nicht nur die Rollenpruefung aus der
urspruenglichen Skizze, sondern der Umbau auf nutzereigene Quellen, und die
Grundsatzfrage aus Punkt 2 ist mit "eigene nutzerseitige Modulseite" beantwortet
— das ist ab jetzt die Bauform, an der sich DKV-Fleet und kuenftige Module
orientieren.
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/app/(portal)/modules/tender-radar
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/api exec vitest run src/tenders && pnpm --filter @tessera/api type-check && pnpm --filter @tessera/web type-check
test ! -f .planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md && test -f .planning/todos/completed/2026-08-11-tender-radar-einstellungen-mischen-rollen.md
Alle Tests in Web und API laufen gruen, beide Typpruefungen sind sauber, der
Backlog-Punkt liegt im Ordner der erledigten Punkte.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Browser-Anzeige -> Serverpruefung | Die Rollenpruefung im Browser ist eine Anzeigefrage; die verbindliche Pruefung liegt im Server. |
| Nutzerseite -> Endpunkte fuer plattformweite Einstellungen | Die Nutzerseite darf keinen Weg zu plattformweiten Schreibvorgaengen oeffnen. |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-17-14 | Elevation of Privilege | Rollenpruefung im Browser | medium | mitigate | Die Pruefung blendet nur Bedienelemente aus. Die Endpunkte fuer Abrufintervall und plattformweite Feeds bleiben serverseitig rollengeprueft (Plan 17-02, T-17-08). Ein Test belegt, dass die Rolle USER die Abschnitte nicht zu sehen bekommt. |
| T-17-15 | Elevation of Privilege | RssFeedListForm mit scope="personal" |
high | mitigate | Der Wirkungsbereich wandert als Wunsch in die Anfrage; ob er gewaehrt wird, entscheidet ausschliesslich der Server. Ein manipuliertes Frontend erreicht nichts. |
| T-17-16 | Information Disclosure | Kurzes Aufblitzen der Administrationsabschnitte vor dem Laden der Kontodaten | low | mitigate | Solange die Rolle unbekannt ist, wird ein Platzhalter gezeigt; ein Test deckt genau diesen Zustand ab. |
| T-17-17 | Repudiation | Unvollstaendige Uebersetzung | low | mitigate | Ein Pruefschritt vergleicht die Schluesselsaetze beider Sprachdateien und bricht bei Abweichung ab. |
| T-17-SC | Tampering | Paketinstallationen | low | accept | Diese Phase installiert kein neues Paket. |
| </threat_model> |
<success_criteria>
- "Meine Quellen" traegt Postfach, eigene Feeds und Benachrichtigungsintervall.
- Die Einstellungsseite traegt nur Abrufintervall und plattformweite Feeds.
- Ein normaler Nutzer sieht auf der Einstellungsseite keine Bedienelemente mehr, die beim Speichern in eine Fehlermeldung laufen.
- Das Zahnrad fuehrt jeden Nutzer auf eine fuer ihn bedienbare Seite.
- Alle Beschriftungen liegen in Deutsch und Englisch vor.
- Der Backlog-Punkt ist abgeschlossen und die Grundsatzfrage darin beantwortet. </success_criteria>