Commit Graph

1061 Commits

Author SHA1 Message Date
schalli 4ed1889c8d docs(07-03): complete DKV export + SMTP settings plan
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Waiting to run
- 07-03-SUMMARY.md: all 4 tasks documented, threat mitigations verified,
  user-files/ path resolution and MailModule factory priority chain explained
- STATE.md: advanced to Plan 4 of 6, added 5 new decisions, metrics row
- ROADMAP.md: 07-03 checked complete, Phase 7 progress 3/6
2026-06-27 00:16:05 +02:00
schalli de48e35c74 feat(07-03): Migrate MailModule to DB-sourced SMTP transport with env fallback (D-06)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Waiting to run
- MailerModule.forRootAsync factory now async; injects SettingsService + ConfigService
- Priority 1: getStartupSmtpConfig() reads first SmtpConfig DB row (single-tenant default)
  — T-07-11: decrypted password used only to build transport, never logged
- Priority 2: env vars MAIL_HOST/MAIL_PORT/MAIL_USER/MAIL_PASS
- Priority 3: legacy TESSERA_SMTP_* env vars (backward compat)
- Priority 4: localhost:1025 hardcoded final fallback (Mailhog dev default)
- imports SettingsModule; no circular import (MailModule → SettingsModule → CalendarModule)
- mail.service.ts unchanged — still injects @nestjs-modules/mailer MailerService
2026-06-27 00:12:43 +02:00
schalli 4deefb52de feat(07-03): DkvMailService — runtime nodemailer transport with xlsx attachment (DKV-04)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Waiting to run
- createTransport() called per-send from DB SmtpConfig (Pitfall 3 mitigation — not at startup)
- Injects SettingsService to load decrypted SMTP config per tenant
- secure/requireTLS mapped from encryption field (ssl-tls / starttls / none)
- Auth omitted when username absent (anonymous relay support)
- Attachment contentType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- Error path rethrows after generic log (T-07-10) so DkvService can run 3-retry backoff (D-16)
- Does not import @nestjs-modules/mailer abstractions
2026-06-27 00:11:59 +02:00
schalli 6b76ca9633 feat(07-03): DkvExportService — xlsx generation + user-files/ prune (DKV-04)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 41s
Tessera CI/CD / Tests (push) Waiting to run
- buildExcelBuffer: 5-column xlsx per D-13 (Lieferdatum as string, never Date), SheetJS aoa_to_sheet
- resolveFahrzeug: replaces {Marke}/{Modell}/{Kennzeichen}/{Fahrer} tokens in format string (D-19)
- writeAndPrune: server-side filename DKV_YYYY-MM_<nr>.xlsx (T-07-09 path-traversal prevention),
  writes to user-files/ (resolved from monorepo root, not request input), prunes to last 10 DKV_*.xlsx
  files sorted by mtime ascending (D-15, Pitfall 7 atomicity)
2026-06-27 00:10:56 +02:00
schalli 1bec0e76ee feat(07-03): SettingsModule — SMTP config backend + connection test (DKV-05)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 37s
Tessera CI/CD / Tests (push) Waiting to run
- SmtpConfigDto: host/port/encryption/username/password/fromAddress with class-validator
- SettingsService: getSmtpConfig (SMTP_SAFE_SELECT, no password), saveSmtpConfig (AES-256-GCM
  encryption via CalendarCryptoService, preserve existing password on empty), getDecryptedSmtpConfig
  (internal, used by DkvMailService), testSmtpConfig (nodemailer.verify(), returns boolean, T-07-16),
  getStartupSmtpConfig (tenant-agnostic, used by MailModule factory, D-06)
