docs(14): create phase plan (5 plans, 4 waves)

This commit is contained in:
2026-07-23 12:14:26 +02:00
parent 9bd93ce8ff
commit cf105f0884
6 changed files with 915 additions and 2 deletions
@@ -0,0 +1,169 @@
---
phase: 14-rss-email-alert-ingestion-module-rollout
plan: 05
type: execute
wave: 4
depends_on: [14-02, 14-03, 14-04]
files_modified:
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- apps/web/src/messages/tenderRadar-parity.spec.ts
- apps/web/src/app/(portal)/modules/tender-radar/page.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx
- 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/RssFeedListForm.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx
autonomous: true
requirements: [CONFIG-03]
must_haves:
truths:
- "The entire tender-radar UI (results, filters, saved searches, detail, coverage, settings, RSS/email forms) renders via a tenderRadar i18n namespace, usable in DE and EN"
- "de.json and en.json have identical key sets under tenderRadar (no missing translation on either side)"
artifacts:
- "tenderRadar namespace added to de.json + en.json with EN translations authored by Claude"
- "tenderRadar-parity.spec.ts enforcing de/en key-set equality"
key_links:
- "Every tender-radar component/page reads strings via useTranslations('tenderRadar') — no hardcoded German literals remain"
---
<objective>
Complete the bilingual module rollout (CONFIG-03/UI-06 i18n, D-10/D-11): introduce a new `tenderRadar` namespace in de.json + en.json and convert every tender-radar component and page from hardcoded German strings to `useTranslations('tenderRadar')` keys. No visible language switcher (D-11) — locale continues to follow the existing next-intl cookie mechanism. EN translations are authored here with consistent Vergabe-domain terminology.
Purpose: The module was intentionally built with German MVP-stub strings; this is the point that convention is retired for tender-radar specifically.
Output: A complete `tenderRadar` namespace (DE + EN), all components converted, and a de/en key-parity guard test.
</objective>
## Phase Goal (MVP user story)
**As a** user, **I want to** use the entire Ausschreibungs-Radar UI in German or English, **so that** English-speaking colleagues can work with it without a German-only barrier.
Slice: Task 1 lands the namespace + parity guard; Task 2 converts the main results/filter components; Task 3 converts the settings + config forms. After Task 3 the whole module honors the selected locale.
## Artifacts this phase produces
- New top-level `tenderRadar` namespace in `apps/web/src/messages/de.json` and `en.json` (parallel key sets), covering: page headings, ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner (incl. denylist "manuell beobachten" block from 14-04), settings page + SourceConfigForm + RssFeedListForm (14-02) + EmailAlertConfigForm (14-03).
- `apps/web/src/messages/tenderRadar-parity.spec.ts` — structural de/en key-set equality test for the namespace.
- All listed components/pages converted to `useTranslations('tenderRadar')`.
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-CONTEXT.md
@.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-RESEARCH.md
@apps/web/src/i18n/request.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: tenderRadar namespace (DE + EN) + de/en key-parity spec</name>
<files>apps/web/src/messages/de.json, apps/web/src/messages/en.json, apps/web/src/messages/tenderRadar-parity.spec.ts</files>
<read_first>
- apps/web/src/messages/de.json + en.json (top-level namespace shape; dkvFleet namespace as the sibling template for a module namespace)
- apps/web/src/i18n/request.ts (locale from NEXT_LOCALE cookie, default 'de' — no code change needed for D-11)
- apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx (useTranslations('marketplace') consumption example)
- RESEARCH.md i18n section (D-10 namespace shape, key-parity test recommendation) + D-11 (no switcher)
</read_first>
<action>
Inventory every hardcoded German string across the tender-radar components and pages (page.tsx, ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner, settings/page.tsx, SourceConfigForm, RssFeedListForm, EmailAlertConfigForm). Add a `tenderRadar` object to de.json capturing all of them as keys (group by component, e.g. `results.*`, `filter.*`, `savedSearch.*`, `detail.*`, `coverage.*`, `settings.*`, `rssFeeds.*`, `emailAlerts.*`), preserving the exact current German copy as values. Add the identical key set to en.json with faithful English translations using consistent Vergabe-domain terminology (Ausschreibung → tender, Vergabestelle → contracting authority, Frist → deadline, Auftragswert → estimated value). Create tenderRadar-parity.spec.ts (apps/web, jsdom) that imports both JSON files and asserts the recursively-flattened key sets under `tenderRadar` are identical (fails listing any key present in one locale but not the other). Do NOT add a language switcher (D-11).
</action>
<verify>
<automated>cd apps/web && npx vitest run src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit</automated>
</verify>
<acceptance_criteria>
- de.json and en.json both contain a `tenderRadar` namespace with identical (recursively-flattened) key sets.
- tenderRadar-parity.spec.ts passes and would fail on any de/en key mismatch.
- No language-switcher UI element is introduced.
</acceptance_criteria>
<done>The tenderRadar namespace exists in both locales with enforced key parity and authored EN translations.</done>
</task>
<task type="auto">
<name>Task 2: Convert results-side components to useTranslations</name>
<files>apps/web/src/app/(portal)/modules/tender-radar/page.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx</files>
<read_first>
- apps/web/src/app/(portal)/modules/tender-radar/page.tsx (headings)
- apps/web/src/app/(portal)/modules/tender-radar/components/{ResultsList,FilterPanel,SavedSearchBar,TenderDetail,CoverageBanner}.tsx (strings to replace)
- apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx + SavedSearchBar.test.tsx + TenderDetail.test.tsx + CoverageBanner.test.tsx (existing tests — update next-intl mock so t(key) resolves; mirror marketplace tenant-selector.test.tsx mock)
- tenderRadar keys added in Task 1
</read_first>
<action>
Convert page.tsx, ResultsList.tsx, FilterPanel.tsx, SavedSearchBar.tsx, TenderDetail.tsx, and CoverageBanner.tsx to read every user-facing string via `const t = useTranslations('tenderRadar')` and `t('...')` (or `t.rich`/interpolation where the current copy contains values). Replace all German literals with their tenderRadar keys from Task 1 — no hardcoded user-facing German text may remain in these files. Update the affected component tests to provide a next-intl `useTranslations` mock (returning the key or a lookup) so they still pass, mirroring the marketplace test mock convention. Preserve behavior and markup — this is a string-source swap only.
</action>
<verify>
<automated>cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar/components" && npx tsc --noEmit</automated>
</verify>
<acceptance_criteria>
- `grep -nP "[A-Za-zÄÖÜäöüß]{4,}" ` audit: no user-facing German string literals remain in the six converted files (strings come from t()).
- Each converted component imports useTranslations('tenderRadar').
- Updated component tests pass; web tsc clean.
</acceptance_criteria>
<done>The results list, filters, saved searches, detail overlay, coverage banner, and page headings all render via the tenderRadar namespace.</done>
</task>
<task type="auto">
<name>Task 3: Convert settings + config forms to useTranslations</name>
<files>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/RssFeedListForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx</files>
<read_first>
- apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx (headings + digest section strings)
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx (labels, validation/save messages)
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx (from Plan 14-02)
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx (from Plan 14-03)
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx (update next-intl mock)
- tenderRadar keys added in Task 1
</read_first>
<action>
Convert settings/page.tsx, SourceConfigForm.tsx, RssFeedListForm.tsx, and EmailAlertConfigForm.tsx to `useTranslations('tenderRadar')`, replacing every label, placeholder, button, validation message, and success/error string with its tenderRadar key. For dynamic validation messages that embed numbers (e.g. interval bounds), use next-intl interpolation (`t('...', { min, max })`). Update SourceConfigForm.test.tsx (and any other affected settings test) with the next-intl mock so they pass. No hardcoded user-facing German text may remain in these four files.
</action>
<verify>
<automated>cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar/settings" src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit</automated>
</verify>
<acceptance_criteria>
- settings/page.tsx + the three config forms read all user-facing strings via useTranslations('tenderRadar').
- No user-facing German literal remains in these four files.
- Settings tests pass; parity spec still green (any key added here exists in both locales); web tsc clean.
</acceptance_criteria>
<done>The entire tender-radar settings surface (source config, RSS feeds, email alerts, digest) is bilingual.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| (none new) | i18n string-source swap; no new data flow or input surface |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-14-05-01 | Tampering | de/en key drift | low | mitigate | tenderRadar-parity.spec.ts fails the build on any missing/extra key in either locale |
| T-14-05-SC | Tampering | npm/pip/cargo installs | low | accept | No new packages — next-intl already installed |
</threat_model>
<verification>
- `cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar" src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit` — all tender-radar web tests + parity green.
- Manual: toggling the NEXT_LOCALE cookie de↔en switches the whole module UI.
</verification>
<success_criteria>
- Entire tender-radar UI usable in DE and EN via the tenderRadar namespace (CONFIG-03, D-10).
- de/en key parity enforced by test; no language switcher added (D-11).
</success_criteria>
<output>
Create `.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-05-SUMMARY.md` when done.
</output>