- Replace bun with pnpm (v10.33.0) via corepack
- Upgrade all Docker images from node:22/bun to node:25-alpine
- Update CI pipeline, pre-commit hooks, and prettierignore
- Add packageManager field to package.json for version pinning
- Upgrade CI deploy to Helm 4.1 with --rollback-on-failure --wait=watcher
- Replace initialDelaySeconds with startup probe (15x2s=30s window)
- Set resources req=limit (100m/128Mi) for Guaranteed QoS class
- Add ConfigMap checksum annotation to trigger rollouts on config changes
- Set resources req=limit (100m/128Mi) for Guaranteed QoS class
- Add ConfigMap checksum annotation to trigger rollouts on config changes
- Add retry limit (60 attempts) to migration init container
- Use TARGETARCH in Dockerfile for multi-arch build support
- Set GOMAXPROCS and GOMEMLIMIT from cgroup limits to prevent
thread oversubscription and unbounded GC memory growth
- Add startup probe (60s budget) to gate liveness/readiness during
connection pool initialization
- Increase liveness failureThreshold to 5 to avoid restarts on
transient issues
- Remove initialDelaySeconds (startup probe replaces this)
- Upgrade CI from alpine/helm:3.17 to alpine/helm:4.1
- Replace deprecated --atomic with --rollback-on-failure + --wait=watcher
BusyBox 1.37 nc -z is broken (outputs "punt!" and never exits),
causing the wait-for-cache init container to loop indefinitely.
The cache is healthy — the backend should handle reconnects itself.
Prevents the backend from starting before the DragonflyDB operator
has the cache pod ready and reachable. Mirrors the existing
wait-for-postgres pattern in the migration job.
Add sectionName to HTTPRoute for HTTPS listener pinning and a separate
HTTP→HTTPS 301 redirect route. Update resources from req=limit to
request/limit separation for pay-as-you-go billing.
Replace manual Valkey Deployment+Service with DragonflyDB operator CRD.
Add sectionName to HTTPRoute for HTTPS listener pinning and a separate
HTTP→HTTPS 301 redirect route. Update resources from req=limit to
request/limit separation for pay-as-you-go billing. Fix NetworkPolicy
cache pod selector to match operator-managed labels.
Mistral rejects json response format when tools (web search) are
active. Override to text since the prompt already requests JSON
output in the instructions.
Use the Conversations API (POST /v1/conversations) via the SDK for
Pass 1 agent calls instead of /agents/completions which doesn't
support built-in web search connectors. Pass 2 uses the SDK's
ChatComplete method.
Add Woodpecker secrets for AI_API_KEY, AI_AGENT_SIMPLE, and
TURNSTILE_SECRET_KEY. Create ci-secrets.yaml template and wire
them through the deploy step alongside existing SMTP secrets.
Set CPU and memory requests equal to limits (100m/100Mi). Switch rolling
update strategy to maxSurge=1, maxUnavailable=0 so new pods start
before old ones terminate.
Set CPU and memory requests equal to limits (100m/100Mi) for backend,
cache, and web. Switch rolling update strategy to maxSurge=1,
maxUnavailable=0 so new pods start before old ones terminate.
Add readiness probe to cache deployment.
Public search now deduplicates to one card per series with an edition
count badge. Admin list uses a grouped endpoint with expandable rows.
Market detail page shows an edition year switcher when multiple editions
exist. Admin detail page includes series edition management.
- Public search dedup: CTE with ROW_NUMBER picks best edition per series
(nearest future, fallback most recent past), COUNT(*) OVER for edition_count
- Add EditionCount to Market model and MarketSummary DTO
- GetBySlug returns nearest-future edition by default, supports ?year= override
- GetBySlug returns sibling edition briefs for edition switcher UI
- New GET /admin/markets/grouped endpoint returns AdminSeriesGroup[] with
series-level pagination and all editions per group
- Add AdminSearchGrouped to repository, service, handler, and routes
Admin endpoint POST /admin/series/:id/editions creates a new yearly
edition for an existing series, inheriting series-level defaults.
Public market submissions now auto-match against existing series using
trigram similarity (>0.6) on name + exact city match. Matched
submissions link to the existing series instead of creating duplicates.
Fix Gin route tree conflict by nesting markets and series groups under
a shared /admin parent group.
Split markets into market_series (persistent identity) + market_editions
(yearly instances). Migration preserves edition UUIDs from old market IDs
for URL backward compatibility. Edition statuses: rumored, confirmed,
active, completed, cancelled, archived.
AI research now uses two-pass pipeline: Pass 1 via pre-created Mistral
agent with web search for structured extraction, Pass 2 via
mistral-large-latest for description writing and field retry. Confidence
scoring is source-derived (source count + extraction type) instead of
LLM self-reported.
- Search box for name/city filtering
- Clickable column headers for sorting (name, city, status, date, created)
- Sort direction toggle (asc/desc) with arrow indicators
- All filters preserved through pagination and sort changes
- AI research via Mistral API for admin market editing
- Auto-geocoding via Nominatim OSM with rate limiting
- Public geocode endpoint (POST /api/v1/geocode)
- Duplicate detection using pg_trgm trigram similarity
- Extended SubmitMarketRequest with street, opening_hours, admission_info
- pg_trgm migration for fuzzy name matching
- AI research panel with structured display (opening hours, admission)
- Shared MarketForm component with admin/public mode
- Geocode button to derive coordinates from address
- Opening hours, admission info fields in all forms
- Currency-aware pricing, full European country list
- Public submit now includes all market fields
- Weekday normalization from date for AI research results
- Duplicate detection warning on admin detail view
Backend returns code '2fa_required' but frontend checked for
'totp_required', so the TOTP input field never appeared. Also fix
the same Svelte pattern brace escaping issue as in TOTPSetup.
- Replace inline nav items with UserMenu dropdown (display name trigger,
Profil/Sicherheit/Admin/Abmelden, click-outside/Escape to close)
- Add password set/change form to profile security section
- Fix Turnstile site key (extra A, swapped l/1)
- Add PUT /auth/password for setting/changing passwords (handles both
first-time set for magic link/OAuth users and change for password users)
- Generate random medieval display names (e.g. Gaukler1025) for new
magic link and OAuth users instead of leaving display_name empty
- Add has_password field to ProfileData response
The page uses $env/dynamic/public which reads env vars at runtime,
not build time. The Docker build ARG/ENV only exists in the builder
stage and doesn't propagate to the Node.js runtime container.
Add PUBLIC_TURNSTILE_SITE_KEY to the Helm ConfigMap so it's
available as a process.env var when the SSR server runs.
maxSurge=1 requires a second pod during rollout, but the tenant
ResourceQuota (1 CPU limit) is already at 900m — the extra 250m
exceeds the cap and the pod can't schedule, causing a 5min timeout.
Switch to maxSurge=0/maxUnavailable=1 (kill-then-start) to stay
within quota. Matches the web deployment strategy.