Files

12 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed coverage duration completed status
10-ausschreibungs-radar-foundation-d-e-ingestion 06 ui
next.js
react
vitest
testing-library
tender-radar
admin-settings
tdd
phase provides
10-02 (marketplace registration) TendersModule seeded, tender-radar/page.tsx placeholder + module-loader whitelist entry
phase provides
10-05 (controller/DTOs) GET/PUT /modules/tender-radar/source-config (Roles-guarded, singleton doe-opendata config, live scheduler apply)
tender-radar-api.ts web client — fetchSourceConfig/saveSourceConfig against the Plan 05 endpoint
SourceConfigForm — admin form for the shared DÖE poll interval (5-1440 min) + isActive toggle, with client-side bounds mirroring the backend DTO
settings/page.tsx — standard App Router route rendering the form under the already-whitelisted tender-radar module
Component test coverage: fetch-on-mount, save payload, out-of-bounds client-side rejection
phase 11 saved searches/filter UI (may extend this settings surface with per-search-profile config later)
added patterns
Admin config form without secrets: SourceConfigForm mirrors DKV's InboxConfigForm load-on-mount/save flow but omits all credential fields, since the DÖE source is auth-free (T-10-18)
Client-side bound mirroring: pollIntervalMin clamp (5-1440) duplicated on the client purely as UX/DoS-floor guidance — the server's SourceConfigDto class-validator bounds remain the sole authority
created modified
apps/web/src/lib/tender-radar-api.ts
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx
No next-intl in SourceConfigForm/settings/page.tsx — hardcoded German strings per plan instruction, matching the existing tender-radar/page.tsx placeholder's documented MVP-stub convention (full i18n is CONFIG-03, Phase 14)
No module-loader whitelist change — settings/page.tsx is a standard nested Next.js App Router route under the already-whitelisted tender-radar module page (Plan 10-02), unlike the top-level module entry itself
Read-only sourceType/lastIngestedDay display fields added beyond the plan's minimum ask (interval + toggle) so the admin can see the day-cursor state, as the plan's action text explicitly suggested ("Optionally display...")
INGEST-06
id description requirement verification human_judgment
D1 Admin can read and change the shared DÖE poll interval / active state from a web form in module settings (not only via the raw API) INGEST-06
kind ref status
automated_ui SourceConfigForm.test.tsx#'fetches config on mount and renders the returned pollIntervalMin in the interval input' pass
kind ref status
automated_ui SourceConfigForm.test.tsx#'editing the interval and clicking Speichern calls saveSourceConfig with the new pollIntervalMin and isActive' pass
false
id description requirement verification human_judgment
D2 Client-side interval bounds (5-1440 min) mirror the backend SourceConfigDto and block out-of-bounds saves before they reach the server INGEST-06
kind ref status
automated_ui SourceConfigForm.test.tsx#'rejects an interval below 5 client-side without calling saveSourceConfig' pass
kind ref status
automated_ui SourceConfigForm.test.tsx#'rejects an interval above 1440 client-side without calling saveSourceConfig' pass
false
id description requirement verification human_judgment rationale
D3 Settings route renders the form and round-trips a real GET/PUT to the Plan 05 endpoint in a live browser session (not just component-test mocks) INGEST-06
true Component tests mock tender-radar-api; an actual browser round-trip against the running API (login as admin, open /modules/tender-radar/settings, change interval, reload, confirm persistence) was not exercised in this run — recommend a quick UAT pass once the local stack is rebuilt with these frontend changes.
3min 2026-07-21 complete

Phase 10 Plan 06: Ausschreibungs-Radar Admin Settings UI Summary

SourceConfigForm + tender-radar-api.ts client deliver the missing admin-facing half of INGEST-06 — a settings form (interval 5-1440 min + active toggle) that reads and writes the Plan 05 GET/PUT /modules/tender-radar/source-config endpoint, closing the plan-review gap that flagged the requirement as REST-only.

Performance

  • Duration: 3 min
  • Started: 2026-07-21T09:23:22Z
  • Completed: 2026-07-21T09:25:48Z
  • Tasks: 2 (Task 1 auto, Task 2 auto with tdd="true")
  • Files modified: 4 (all created, none modified)