- SettingsController: GET/PUT /settings/smtp + POST /settings/smtp/test, all @Roles(ADMIN, SUPER_ADMIN)
- SettingsModule: imports CalendarModule, exports SettingsService
- AppModule: imports SettingsModule
2026-06-27 00:09:47 +02:00
schalli c7b0103a10 docs(07-02): complete DKV inbox-access layer plan
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Waiting to run
- 07-02-SUMMARY.md: interface + IMAP + Exchange providers, all 3 DTOs, self-check passed
- STATE.md: advance to Plan 3/6, record 3 decisions, update session to 2026-06-27
- ROADMAP.md: mark 07-01 and 07-02 complete, Phase 7 progress 2/6
2026-06-27 00:06:24 +02:00
schalli 924de76f93 feat(07-02): ExchangeInboxProvider — EWS email inbox access
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 41s
Tessera CI/CD / Tests (push) Waiting to run
- Implements InboxProvider contract (fetchPdfAttachments + testConnection)
- Uses WellKnownFolderName.Inbox + FindItems + EmailMessage.Bind — NOT FindAppointments (Pitfall 6)
- Dynamic import('ews-javascript-api') following ExchangeProvider calendar pattern
- Server-side sender filter via SearchFilter.ContainsSubstring with client-side verification
- 25MB attachment size guard in extractPdfAttachments — PDF-bomb mitigation (T-07-05)
- Generic error messages only in all catch blocks — no credential values (T-07-03)
- Returns [] on error (consistent with calendar ExchangeProvider error path)
2026-06-27 00:03:56 +02:00
schalli b3f21a8747 feat(07-02): ImapProvider — IMAP inbox access via imapflow
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Waiting to run
- Implements InboxProvider contract (fetchPdfAttachments + testConnection)
- fetchAll() called before any download() — avoids IMAP connection deadlock (Pitfall 1)
- ImapFlow constructed with logger:false — credential safety (T-07-03)
- 25MB attachment size guard in streamToBuffer — PDF-bomb mitigation (T-07-05)
- collectPdfParts() recursively traverses MIME tree for application/pdf parts
- Generic error messages only — no credential values in logs (T-07-03)
- secure/requireTLS flags derived from encryption field (ssl-tls vs starttls)
2026-06-27 00:02:00 +02:00
schalli 86ec8966d0 feat(07-02): InboxProvider interface + config/vehicle/history DTOs
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Waiting to run
- inbox-provider.interface.ts: InboxProvider contract with fetchPdfAttachments + testConnection; re-exports InboxConfig/InboxEmail/InboxAttachment with export type (isolatedModules)
- dkv-config.dto.ts: DkvConfigDto with @IsEmail() senderFilter/exportRecipient, @Min(5) pollIntervalMin, @IsIn() protocol/encryption (T-07-04)
- dkv-vehicle.dto.ts: CreateVehicleDto (all required @IsNotEmpty) + UpdateVehicleDto (all optional)
- dkv-history.dto.ts: DkvHistoryQueryDto with @IsInt @Min(1) page/limit pagination (T-07-06)
- Fix: export type re-exports required for isolatedModules TypeScript setting
2026-06-27 00:00:14 +02:00
schalli 6bf032ef94 docs(07-01): complete DKV backend foundation plan
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Waiting to run
- 07-01-SUMMARY.md: execution record with PDF parser analysis, regex deviation, DB push method
- STATE.md: plan counter advanced to 2/6, decisions added, session updated
2026-06-26 19:39:18 +02:00
schalli 6235aaf31c feat(07-01): DKV PDF parser validated against real invoice + injectable service
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 41s
Tessera CI/CD / Tests (push) Waiting to run
- dkv-parser.validate.ts: empirical validation script against user-files/invoice.pdf
  - 27 vehicle blocks, 66 transactions extracted (matches expected count)
  - Handles two PDF extraction formats: single-tx (tab-separated) + multi-tx (columnar)
  - German number parsing: replace(/\./g,'').replace(',','.') applied to km and menge
  - Exits 1 with full raw text dump if zero vehicle blocks parsed (assertion guard)
- dkv-parser.service.ts: @Injectable() NestJS service wrapping validated logic
  - parsePdf(buffer: Buffer): Promise<DkvVehicleBlock[]>
  - Uses pdf-parse v2 class API: new PDFParse({data:buffer}) — NOT v1 pdfParse()
  - Calls destroy() after extraction (T-07-01 memory safety)
  - Generic error messages only on parse failure (T-07-02 info disclosure)

