Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .claude/project/slices/slice-021-notification-recipient.md
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 53 additions & 0 deletions .claude/project/slices/slice-022-panel-branding.md
Original file line number Diff line number Diff line change
@@ -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 `<title>`, 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.
47 changes: 47 additions & 0 deletions .claude/project/slices/slice-023-editor-paste-html.md
Original file line number Diff line number Diff line change
@@ -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).
50 changes: 50 additions & 0 deletions .claude/project/slices/slice-024-editor-sticky-toolbar.md
Original file line number Diff line number Diff line change
@@ -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).