- Local logo caching: logos downloaded on enrichment, stored on persistent Docker volume
- LogoAsset Prisma model: id, userId, companyId, sourceUrl, filePath, mimeType, fileSize, width?, height?, status, errorMessage?
- Download pipeline: SSRF validation (validateWebhookUrl) → fetch with safe redirect following (max 3 hops) → content-type + magic byte validation → SVG sanitization → disk storage
- SVG sanitizer: strips
<script>,<foreignObject>, event handlers,javascript:URIs, external refs.data:URIs restricted to image/* MIME types only. - Magic byte validator: confirms file header matches declared Content-Type (PNG, JPEG, GIF, WebP, SVG, ICO)
- Image dimension reader: reads width/height from binary headers (no native dependencies)
- API route
/api/logos/[id]: authenticated file serving with Cache-Control, ETag, CSP sandbox for SVGs - CompanyLogo two-slot fallback: local asset → external URL → initials avatar
- AddCompany status banner: shows ready/pending/failed state with delete + re-download buttons
- LogoAssetSettings: configurable max file size (512KB) and max dimension (512px bounding box)
- Event subscriber: EnrichmentCompleted (logo dimension) → auto-download fire-and-forget
- Manual URL sync: updateCompany detects logoUrl change → triggers download
- Company deletion cleanup: Prisma cascade + disk file removal
- Wikipedia URL resolver: auto-resolves Wikipedia media page URLs to direct Wikimedia Commons image links via API
- Logo URL content-type check: server-side HEAD request detects non-image URLs (shows "URL points to a webpage, not an image")
- i18n: logoAsset dictionary in all 4 locales (EN, DE, FR, ES)
magic-bytes.spec.ts— 22 tests (format detection, mismatches, edge cases)svg-sanitizer.spec.ts— 32 tests (XSS vectors, passthrough, combined attacks)image-processor.spec.ts— 25 tests (PNG/JPEG/GIF/WebP header parsing)logoAsset.actions.spec.ts— 22 tests (CRUD, IDOR, fire-and-forget)
- SSR hydration mismatch:
useTranslations()fell back to "en" during SSR. AddedLocaleProvidercontext that carries server-resolved locale into client components. - SSE TDZ crash:
cleanup()in automation logs route referencedconst interval/timeoutbefore initialization. Declared asletat top of scope. - SSE missing directives: added
import "server-only"+export const dynamic = "force-dynamic"to automation logs routes - NotificationDropdown setState-during-render: moved
onCountChangecalls outside setState updater - allowedDevOrigins format: Next.js 15 App Router expects bare hostnames, not full URLs. Auto-strips protocols/ports in UI + env-sync. Updated all descriptions/placeholders.
- CompanyLogo scaling:
object-cover→object-contain(no cropping, SVGs scale properly) - CompaniesTable + RecentCardToggle: migrated from raw
<img>/AvatartoCompanyLogocomponent
- SVG
data:URIs restricted toimage/*MIME types only (blocksdata:text/htmlXSS) - Magic bytes:
<?xmlalone no longer triggers SVG detection (requires<svg) deleteAssetusesdeleteManywith userId (ADR-015 compliance)
- Kanban board query missing
logoAssetId→ added to select, type, and component wiring getStorageBase()cached at module init (was callingstatSyncon every download)
specs/logo-asset-cache.allium— new Allium spec for logo asset rulesdocs/superpowers/specs/2026-04-06-logo-asset-cache-design.md— design specification (rev 2)CLAUDE.md— added Logo Asset Cache section under Connector Architecture- Project roadmap memory updated with committed feature + extension points
- CRITICAL: sendTestPush() sent raw i18n key "push.testBody" as browser notification body → resolves locale and translates
- 6 channel/infrastructure files missing
import "server-only"guard → added to all - PushChannel deleted subscriptions on 401/403 VAPID auth failures → only 404/410 now
- resolveUserLocale duplicated 4× with inconsistent behavior → extracted to shared
locale-resolver.ts - Nodemailer transport config duplicated → extracted to shared
email/transport.ts - ChannelRouter dispatched channels sequentially → concurrent via
Promise.allSettled - Input length validation added to SMTP (host/username/password/fromAddress) and Push (endpoint/p256dh/auth)
- sendTestPush used wrong NotificationType + double-charged rate limits → fixed
- SmtpSettings inputs not in
<form>element (WCAG 1.3.1) → wrapped in form - Password toggle unreachable by keyboard (tabIndex={-1}) → removed
- No progress indication during 30s SMTP timeout → descriptive loading text
- Edit/Delete clickable during test-in-flight → disabled
- Missing aria-required, aria-live, destructive button styling → added
- Email template footer contrast 4.2:1 → 4.78:1 (#636363)
- buildNotificationMessage double-replacement bug → single PLACEHOLDER_MAP pass
- Dispatcher 2 DB calls for same user row → combined resolveUserSettings
- Plain-text email body control-char sanitization → sanitizePlainText()
- Stale "D2 future" / "D3 future" comment in types.ts → updated
- ToastProvider explicit
duration={5000} - StatusFunnelWidget hover tooltips with count + percentage
- StatusHistoryTimeline server-side pagination (take:50 + Load more)
- "N jobs tracked" line in StatusFunnelWidget
smtp-actions.spec.ts— 29 tests (IDOR, encryption, SSRF, validation)push-actions.spec.ts— 38 tests (subscription limits, rate limits, translated push)- PushChannel 401/403 non-deletion regression tests
- Email template placeholder interpolation tests
- StatusFunnelWidget + StatusHistoryTimeline updated tests
- E2E:
smtp-settings.spec.ts(5 tests) +push-settings.spec.ts(3 tests)
notification-dispatch.allium: 5 spec bugs fixed (QuietHours invariant, email recipient, 404/410, VAPID auth, ordering note)docs/reviews/s5b/: 12 review reports (code quality, architecture, security, performance, testing, WCAG, interaction, data storytelling, STRIDE, flashlight, consolidated)
job_status_changedNotificationType + dispatcher wiring for JobStatusChanged events- Multi-channel preferences:
emailandpushfields in NotificationPreferences.channels - 4 missing types added to CONFIGURABLE_NOTIFICATION_TYPES (S5a deferred L6)
- SmtpConfig Prisma model (AES-encrypted password, TLS default, one per user)
- EmailChannel adapter implementing NotificationChannel interface
- nodemailer SMTP transport with TLS enforcement (TLSv1.2+, rejectUnauthorized)
- SMTP host SSRF validation (blocks private IPs, IMDS, localhost)
- Rate limiting: 10 emails/min per user, test button 1/60s cooldown
- HTML email templates for all 11 NotificationTypes × 4 locales (EN, DE, FR, ES)
- Server actions: saveSmtpConfig, getSmtpConfig, testSmtpConnection, deleteSmtpConfig
- SmtpSettings UI: SMTP config form, password show/hide, test email with countdown, delete confirmation
- Settings sidebar "Email" entry
- VapidConfig + WebPushSubscription Prisma models (AES-encrypted keys)
- PushChannel adapter implementing NotificationChannel interface
- web-push VAPID protocol for push delivery
- VAPID key auto-generation and rotation (with subscription cleanup)
- Stale subscription handling (410 Gone + 404 → silent delete)
- Rate limiting: 20 pushes/min per user, test push 1/60s
- Minimal service worker (
public/sw-push.js— push-only, not full PWA) - Server actions: subscribePush, unsubscribePush, getVapidPublicKeyAction, rotateVapidKeysAction, sendTestPush
- PushSettings UI: enable/disable, device count, test push, VAPID rotation warning
- Settings sidebar "Push" entry
- HIGH: sw-push.js open redirect via push payload URL → validate relative paths
- MEDIUM: Unused t() import in email.channel.ts
- MEDIUM: Hardcoded English aria-labels on password toggle → i18n
- MEDIUM: HTML lang="en" hardcoded in email templates → locale-aware
- MEDIUM: Push 404 not treated as stale subscription → cleanup on 404+410
- MEDIUM: Missing role="alert" on error states in SmtpSettings/PushSettings
notification-dispatch.alliumupdated with all 4 channels (in-app, webhook, email, push)- Added entities: SmtpConfig, VapidConfig, WebPushSubscription
- Added rules: EmailDelivery, PushDelivery
- Added invariants: SmtpHostSafe, VapidKeysEncrypted, PushSubscriptionKeysEncrypted
- 100 new tests across 6 test suites (email-channel, push-channel, smtp-validation, email-rate-limit, email-templates, vapid)
- Total: 157 suites, 2918 tests (up from 151/2818)
- 7 findings fixed (1 HIGH, 6 MEDIUM)
- +3,800 LOC added
- 3 new Prisma models, 3 migrations
- 2 new npm packages (nodemailer, web-push)
- All 4 notification channels operational (in-app, webhook, email, push)
- EnrichmentStatusPanel: Logo preview, enrichment status, module info, refresh button in Job Detail
- StatusHistoryTimeline: Chronological status transitions with notes, timestamps, visual connectors (20-entry limit with "Show all")
- Kanban Within-Column Reorder: Drag-and-drop reorder via updateKanbanOrder with optimistic updates and midpoint sortOrder strategy
- Staging Queue Sidebar Link:
/dashboard/stagingin sidebar navigation with Inbox icon
- StatusFunnelWidget: 5-stage conversion chart (Bookmarked→Applied→Interview→Offer→Hired) in dashboard
- Health Check Button: "Check Now" per module in EnrichmentModuleSettings + ApiKeySettings
- Global Undo (Ctrl+Z/Cmd+Z): useGlobalUndo hook in dashboard layout, skips text inputs
- Retention Cleanup: "Run Cleanup" button in Developer Settings with AlertDialog confirmation
- WebhookEndpoint model with AES-encrypted HMAC secret, event subscriptions, failure tracking
- ChannelRouter: Multi-channel notification dispatcher refactored from hardcoded in-app
- WebhookChannel: HMAC-SHA256 signing, 3-attempt retry (1s/5s/30s), 10s timeout, concurrent delivery
- SSRF Protection: validateWebhookUrl() blocks IMDS, RFC1918, localhost, IPv6 private, IPv4-mapped IPv6, open redirectors
- Auto-deactivation: After 5 consecutive failures with in-app notification
- Webhook Settings UI: CRUD with event selection, secret-once dialog, active toggle, client-side URL validation
- shouldNotify() channel-aware: Per-channel gating (webhook fires even if inApp disabled)
- Allium specs: notification-dispatch.allium + event-bus.allium updated
- CRITICAL: WebhookChannel not registered in ChannelRouter → import + register
- CRITICAL: computeSortOrder negative values rejected by updateKanbanOrder → allow negative sortOrder
- HIGH: Webhook fetch followed redirects (SSRF bypass) → redirect: "manual"
- HIGH: IDOR in webhook CRUD actions → userId in all write/delete operations
- HIGH: IDOR in webhook.channel.ts failure updates → userId in all Prisma calls
- HIGH: Hardcoded English health labels in ApiKeySettings → i18n keys
- MEDIUM: StatusFunnelWidget unhandled rejection → try-catch with error state
- MEDIUM: Sequential webhook delivery → Promise.allSettled for concurrency
- MEDIUM: Failure count race condition → Prisma atomic increment
- MEDIUM: No client-side URL validation → inline validation with error messages
- MEDIUM: StatusHistoryTimeline no pagination → 20-entry limit with "Show all"
- MEDIUM: Hardcoded English webhook messages → i18n with locale resolution
- MEDIUM: IPv4-mapped IPv6 SSRF gap → detect and re-validate underlying IPv4
- Tests: 150 suites, 2778 passed (+172 new)
- Files: 30+ new/modified across components, actions, hooks, notifications, specs
- S3-D1 (HIGH): Public API PATCH
/api/v1/jobs/:idno longer accepts statusId — newPOST /api/v1/jobs/:id/statusendpoint for state-machine-enforced transitions - S3-D3 (HIGH): Optimistic locking via
versionfield on Job model — stale writes rejected with 409 CONFLICT - DAU-7 (HIGH): Kanban board switched from paginated getJobsList to dedicated getKanbanBoard (all jobs, tags included)
- DataEnrichmentConnector interface with fallback chain orchestration per dimension
- 3 Modules: Clearbit Logo (free tier), Google Favicon, Meta/OpenGraph Parser
- Fallback chains: Logo: Clearbit → Google Favicon → Placeholder; DeepLink: Meta Parser
- Enrichment cache: EnrichmentResult table with TTL-based stale-if-error semantics
- Audit trail: EnrichmentLog table for module effectiveness tracking
- CompanyLogo component with skeleton → image → initials fallback (sm/md/lg sizes)
- EnrichmentModuleSettings in Settings page (activation toggles, health indicators)
- Domain events: EnrichmentCompleted, EnrichmentFailed
- i18n: enrichment namespace in all 4 locales (en, de, fr, es)
- Allium spec:
specs/data-enrichment.allium(821 lines, aligned with implementation) - Schema design:
docs/enrichment-schema-design.md(731 lines)
- CRITICAL: Meta-parser SSRF via redirect chain →
redirect: "manual"with URL revalidation - CRITICAL: Meta-parser memory DoS via unbounded response.text() → streaming body read (100KB limit)
- CRITICAL: No rate limiting on enrichment server actions → per-user sliding window
- CRITICAL: Modules not registered (commented-out imports) → connectors.ts activated
- HIGH: IDOR in enrichmentResult.update → userId in all WHERE clauses (ADR-015)
- HIGH: Clearbit domain not validated → domain regex + redirect: "manual"
- HIGH: XSS via unsanitized OpenGraph data → sanitizeMetaValue + image URL validation
- HIGH: Orchestrator not using globalThis → HMR-safe singleton pattern
- HIGH: Missing DEGRADED health check → skip degraded + unreachable modules
- feat: Auto-trigger enrichment on CompanyCreated + VacancyPromoted events
- feat: Cockatiel resilience wrappers for all enrichment modules
- fix: S3 deferred MEDIUM items (F7 i18n, F6 toast, EDGE-3 CTA, D5 expired, D7 promoter)
- fix: WCAG Level A findings (health indicator, loading states, imageState reset)
- fix: Interaction design findings (status feedback, deactivation dialog, mobile settings)
- fix: extractDomain improved heuristic
- fix: Logo writeback logic deduplicated
docs/reviews/s4/consolidated-report.md-- 62 findings across 6 dimensionsdocs/reviews/s4/wcag-audit.md-- 12 WCAG 2.2 findings (7 Level A fixed)docs/reviews/s4/blind-spot-analysis.md-- 17 findings (failure modes, boundaries, data integrity)docs/adr/ADR-025-data-enrichment-connector.md-- Data Enrichment Connector Architecture decision
- 121 new tests across 8 suites (modules, orchestrator, actions, components)
- Total: 138 suites, 2569 tests passing
/comprehensive-review:full-review— Code Quality (13), Architecture (7), Security (8), Performance (7), Testing (7), Best Practices (12)/accessibility-compliance:wcag-audit-patterns— 21 WCAG 2.2 findings (5 Critical Level A)/ui-design:interaction-design— Interaction quality score 7.5/10/business-analytics:data-storytelling— Data storytelling maturity 2.5/10allium:weed— 8 new spec divergences (2 Medium, 6 Low)- 68 raw findings → 42 unique after deduplication across dimensions
- CRITICAL: Cross-user FK injection in addJob/updateJob — ownership verification for all FK inputs (CON-C01)
- HIGH: Cross-user data leak in addJobToQueue — createdBy filter on entity lookups (CON-H05)
- HIGH: getJobsList unbounded limit — clamped to MAX_LIMIT=200 (CON-H06)
- HIGH: Resume:true leaks File.filePath — explicit select excluding filePath (CON-H07)
- MEDIUM: handleError leaks raw Prisma errors — generic msg fallback (CON-M09)
- CRITICAL: Drag handle aria-label identical for all cards → per-card label + aria-describedby (CON-C02)
- CRITICAL: Collapse/expand buttons missing aria-expanded (CON-C03)
- CRITICAL: Mobile status Select unlabelled (CON-C04)
- CRITICAL: Search input + filter Select unlabelled (CON-C05)
- CRITICAL: ToastClose dismiss button no accessible name (CON-C06)
- MEDIUM: DragOverlay clone not hidden from a11y tree (WCAG-M03)
- MEDIUM: Column card list aria-label hardcoded English "jobs" → translated
- MEDIUM: getStatusLabel duplicated in 3 components → shared status-labels.ts (CON-M05)
- CRITICAL: DnD linear scan O(n×cols) at 60Hz → useMemo Map O(1) lookups (CON-C07)
- HIGH: Serial DB reads in changeJobStatus → Promise.all (CON-H01)
- HIGH: No React.memo on KanbanCard/KanbanColumn (CON-H02)
- HIGH: new Date() per card per render → module-scope getToday() + useMemo (CON-H03)
- HIGH: updateKanbanOrder missing note length validation (CON-H04)
- MEDIUM: Undo button shown for irreversible transitions → guard with isValidTransition (CON-M01)
- MEDIUM: StatusTransitionDialog note persists across reopenings → useEffect reset (CON-M13)
- MEDIUM: Stale closure in setUndoWithTimeout → useRef for timeout handle (CON-M07)
- Updated 142 test assertions for handleError generic message change
- Added FK ownership mock setup in job.actions.spec.ts and security-idor.spec.ts
- Total: 2390 tests passing (126 suites)
- Consolidated report with deduplication log, 42 findings across 7 dimensions
- Architecture review, Code quality review, Performance analysis, Test coverage analysis, WCAG audit
- Data storytelling gap analysis (5 recommendations)
- Allium weed report (8 divergences)
- a11y (CRITICAL): AutomationList cards — keyboard accessibility (tabIndex, onKeyDown, focus ring)
- a11y (CRITICAL): AutomationDetailHeader — aria-labels on icon-only back/refresh buttons
- a11y (HIGH): 13 spinners across 8 components — add
motion-reduce:animate-none - a11y (HIGH): SchedulerStatusBar — scope aria-live to sr-only span (prevent chatty announcements)
- a11y (HIGH): StagedVacancyCard — add aria-label to checkbox
- a11y (HIGH): Decorative icons missing aria-hidden in 3 components (12 icons)
- i18n (HIGH): AutomationMetadataGrid — translate status/jobBoard enum values
- ux (MEDIUM): RunStatusBadge — add hour formatting (≥3600s shows "Xh Ym Zs")
- ux (MEDIUM): RunHistoryList — add error state with retry + duration formatting
- 18 new RunHistoryList unit tests (error/retry/duration/status/blocked reasons)
- 2 new RunStatusBadge hour formatting tests (boundary + multi-hour)
- Updated AutomationMetadataGrid test mock for translated values
- Total: 2275 → 2295 tests (+20)
- Interaction design review — verified 5/15 prior claims, found 12 new issues
- WCAG 2.2 audit — 15 new findings (2 CRITICAL, 5 HIGH fixed)
- UX data story — coverage heatmap, edge case funnel, quality scorecard
- Consolidated report — 29 raw findings → 26 unique, 3 deduped, 8 fixed
scheduler-coordination.allium— add RunStatusBadge hours, RunHistoryList error/duration
- Root cause: "Formatter reverted S1b edits" was FALSE — no formatter exists in project
- Actual cause: S1b agent fabricated fix claims without making code changes
- axe-core: jest-axe integration with 4 a11y test files (8 tests, 0 violations)
- E2E: 9 new Playwright tests — staging-crud, settings-api-keys, settings-blacklist (68→77)
- Unit: 22 new tests for extracted components + UX behaviors (116→121 suites)
- ux (HIGH): RunHistoryList — hide 4 numeric columns on mobile
- ux (MISSING): RunHistoryList — add loading skeleton state
- ux (MISSING): RunProgressPanel — "Run completed" transition on run end (3s auto-hide)
- ux (HIGH): DeckView — mobile swipe hint ("Swipe to decide" on first card)
- a11y (MEDIUM): ViewModeToggle — roving tabindex + arrow key navigation
- StagingContainer 497→398 LOC: extract useStagingActions hook + StagingNewItemsBanner
- AutomationDetailPage 509→309 LOC: extract useConflictDetection + Header + MetadataGrid
- scheduler-coordination.allium: 3 surfaces updated, RunStatusBadge surface added
- vacancy-pipeline.allium: StagingQueue updated, DeckView surface added
- a11y (HIGH): aria-live regions for SchedulerStatusBar and RunProgressPanel state changes
- a11y (HIGH): Complete progressbar ARIA (valuemin, valuetext, label) on desktop + mobile
- a11y (HIGH): DeckView container missing focus indicator — added focus-visible:ring-2
- a11y (HIGH): DeckCard "Show more" button below 24px WCAG 2.5.8 target
- a11y (HIGH): AutomationList nested
<button>inside<Link>— restructured to div+router.push - a11y (HIGH): Non-focusable tooltip triggers in AutomationList + RunHistoryList → buttons
- a11y (HIGH): PublicApiKeySettings amber-600 contrast ~3.0:1 → orange-700 (~4.8:1)
- a11y: DeckCard match score amber SVG contrast improved
- a11y: motion-reduce:animate-none on 6 spinner instances (Loading, SchedulerStatusBar, Settings×4)
- a11y: RunStatusBadge live region throttled (status changes only, not per-second elapsed time)
- a11y: Decorative icons aria-hidden="true" across 8 components
- a11y: AutomationList scroll-mt-14 for sticky header focus obscuring
- a11y: ViewModeToggle focus-visible styles + increased touch targets
- i18n (HIGH): Translate raw
automation.status/automation.jobBoardin AutomationList - i18n (HIGH): Translate raw
blockedReasonin RunHistoryList (7 known reasons + fallback) - i18n (HIGH): Locale-aware elapsed time in RunStatusBadge (DE: "2 Min. 30 Sek.")
- i18n (HIGH): Remove
as Parameters<typeof t>[0]casts —as conston PHASE_KEYS - i18n: formatNumber for RunProgressPanel phase counters
- i18n: Remove
as anyon PAUSE_REASON_KEYS
- ux (HIGH): CompanyBlacklist delete now has AlertDialog confirmation
- ux (HIGH): Error states for initial load failure (PublicApiKeySettings, CompanyBlacklistSettings)
- ux (HIGH): StagingContainer stale vacancies flash on tab switch — clear on change
- ux: Public API search case-insensitive (
mode: 'insensitive'on SQLite) - ux: StagingContainer notification banner
role="status"for screen readers - ux: Removed Bootstrap
btn btn-primaryclasses from StagingContainer - ux: DeckView preview cards
aria-hiddenfrom assistive technology
- Updated api-v1-jobs, RunProgressPanel, RunStatusBadge test expectations for S2 changes
docs/user-journey-audit.md— comprehensive audit of 8 features and 14 components
__tests__/with-api-auth.spec.ts— 12 integration tests for security perimeter (TG-3)__tests__/api-v1-jobs.spec.ts— 52 functional tests for all 8 API v1 endpoints (TG-4)- 24 Allium spec divergences resolved across scheduler-coordination, security-rules, vacancy-pipeline
- Total test delta: +70 tests (2170 → 2240)
- sec (HIGH):
inferErrorStatus()broke with i18n keys — camelCase patterns now matched - sec (HIGH):
_statusResolvedsentinel could leak into Prisma update — isolated variable - sec (HIGH):
interview.deleteManylacked userId scope (ADR-015) — added defense-in-depth
- sec (CRITICAL): API v1 GET/PATCH/POST job responses leaked userId, matchData, foreign keys — replaced
includewith explicitselect - perf (CRITICAL): ConnectorCache singleton broken in production (0% hit rate) — unconditional globalThis assignment
- sec: degradation findUnique without userId (ADR-015) — changed to findFirst
- sec: IP rate limiting trusted spoofable x-forwarded-for — unique fallback per request
- sec: Misleading "constant-time" comment on API key validation — corrected
- sec: SSE endpoint lacked per-user connection limit — max 5 connections
- sec: Cache key injection via unsanitized delimiter — sanitize params segment
- sec: removeBlacklistEntry TOCTOU — atomic deleteMany
- perf: PATCH /api/v1/jobs/:id 9 sequential DB round-trips → Promise.all (~5)
- perf: POST /api/v1/jobs 5 sequential upserts → Promise.all
- perf: AutomationDetailPage duplicate getAutomationRuns call — removed
- perf: getBlacklistEntries unbounded query — added take:500
- perf: Cache eviction was FIFO → true LRU via Map re-insertion
- perf: Expired cache entries accumulated → periodic prune (15 min)
- perf: Notes endpoint unbounded — added pagination (default 25, max 100)
- i18n: 11x hardcoded English throws in publicApiKey.actions.ts → i18n keys
- i18n: 3x hardcoded English in companyBlacklist.actions.ts → i18n keys
- i18n: 5x hardcoded "Error" toast titles → t("common.error")
- arch: event-types.ts bidirectional coupling with scheduler → inlined RunSource
- a11y: ViewModeToggle radiogroup aria-label describes purpose
- code: UUID validation duplicated in 5 locations → shared isValidUUID()
- code: 4x duplicate findOrCreate helpers → shared helpers.ts
src/lib/api/helpers.ts— shared findOrCreate, resolveStatus, JOB_API_SELECT__tests__/validate-api-key.spec.ts— validateApiKey unit tests (TG-1)BlacklistMatchTypeextended with starts_with/ends_with + matcher
- perf: lastUsedAt DB writes throttled to max 1 per 5 minutes per key (PERF-1)
- perf: Dedup job URL query bounded to 90-day window (PERF-2)
- perf: Rate limiter Map capped at 10,000 entries with LRU eviction (PERF-3)
- perf: Legacy api-key-resolver.ts lastUsedAt also throttled (missed in initial fix)
- i18n: 16 hardcoded English strings in automation detail page (A9)
- ux: Run Now tooltip explains all disabled states — running, paused, resume missing (B6)
- a11y: BaseCombobox trigger now has
aria-expandedandtype="button"(WEED-1) - a11y: TagInput clears input on popover close by click-outside (WEED-2)
- test: company/job action tests aligned with IDOR security fixes (WEED-6)
- test: Jest config excludes
.tracks/worktree files (WEED-7) - e2e:
uniqueIddeduplication — keyboard-ux uses shared helper (WEED-3) - e2e:
e2e/.auth/added to .gitignore (WEED-4)
src/lib/api/last-used-throttle.ts— reusable DB write throttle utility__tests__/last-used-throttle.spec.ts— 7 unit tests for throttle logicdocs/gap-analysis-sprint-abc.md— Sprint A/B/C gap analysis (24/24 items DONE)
- All 19 Allium specs aligned with implementation (26+ divergences fixed)
- action-result: function inventory 117→139 (added stagedVacancy, blacklist, undo, publicApiKey)
- event-bus: async handlers, wildcard subscribe, scheduler/degradation events
- scheduler-coordination: two-phase model (removed polling/cooldown)
- module-lifecycle: blocked/rate_limited count as failures, cachePolicy on manifest
- notification-dispatch: quiet hours drop not queue, vacancy_batch_staged rename
- security-rules: AES-256-GCM (was incorrectly noted as CBC)
- profile-resume: per-user title uniqueness, LicenseOrCertification/OtherSection
- i18n-system: 3-step server locale resolution (DB→cookie→default)
- ai-provider: CredentialMode→CredentialType with none variant
- e2e-test-infrastructure: relaxed constraints to match actual test patterns
- api: Public API v1 Foundation (ROADMAP 7.1 Phase 1) — REST endpoints for Jobs CRUD + Notes with API Key auth, SHA-256 hashing, in-memory rate limiting (60 req/min), Zod validation, CORS, and ActionResult→HTTP bridge
- api: API Key Management UI in Settings — create/copy/revoke/delete keys with i18n (en/de/fr/es)
- blacklist: Company Blacklist (ROADMAP 2.15) — block companies from staging pipeline with name/pattern matching, Settings UI
- cache: Response Caching Stufe 1 (ROADMAP 0.9) — in-memory LRU cache for external API responses with HTTP cache headers on ESCO/EURES proxy routes
- staging: JobDeck swipe UI (ROADMAP 2.7 Phase 1) — card-based vacancy review with dismiss/promote/superlike actions, undo support, and Tailwind animations
- api: IDOR prevention on resume/tag ID associations (ownership validation)
- api: Max length constraints on all API input fields
- api: Cache-Control: no-store + X-Content-Type-Options: nosniff headers
- api: Per-user API key limit (max 10 active)
- api: Revoke-before-delete enforcement on API keys
- security: 25 vulnerability fixes — IDOR ownership checks, credential URL defense, auth secret fail-fast, input validation (SEC-1 to SEC-18, BS-1 to BS-7)
- jobs:
resumeId: ""caused P2003 FK constraint error when no resume selected — changed tonull
- e2e: Repaired all 68 E2E tests (was 8/68 passing) — stale data cleanup, networkidle→domcontentloaded, EURES→Arbeitsagentur, locale cookies, timing fixes, startTransition waits
- e2e: Playwright workers optimized: 3 default, 1 CI (was 4/7)
- specs: 10 Allium specifications distilled from codebase
- security: STRIDE threat model, ADRs 015-018, upstream bug reports
- roadmap: ROADMAP 8.5 E2E Repair completed
1.1.3 (2026-02-28)
1.1.2 (2026-02-28)
- display user email in profile dropdown instead of static text
- replace release-please workflow with local release script
- release 1.1.1
1.1.1 (2026-02-28)
- ui: display user email in profile dropdown instead of static text (2fee6ee)
- ui: display user email in profile dropdown instead of static text (bc39aa5)
1.1.0 (2026-02-28)
- add release automation (6fd8247)
- Add job draft date in job details (f6c2bb6)
- Admin tab switch (8c57052)
- bullet and order styling of editor content (423b0f4)
- button hydration error (d7e97a0)
- Combobox filter issue (1ab477e)
- Combobox undefined error (fdaa9fe)
- configure release-please to target dev branch (9ca7db0)
- Create company bug when adding experience (c992077)
- DatePicker bug in Safari browser (0f24106)
- Dialog scroll (93f8e7d)
- Edit company (d7a15e2)
- Error accessing ollama api endpoint in docker (83aa24a)
- Failing Tasks playwright tests (4c2cecf)
- hydration error, minor refactor (6d2db31)
- job status undefined issue (91d3097)
- jobsApplied based on applied field (d0ad166)
- login error validation (7df090a)
- minor layout issues (55e1e42)
- no matching decryption secret (b8f3919)
- openssl not found (290a1a7)
- resume undefined issue (dbe01a9)
- Revalidate company list in addjob when adding company (785c49b)
- route path (4234c08)
- session based conditional rendering (b008e1b)
- add release automation (6fd8247)
- Add job draft date in job details (f6c2bb6)
- Admin tab swich (8c57052)
- bullet and order styling of editor content (423b0f4)
- button hydration error (d7e97a0)
- Combobox filter issue (1ab477e)
- Combobox undefined error (fdaa9fe)
- configure release-please to target dev branch (9ca7db0)
- Create company bug when adding experience (c992077)
- DatePicker bug in Safari browser (0f24106)
- Dialog scroll (93f8e7d)
- Edit company (d7a15e2)
- Error accessing ollama api endpoint in docker (83aa24a)
- Failing Tasks playwright tests (4c2cecf)
- hydration error, minor refactor (6d2db31)
- job status undefined issue (91d3097)
- jobsApplied based on applied field (d0ad166)
- login error validation (7df090a)
- minor layout issues (55e1e42)
- no matching decryption secret (b8f3919)
- openssl not found (290a1a7)
- resume undefined issue (dbe01a9)
- Revalidate company list in addjob when adding company (785c49b)
- route path (4234c08)
- session based conditional rendering (b008e1b)