Accomplishments

  • tender-radar-api.ts — SourceConfig/SaveSourceConfigPayload types plus fetchSourceConfig()/saveSourceConfig() hitting GET/PUT /modules/tender-radar/source-config, mirroring dkv-api.ts conventions (NEXT_PUBLIC_API_URL, credentials: 'include').
  • SourceConfigForm — loads the singleton config on mount, exposes a numeric pollIntervalMin input (client-side clamp 5-1440, mirroring the backend SourceConfigDto bounds) and an isActive toggle switch (same visual pattern as InboxConfigForm's Aktiv switch), plus read-only sourceType/lastIngestedDay display so the admin can see the day-cursor state. Save success/error and validation-error feedback states, no credentials fields (the DÖE source has none).
  • settings/page.tsx — standard nested App Router route rendering the form; no module-loader.ts whitelist change needed since the parent tender-radar slug is already whitelisted (Plan 10-02).
  • Component test (SourceConfigForm.test.tsx, 4 tests): fetch-on-mount populates the interval input, editing + Speichern calls saveSourceConfig with the edited { pollIntervalMin, isActive } payload, and both out-of-bounds directions (below 5, above 1440) are rejected client-side with zero saveSourceConfig calls.
  • Full web Vitest suite green (111/111 across 20 files), tsc --noEmit clean for apps/web.

Task Commits

Each task committed atomically:

  1. Task 1: tender-radar-api client + admin source-config settings form - 4360bc0 (feat)
  2. Task 2: SourceConfigForm component test - 8e3dfda (test)

Plan metadata: see final docs(10-06) commit.

TDD Gate Compliance

  • Task 2 (tdd="true"): the test suite's first draft ran red on two of its four cases — not because the target behavior was unimplemented, but because a test-harness bug (mock call-history leaking across it() blocks, see Deviations) caused a later "not.toHaveBeenCalled()" assertion to see a prior test's captured call. This is a test-infrastructure fix (Rule 1), not evidence of a missing feature: Task 1's implementation already had the correct out-of-bounds guard. After adding mockReset() in afterEach, all four cases passed against the already-correct Task 1 implementation on the very next run. This plan's frontmatter is type: execute with a per-task tdd="true" flag (not a full type: tdd plan), matching the exact precedent documented in Plan 10-05's Task 3 — the strict "investigate a passing RED" rule applies to full-plan TDD gates, not this per-task flag usage.
  • No REFACTOR commit was needed beyond the inline mock-hygiene fix folded into the Task 2 test commit.

Files Created/Modified

  • apps/web/src/lib/tender-radar-api.ts - SourceConfig/SaveSourceConfigPayload types, fetchSourceConfig(), saveSourceConfig()
  • apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx - settings route rendering <SourceConfigForm />
  • apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx - interval input (5-1440 min) + isActive toggle + read-only sourceType/lastIngestedDay + save flow
  • apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx - 4 Vitest + Testing Library tests

Decisions Made

  • No next-intl — hardcoded German strings in both new components, per plan instruction and matching the existing tender-radar/page.tsx placeholder's documented MVP-stub convention (full i18n is CONFIG-03, Phase 14).
  • No module-loader.ts change — settings/page.tsx is a standard nested App Router route under the already-whitelisted tender-radar slug; the whitelist gate only applies to the top-level module entry (Plan 10-02), not sub-routes.
  • Read-only sourceType/lastIngestedDay display — added beyond the plan's minimum ask (interval + toggle), per the plan's own "Optionally display..." suggestion, so an admin can see the day-cursor state without a separate API call.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Test mock call-history leaked across it() blocks in SourceConfigForm.test.tsx

  • Found during: Task 2 (initial test run)
  • Issue: afterEach only called vi.restoreAllMocks(), which does not clear vi.fn() call history for the module-level mockFetchSourceConfig/mockSaveSourceConfig mocks (only restores spies to their original implementation). The "rejects an interval below 5" and "above 1440" tests' expect(mockSaveSourceConfig).not.toHaveBeenCalled() assertions saw the previous test's captured saveSourceConfig(120, true) call and failed.
  • Fix: Added mockFetchSourceConfig.mockReset() / mockSaveSourceConfig.mockReset() to afterEach, run before vi.restoreAllMocks().
  • Files modified: apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx
  • Verification: All 4 tests pass on rerun; full web suite (111/111) still green.
  • Committed in: 8e3dfda (Task 2 commit, caught and fixed before commit)

Total deviations: 1 auto-fixed (1 Bug — test-infrastructure only, no production code change) Impact on plan: Zero scope creep; the fix is entirely within the new test file's own hygiene and does not touch SourceConfigForm.tsx or tender-radar-api.ts.

Issues Encountered

None beyond the one documented deviation above. Local Docker stack (per environment context) was not needed for this frontend-only plan — no live DÖE calls, no live API round-trip exercised (see coverage D3's human_judgment: true rationale).

User Setup Required

None - no external service configuration required. The local Docker stack was left running unmodified; rebuilding the web container to pick up these frontend changes (so /modules/tender-radar/settings is reachable in a live browser session) is left to the user per project convention.

Next Phase Readiness

  • INGEST-06 is now fully complete on both halves: Plan 05 delivered the @Roles-guarded GET/PUT /modules/tender-radar/source-config endpoint with live scheduler apply, and this plan delivers the admin-facing form driving it — closing the plan-review gap that the requirement needed a UI, not only a REST surface.
  • This closes out Phase 10 (ausschreibungs-radar-foundation-d-e-ingestion) — all 6 plans complete. Phase 11 (saved searches/filter UI) can build on the GET /modules/tender-radar / GET /modules/tender-radar/:id read surface (Plan 05) and, if needed, extend this settings surface with additional per-search-profile config.
  • No blockers for Phase 11. Recommended follow-up (non-blocking): a quick live-browser UAT pass confirming the settings form round-trips against the real API once the local stack is rebuilt (coverage D3).

Self-Check: PASSED

  • FOUND: apps/web/src/lib/tender-radar-api.ts
  • FOUND: apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
  • FOUND: apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx
  • FOUND: apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx
  • FOUND commit: 4360bc0
  • FOUND commit: 8e3dfda

Phase: 10-ausschreibungs-radar-foundation-d-e-ingestion Completed: 2026-07-21