6.7 KiB
6.7 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14-rss-email-alert-ingestion-module-rollout | 04 | api,web |
|
|
|
|
|
|
|
|
|
~25min | 2026-07-23 | complete |
Phase 14 Plan 04: Denylist Transparency (vergabe24/aumass) Summary
Exposes DENYLISTED_PORTALS (source-registry.ts) via a new GET /modules/tender-radar/denylisted-portals read endpoint carrying canonical direct-link URLs, and renders it in CoverageBanner as an independent "manuell beobachten" block with clickable links — turning the two AGB-prohibited portals from a silent coverage gap into an explicit, actionable hint (UI-06/D-12).
Performance
- Tasks completed: 2 of 2
- Files modified: 6 (1 created, 5 modified)
- API tests: 389/389 passing (was 387/387; +2 new: response-shape + route-order guard)
- Web tests: 148/148 passing (was 144/144; +4 new: CoverageBanner denylist rendering, independence-from-coverage-note, fail-silent x2)
- Typecheck:
apps/apiandapps/webboth clean (tsc --noEmit)
Accomplishments
- Task 1: Added
PORTAL_URLS: Record<(typeof DENYLISTED_PORTALS)[number], string>tosource-registry.ts(vergabe24→https://www.vergabe24.de,aumass→https://www.aumass.de). The typedRecordkeyed offDENYLISTED_PORTALS's own element type means the portal set is never re-declared — TypeScript itself would fail to compile if a future denylist addition lacked a URL entry. AddedGET /modules/tender-radar/denylisted-portalstoTendersController, gated by@UseModule('tender-radar')(read-surface, same stance asgetCoverage), declared before@Get(':id')(route-order pitfall) and aftergetCoverage(grouped with the other read-surface routes). Handler maps overDENYLISTED_PORTALSand looks up each portal's URL inPORTAL_URLS, returning{ portals: [{ portal, url }] }. Extendedtenders.controller.spec.tswith a response-shape assertion (vergabe24/aumass+ their URLs) and a route-declaration-order guard (getDenylistedPortalsbeforegetTender), following the file's existingdescribe/itconventions for the other Pitfall-5 static routes. - Task 2: Added
DenylistedPortal/DenylistedPortalsResponsetypes andfetchDenylistedPortals()totender-radar-api.ts, following the existingcredentials: 'include'fetch convention (same shape asfetchCoverage).CoverageBanner.tsxnow runs a second independentuseEffect/fetch/fail-silent lifecycle for the denylisted portals, rendering a "Manuell beobachten:" paragraph listing each portal as a clickable link (target="_blank",rel="noopener noreferrer") to its canonical URL. This block is NOT gated behind the existingonlyDoecoverage-note condition — the component's outer render-gate changed from "nothing renders unless coverage fetched and onlyDoe" to "nothing renders only when BOTH the coverage note AND the denylist list are empty," so the manual-watch hint stays visible even once more public sources are ingested (onlyDoe becomes false) or if the coverage fetch itself fails. AddedCoverageBanner.test.tsx(new file — none existed before this plan) with 4 tests: both portal hrefs + link attributes render correctly, the block renders independently whenonlyDoeis false, a rejected denylisted-portals fetch fails silently while the coverage note still renders, and both fetches failing renders nothing.
Deviations from Plan
None — plan executed as written. CoverageBanner.test.tsx was a net-new file (the plan listed it under files_modified, but no prior version existed to modify).
Task Commits
- Task 1 —
28c6c7f(feat) —source-registry.tsPORTAL_URLS +tenders.controller.tsdenylisted-portals route +tenders.controller.spec.tstests. - Task 2 —
947325f(feat) —tender-radar-api.tsfetchDenylistedPortals +CoverageBanner.tsxdenylist block +CoverageBanner.test.tsx(new).
Plan metadata: (this SUMMARY's own commit — see final commit below)
Both tasks are single feat commits per the plan's type="auto" (non-TDD) declaration.
Self-Check: PASSED
apps/api/src/tenders/source-registry.ts— FOUNDapps/api/src/tenders/tenders.controller.ts— FOUNDapps/api/src/tenders/tenders.controller.spec.ts— FOUNDapps/web/src/lib/tender-radar-api.ts— FOUNDapps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx— FOUNDapps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.test.tsx— FOUND- Commit
28c6c7f— FOUND ingit log --oneline --all - Commit
947325f— FOUND ingit log --oneline --all