Regex adjustment vs Research Pattern 4: space-based regex replaced with
tab-split (single-tx) + columnar transpose (multi-tx) after empirical analysis
of actual invoice.pdf text extraction output.
2026-06-26 19:36:45 +02:00
schalli c4b39ccd1b feat(07-01): install DKV deps, add Prisma models, wire ScheduleModule + crypto export
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 37s
Tessera CI/CD / Tests (push) Waiting to run
- pnpm add imapflow@^1.4.3, pdf-parse@^2.4.5, xlsx@^0.18.5 to @tessera/api
- Append DkvModuleConfig, DkvVehicleMaster, DkvInvoiceHistory, SmtpConfig models to schema.prisma (15 models total)
- Add ScheduleModule.forRoot() to AppModule imports (prerequisite for DkvSchedulerService)
- Export CalendarCryptoService from CalendarModule (needed by DkvModule + SettingsModule)
- Create apps/api/src/dkv/dkv.types.ts with DkvVehicleBlock, DkvTransaction, InboxConfig, InboxEmail, InboxAttachment, ExportRow interfaces
2026-06-26 19:25:42 +02:00
schalli de06794e67 docs(07): create phase 7 execution plans for DKV fleet module
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Waiting to run
6 plans covering full pipeline: PDF parsing foundation (Wave 0),
inbox providers + export/SMTP services (Wave 1), pipeline
orchestration + frontend pages + settings UI (Wave 2). Includes
D-06 MailModule DB-config migration and Nyquist validation strategy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 18:56:59 +02:00
schalli e9f4f2dcad docs(07): UI design contract and validation strategy for DKV fleet module
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Waiting to run
2026-06-26 14:55:41 +02:00
schalli baf502d967 docs(07): fix blocking typography and spacing issues in UI-SPEC
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Waiting to run
Collapse Display h1 weight from 700 to 600 (font-semibold) to keep
declared weights at exactly two (400 + 600). Remove 12px md token from
named spacing scale; move px-3/py-1.5 to Component Exceptions subsection.
Apply non-blocking recommendations: focal point sentence, aria-labels for
icon-only AKTIONEN buttons, and more specific form CTA copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 14:53:49 +02:00
schalli 0c33fd6a34 docs(07): UI design contract for DKV Fleet Module
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Waiting to run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 14:49:10 +02:00
schalli 741feb5946 docs(07): research phase DKV fleet module
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Waiting to run
2026-06-26 14:37:51 +02:00
schalli 4ee23bd9a4 docs(state): record phase 7 context session
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Waiting to run
2026-06-26 14:22:38 +02:00
schalli 34f06c9aa6 docs(07): capture phase context for DKV Fleet Module
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Waiting to run
2026-06-26 14:22:34 +02:00
schalli c69c7b2900 wip: pause — UI-Umbau fertig, DKV-Modul-Planung ausstehend
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Failing after 12m53s
Tessera CI/CD / Build & Deploy (push) Has been skipped
2026-06-26 12:16:05 +02:00
schalli ba02b25cba refactor(ui): restructure sidebar and admin navigation
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Failing after 45s
Tessera CI/CD / Build & Deploy (push) Has been skipped
- Sidebar: remove footer (user info, settings, locale switcher), remove admin section, make category headers static with per-category collapse toggle
- Header dropdown: replace inline admin links with single "Administrator" entry
- Admin section: dedicated layout with AdminSidebar (mirrors Settings pattern) at /admin/*
- Disable self-delete button in user management (backend already rejects with 403)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 08:34:19 +02:00
schalli 02652362f3 docs: pause work — v1.0 complete, next session: new modules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:58:38 +02:00
schalli 9f78580606 feat(domaincheck): support all TLDs with suggestion alternatives
Tessera CI/CD / Lint & Type Check (push) Successful in 1m8s
Tessera CI/CD / Tests (push) Successful in 1m13s
Tessera CI/CD / Build & Deploy (push) Successful in 2m39s
- Accept full domains (e.g. "example.xyz") not just labels
- Check the entered TLD as primary result
- Show .de, .com, .net, .org as alternative suggestions below
- Primary result highlighted with accent border
- Input without TLD still works (shows all 4 suggestions)
- Updated i18n placeholders and added "suggestions" label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 14:54:30 +02:00
schalli 95423497ad docs(06): complete Phase 06 — desktop wrapper + CI/CD pipeline verified
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 45s
Tessera CI/CD / Build & Deploy (push) Successful in 39s
- 06-01: Tauri scaffold complete
- 06-02: Native features (tray, window-state, autostart, version check, AppImage)
- 06-03: Gitea CI/CD pipeline green (Run #89 — all 3 jobs pass)
- Clean up handoff and continue-here files
- Update STATE.md to 100% (all 6 phases complete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 14:09:17 +02:00
schalli c65ada0ba9 feat(06-02): add native desktop features — tray, window-state, autostart, version check
- Add GET /health/version public endpoint to API
- Extend Tauri with 4 plugins: notification, autostart, window-state, store
- Implement close-to-tray with prevent_close + prevent_exit (Pitfall 2)
- Tray menu with Oeffnen/Beenden (German labels)
- Async startup version check against server /health/version
- Generate Tessera-branded icons (yellow T on dark bg, OKLCH palette)
- Update capabilities for notification, autostart, window-state permissions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 14:01:12 +02:00
schalli 4d94a254fd fix(06-03): add .gitkeep to apps/web/public for Docker build
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Deploy (push) Successful in 1m58s
Git doesn't track empty directories, so apps/web/public is missing
in CI checkout. The web Dockerfile COPY --from=builder fails when
public dir doesn't exist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 13:19:33 +02:00
schalli c48e61f95d fix(06-03): make prisma postinstall conditional for Docker multi-stage build
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 46s
Tessera CI/CD / Build & Deploy (push) Failing after 1m42s
In Docker deps stage only package.json files are copied (no schema),
causing prisma generate to fail. Builder stage already runs explicit
prisma generate with full source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 13:15:15 +02:00
schalli e81dbb0e69 docs(06): pause work — 06-01 complete, 06-03 pending CI verification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 12:37:49 +02:00
schalli faff50b1ee fix(06-03): add prisma generate postinstall for CI type-check
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Successful in 46s
Tessera CI/CD / Build & Deploy (push) Failing after 51s
CI environment lacks generated Prisma types after pnpm install.
Adding postinstall script ensures prisma generate runs automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 11:56:16 +02:00
schalli e5d45e121b fix(06-03): replace pnpm/action-setup with corepack for Gitea Actions compatibility
Tessera CI/CD / Lint & Type Check (push) Failing after 49s
Tessera CI/CD / Tests (push) Has been skipped
Tessera CI/CD / Build & Deploy (push) Has been skipped
pnpm/action-setup@v4 fails in Gitea Actions runners. Use corepack (built into Node.js)
to activate pnpm@9.15.0 matching the packageManager field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 11:38:04 +02:00
schalli 2e4d499730 docs(06-03): complete CI/CD pipeline plan
Tessera CI/CD / Lint & Type Check (push) Failing after 1m10s
Tessera CI/CD / Tests (push) Has been skipped
Tessera CI/CD / Build & Deploy (push) Has been skipped
2026-06-25 11:01:55 +02:00
schalli 756925bd25 feat(06-03): create Gitea Actions multi-stage CI/CD pipeline
- Three chained jobs: quality (lint+type-check) -> test (vitest) -> build-deploy
- Uses plain docker compose build/up (not build-push-action, avoids Pitfall 4)
- Triggers on push to main branch only (D-11, D-12)
- No hardcoded secrets in workflow (T-06-08)
2026-06-25 10:58:50 +02:00
schalli c0e32939e7 feat(06-03): add act_runner compose definition and CI/CD setup runbook
- docker-compose.ci.yml with act_runner service (ephemeral mode, Docker socket mount)
- docs/ci-cd-setup.md runbook covering Gitea remote, runner setup, secrets, security
- Registration token referenced from environment, never hardcoded (T-06-08)
2026-06-25 10:57:45 +02:00
schalli f41e673dd5 docs(06-01): complete desktop Tauri scaffold plan 2026-06-25 10:17:22 +02:00
schalli d944aaa5a0 feat(06-01): add first-run setup page with server URL input and validation
- Create setup.html with centered card on dark OKLCH background
- Validate URL via URL constructor, reject malformed input (T-06-01)
- Warn on non-HTTPS non-localhost URLs but allow (internal LAN support)
- Persist server_url to tauri-plugin-store config.json
- Navigate WebView to configured server after save
- All visible strings in German (Verbinden, Server-URL eingeben, etc.)
- Design tokens match Tessera OKLCH color system (primary, dark bg)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 10:14:17 +02:00
schalli 48e3a693aa feat(06-01): scaffold apps/desktop as @tessera/desktop Tauri 2.x project
- Create Tauri project structure with Cargo.toml, tauri.conf.json, build.rs
- Implement lib.rs with store plugin and server URL navigation on startup
- Configure capabilities with core:default and store:default permissions
- Set frontendDist to ../src for local setup page (no bundled frontend)
- Add placeholder icons for build compatibility (to be replaced in 06-02)
- Add Cargo target/ to .gitignore
- Window config: 1280x800, centered, native decorations, resizable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 10:13:20 +02:00
schalli 943cc32da7 docs(06): create phase plan 2026-06-25 09:27:16 +02:00
schalli 44154a4697 docs(06-desktop-client-ci-cd): create phase plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 09:25:24 +02:00
schalli 3d8c0660f7 docs(06): research phase domain - Tauri 2.x desktop wrapper + Gitea Actions CI/CD 2026-06-25 09:15:43 +02:00
schalli d6faa680b1 docs(state): record phase 6 context session 2026-06-25 08:35:35 +02:00
schalli a1ba4ef3de docs(06): capture phase context 2026-06-25 08:35:28 +02:00
schalli 380f85058d chore: add .planning/user-files/ to .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 22:44:10 +02:00
schalli ed410eb268 wip: phase 05 complete, pause work 2026-06-24 22:41:29 +02:00
schalli 8a55806a35 docs(05-05): visual verification approved 2026-06-24 22:39:59 +02:00
schalli 184370b759 fix(05): convert flat i18n widget keys to nested structure for next-intl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 22:04:16 +02:00
schalli 37e09dc66f fix(05): add dev default for CALENDAR_ENCRYPTION_KEY in docker-compose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:37:48 +02:00
schalli 6965e52bf7 docs(05-04): complete calendar frontend plan 2026-06-24 15:28:24 +02:00
schalli 24de136b9b feat(05-04): calendar settings page with source management and visibility toggle
- calendar-settings-panel.tsx: source list with color dots, type badges, visibility toggle (CAL-02), edit/delete actions, connection test auto-run, delete confirmation dialog
- calendar-source-form.tsx: add/edit form with name/type/URL/credentials/color; Exchange-mode select for exchange type; username/password hidden for ICS; client-side https-only validation (T-05-14)
- settings/dashboard/calendar/page.tsx: route page rendering CalendarSettingsPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:26:06 +02:00
schalli f99ff9a498 test(05-04): add failing tests for calendar settings panel
- Three test cases: source list with name/type/visibility, visibility toggle calls updateSource, form validation
- Mocks calendar-api functions following existing test patterns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:24:09 +02:00
schalli c0f2f4ca51 feat(05-04): calendar API client, calendar widget, registry wiring
- calendar-api.ts: fetchSources/addSource/updateSource/deleteSource/testSource/fetchEvents with credentials:'include'
- calendar-widget.tsx: upcoming-events list with source color dots, three empty states (no sources/no events/loading)
- widget-registry.tsx: wireCalendarWidget() replaces placeholder with real CalendarWidget
- page.tsx: wires CalendarWidget into registry on mount
- i18n: added calendar.loading key to de.json and en.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:23:27 +02:00