From 32bf2397972430c833527e814dde7ee8c8db4a35 Mon Sep 17 00:00:00 2001 From: Christoph Raschke Date: Wed, 8 Jul 2026 15:06:06 +0200 Subject: [PATCH] docs(slices): archive slices 021-024 (staging-findings 0.6.0) Reconcile plan/state drift: slices 021 (notification recipient), 022 (panel branding), 023 (paste-HTML editor button) and 024 (sticky editor toolbar) all shipped to main (PRs #33-#36) but were never archived. Add Decision-Log entries reconstructed from the plan intent + merged diffs; the ephemeral plan files and the staging-findings-0.6.0 tracking doc are removed (untracked/ignored). --- .../slice-021-notification-recipient.md | 67 +++++++++++++++++++ .../slices/slice-022-panel-branding.md | 53 +++++++++++++++ .../slices/slice-023-editor-paste-html.md | 47 +++++++++++++ .../slices/slice-024-editor-sticky-toolbar.md | 50 ++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 .claude/project/slices/slice-021-notification-recipient.md create mode 100644 .claude/project/slices/slice-022-panel-branding.md create mode 100644 .claude/project/slices/slice-023-editor-paste-html.md create mode 100644 .claude/project/slices/slice-024-editor-sticky-toolbar.md diff --git a/.claude/project/slices/slice-021-notification-recipient.md b/.claude/project/slices/slice-021-notification-recipient.md new file mode 100644 index 0000000..95b2cac --- /dev/null +++ b/.claude/project/slices/slice-021-notification-recipient.md @@ -0,0 +1,67 @@ +# Slice 021 — Operator notification recipient (fix + operator-managed address) + +> Completed: 2026-07-06 +> Commits: 66a1fc2 (PR #33) +> Origin: staging finding **A** (0.6.0), highest priority. + +## What + +The withdrawal notification e-mail now reaches a **defined, operator-managed +recipient** instead of silently not sending. A new `NotificationSettings` +(`spatie/laravel-settings`, one `notification_email` field) is edited on a new +Filament `ManageNotifications` settings page, which also carries a **"Test-Mail +senden"** action (synchronous send to the resolved recipient, Filament +success/error notification) and a read-only "mail transport configured?" hint. +A `NotificationRecipient::resolve()` resolver feeds the recipient into +`SendWithdrawalNotifications`. The spam verdict is now a **prominent subject +prefix** instead of a trailing tag. The consumer acknowledgment (§ 356a Abs. 4) +is untouched. + +## Why + +On staging the operator received nothing: `SendWithdrawalNotifications` only sent +when `config('revoco.merchant_email')` (env `MERCHANT_NOTIFICATION_EMAIL`, +default `null`) was set — env-only and unset. The recipient had to become +operator-manageable with a safe fallback, and the operator needed a way to prove +delivery without editable SMTP credentials. + +## Decisions + +- **Secret vs. business-config split.** SMTP transport stays in `MAIL_*` env + (secrets do not belong in a DB settings table); the notification **recipient** + is business config → operator-managed panel setting. Mirrors the project's + existing split (transport in env, legal content in DB settings). +- **Recipient precedence:** `NotificationSettings::notification_email` (panel) + → `config('revoco.merchant_email')` (env, backward-compat) → + `LegalSettings::imprint_email` (the already-required imprint address) → + `null` (nothing sends; panel shows an "unconfigured" hint). Empty string is + treated as unset at each level. Serves both the automated/Docker deployer (env) + and the plain-webspace operator (panel). +- **"Send test e-mail" button, not editable SMTP.** A synchronous test action + a + read-only "configured?" indicator — the comfort win for non-technical operators + — without moving credentials into the DB. +- **E-mail templates stay in code.** § 356a Abs. 4 (advertising-free + acknowledgment) compliance risk + scope; not operator-editable. +- **Body & push already complete.** The notification body already lists every + field + spam status/reason and the push already carries the spam signal — this + slice only added the recipient surface + subject prominence. + +## Commits + +- `66a1fc2` — feat(notifications): operator-managed notification recipient + test + mail + prominent spam subject + +Files: `app/Settings/NotificationSettings.php`, +`app/Support/NotificationRecipient.php`, +`app/Filament/Pages/ManageNotifications.php`, +`app/Listeners/SendWithdrawalNotifications.php`, +`app/Mail/WithdrawalNotification.php`, `config/settings.php`, +`database/settings/2026_07_06_000000_create_notification_settings.php`, +`lang/{de,en}/panel.php`, `lang/de/mail.php`, +`tests/Feature/NotificationRecipientTest.php`. + +## Follow-ups + +- Archived retroactively during the 2026-07-08 plan/state reconciliation: the + slice shipped straight to `main` via PR #33 without a formal Phase 6–9 recap + pass, so this entry is reconstructed from the plan intent + the merged diff. diff --git a/.claude/project/slices/slice-022-panel-branding.md b/.claude/project/slices/slice-022-panel-branding.md new file mode 100644 index 0000000..3ad77a0 --- /dev/null +++ b/.claude/project/slices/slice-022-panel-branding.md @@ -0,0 +1,53 @@ +# Slice 022 — Admin panel branding (Revoco name, logo, favicon, title) + +> Completed: 2026-07-06 +> Commits: 833ac95 (PR #34) +> Origin: staging finding **B** (0.6.0) — small bug. + +## What + +The Filament admin panel (login page, header, browser ``, favicon) now +renders as **Revoco** instead of falling back to "Laravel". +`AdminPanelProvider::panel()` gained `brandName('Revoco')`, `brandLogo` + +`darkModeBrandLogo` (light/dark SVG wordmarks), `brandLogoHeight`, and `favicon`. +The brand assets are served from `public/img/`. The neutral consumer form is +unchanged. + +## Why + +`AdminPanelProvider` set no branding, so Filament fell back to +`config('app.name')` — which resolves to the "Laravel" default on a deployment +that does not set `APP_NAME` (the prod image ships no dev `.env`). Hardcoding the +brand in the provider makes the panel correct out-of-the-box on **every** +deployment, independent of env. + +## Decisions + +- **Explicit `brandName('Revoco')`, not reliance on `APP_NAME`.** The prod image + ships no dev `.env`; an operator who does not set `APP_NAME` would otherwise see + "Laravel". +- **Revoco branding in the panel does not violate consumer neutrality.** The + neutrality rule governs the consumer-facing withdrawal form (operator-brandable + via `APP_THEME`/`logo_url`); the admin panel is Revoco's own operator tool, so + Revoco branding there is intended. +- **Panel branding is fixed (Revoco), not operator-overridable** for now — the + operator asked for the Revoco mark in the backend; per-operator panel branding + is out of scope. +- **SVG-only assets** — `revoco-logo.svg`, `revoco-logo-dark.svg`, + `revoco-favicon.svg`. No raster `.ico`/`.png` favicon fallback was added; the v5 + panel serves the SVG favicon directly. + +## Commits + +- `833ac95` — feat(panel): brand the admin panel as Revoco (logo, dark logo, + favicon, title) + +Files: `app/Providers/Filament/AdminPanelProvider.php`, +`public/img/revoco-logo.svg`, `public/img/revoco-logo-dark.svg`, +`public/img/revoco-favicon.svg`, `tests/Feature/PanelBrandingTest.php`. + +## Follow-ups + +- Archived retroactively during the 2026-07-08 plan/state reconciliation (shipped + via PR #34 without a formal Phase 6–9 pass). +- If a deployment target needs a raster favicon, add an `.ico`/`.png` fallback. diff --git a/.claude/project/slices/slice-023-editor-paste-html.md b/.claude/project/slices/slice-023-editor-paste-html.md new file mode 100644 index 0000000..86d889d --- /dev/null +++ b/.claude/project/slices/slice-023-editor-paste-html.md @@ -0,0 +1,47 @@ +# Slice 023 — "Paste HTML" button in the legal rich editors + +> Completed: 2026-07-06 +> Commits: f24ef03 (PR #35) +> Origin: staging finding **C** (0.6.0) — comfort feature. + +## What + +The privacy-policy and imprint-addendum RichEditors in `ManageLegal` gained a +**"HTML einfügen"** toolbar button. It opens a modal with a plain `Textarea`; on +save the pasted HTML is **sanitized** and inserted into the editor as formatted, +editable content. Implemented as a Filament v5 `App\Filament\RichContent\ +PasteHtmlPlugin` (`getEditorTools()` → the toolbar button; `getEditorActions()` → +the modal `Action`), registered on the legal editors via `->plugins([...])` with +`pasteHtml` added to their `toolbarButtons()`. + +## Why + +Operators receive law-firm privacy policies as HTML (or plain text). Pasting +directly into a rich editor is fiddly and lossy; a paste-as-HTML modal makes +importing a real legal text painless. + +## Decisions + +- **Filament v5 `RichContentPlugin` is the supported path** — v5 exposes + `getEditorTools()` + `getEditorActions()` + `runCommands()`/`EditorCommand`, + purpose-built for a toolbar button that opens a modal and runs a TipTap command, + so no hacking around the editor. +- **Sanitize on insert, reusing `Str::sanitizeHtml()`** — pasted third-party HTML + is untrusted and ends up on the public legal pages; sanitizing with the exact + tool the render path uses avoids a sanitize mismatch and stored-XSS. The action + passes the sanitized (not raw) content to the insert command. +- **Insert at cursor, not replace-all** — non-destructive; fills an empty editor + naturally. A "replace" affordance was left for later if hands-on shows a need. + +## Commits + +- `f24ef03` — feat(legal): add a "paste HTML" button to the legal rich editors + +Files: `app/Filament/RichContent/PasteHtmlPlugin.php`, +`app/Filament/Pages/ManageLegal.php`, `lang/{de,en}/panel.php`, +`tests/Feature/PasteHtmlPluginTest.php`. + +## Follow-ups + +- Archived retroactively during the 2026-07-08 plan/state reconciliation (shipped + via PR #35 without a formal Phase 6–9 pass). diff --git a/.claude/project/slices/slice-024-editor-sticky-toolbar.md b/.claude/project/slices/slice-024-editor-sticky-toolbar.md new file mode 100644 index 0000000..6f1f589 --- /dev/null +++ b/.claude/project/slices/slice-024-editor-sticky-toolbar.md @@ -0,0 +1,50 @@ +# Slice 024 — Sticky rich-editor toolbar + +> Completed: 2026-07-06 +> Commits: 2bdbdce (PR #36) +> Origin: staging finding **D** (0.6.0) — comfort feature. + +## What + +The Filament RichEditor toolbar now **sticks to the top** of the scroll area +(below Filament's own sticky topbar) while editing a long legal text, so the +formatting buttons stay reachable instead of scrolling out of view. Scoped CSS +(`position: sticky`) applied to the panel RichEditors only; no PHP behavior +change beyond registering the stylesheet. + +## Why + +Long pasted privacy/imprint texts pushed the toolbar out of view, forcing the +operator to scroll back up to format. Pinning the toolbar keeps it usable +throughout a long edit. + +## Decisions + +- **CSS `position: sticky`, no native flag** — Filament v5 exposes no built-in + sticky-toolbar option, so this is a scoped CSS customization, kept minimal and + panel-scoped so it does not leak onto other components. +- **Stick below the topbar, not at `top: 0`** — Filament's own topbar is sticky; + the editor toolbar offsets by its height (with an opaque background + `z-index` + above content) so it never renders underneath. +- **Implementation diverged from the plan** — instead of a `FilamentAsset`- + registered stylesheet, the CSS is injected via a Filament panel **render hook** + wired in `AppServiceProvider`, rendering the Blade partial + `resources/views/panels/sticky-editor-toolbar.blade.php`. Same effect, thinner + wiring than a registered asset file. The test asserts the hook/partial is + present on the panel. +- **Visual feature → Phase-5 is the real gate** — automated coverage is thin by + nature (registration only); correctness is confirmed hands-on against a long + real text. + +## Commits + +- `2bdbdce` — feat(panel): sticky rich-editor toolbar for long legal texts + +Files: `app/Providers/AppServiceProvider.php`, +`resources/views/panels/sticky-editor-toolbar.blade.php`, +`tests/Feature/StickyEditorToolbarTest.php`. + +## Follow-ups + +- Archived retroactively during the 2026-07-08 plan/state reconciliation (shipped + via PR #36 without a formal Phase 6–9 pass).