diff --git a/.gitignore b/.gitignore index 446c387..8f6a83a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ frankenphp frankenphp-worker.php /tests/Browser/Screenshots + +# Planning notes stay local +/docs/plans diff --git a/docs/plans/colour-profiles.md b/docs/plans/colour-profiles.md deleted file mode 100644 index 49f7edb..0000000 --- a/docs/plans/colour-profiles.md +++ /dev/null @@ -1,210 +0,0 @@ -# Colour profiles - -## Goal - -An installation of SealShare can wear one of eight colour profiles instead of the single indigo -scheme. The admin picks the profile in Admin settings, previews it on the page while choosing, and -on Save it applies to everyone: signed-in users, recipients on the upload and download pages, the -Markdown mails and the error pages. Livewire Material learns colour profiles in general — any -application lists its own in config, the package generates them, switches between them before the -first paint and follows the active one everywhere it draws colour — and ships it as 1.1.0 before -SealShare tags 2.0.0. - -## Context - -**Livewire Material 1.0.1** (`../livewire-material`): - -- `php artisan material:scheme {seed} --variant= --contrast= --success= --warning= --info= --output=` - (`src/Console/SchemeCommand.php`) runs `resources/node/scheme.mjs` (Google's - material-color-utilities, a 93 KB bundle) through Node and writes `resources/css/material-scheme.css` - — `:root, [data-theme='light'] { color-scheme: light; --md-sys-color-*: … }` and - `[data-theme='dark'] { … }`, about 60 roles each — and `material-scheme.json` - (`{seed, variant, spec, contrast, light, dark}`). -- Every component and token reads only `--md-sys-color-*` (`resources/css/tokens/theme.css` maps them - to Tailwind colours). The package's own default is `resources/css/tokens/scheme.css` and `.json`. -- `` (in ``, before `@vite`) writes `data-theme`, `data-theme-choice`, - `data-theme-key`, `data-rail`, `data-rail-key` on `` before the first paint, and puts them - back after a `wire:navigate` swap (`onSwap`). `$store.theme` lives in `resources/js/theme.js`. -- `Support\Scheme::load()` / `light()` read `config('livewire-material.scheme')` (the JSON) merged over - the package default; the mail theme (`resources/views/mail/theme.blade.php`) and the fallback styles - of the error pages (`Support\ErrorPage::fallbackStyles()`) use it. -- Tests: `SchemeCommandTest` (runs Node), `TokensTest`, `MailThemeTest`, `ErrorPagesTest`, - `ShowcaseTest`, browser tests in three engines; CI on Gitea. - -**SealShare:** - -- One scheme, `#4f46e5` Vibrant (`.ai/rules/css.md` records the exact command). -- The production Docker image has no Node, so nothing can be generated at runtime. -- Settings → Appearance is the Light/Dark/System picker, stored per browser. All users are admins; - recipients are guests. -- `App\Livewire\Admin\AdminSettings` holds the settings in `Setting` (key/value) and saves them in - `saveSettings()` with one validation call; the form ends in "Save Settings". It uses `Toasts`. -- Octane: the application boots once per worker, so anything request-specific must be read per call. - -## Decisions - -- **The admin chooses, nobody else** — one profile for the whole installation; no per-user or - per-visitor choice. Light, dark and system stay each visitor's own, as now. -- **Ready-made profiles, no free colour** — generated ahead of time with `material:scheme` and shipped - in the CSS: correct from the first frame, no generator in the browser, mails and error pages can - follow. -- **Eight profiles, Vibrant style like today** — `indigo` Indigo `#4f46e5` (the default, today's), - `blue` Blue `#0b57d0`, `teal` Teal `#00897b`, `green` Green `#2e7d32`, `amber` Amber `#e8710a`, - `rose` Rose `#c2185b`, `violet` Violet `#6750a4`, all `vibrant`; `graphite` Graphite `#5f6368` - in the `neutral` style. -- **The mechanism is the package's, the profiles are the application's** — Livewire Material gets - `profiles` in its config; SealShare lists its eight in its published config. Other applications - define their own. -- **Swatch picker at the top of Admin settings, previewed live, applied on Save** — a "Colour - profile" card with one swatch per profile (primary, secondary and tertiary dots, the name, a check - on the chosen one); a click recolours the page at once; "Save Settings" stores it for everyone. - Leaving without saving shows the saved profile on the next page. -- **Profiles are keyed by ``** — the default profile also stands without the - attribute, so the stylesheet works before the head script runs and with an unknown name: - - ```css - :root, [data-theme='light'] { /* default, light */ } - [data-theme='dark'] { /* default, dark */ } - [data-scheme='teal'], [data-scheme='teal'][data-theme='light'] { /* teal, light */ } - [data-scheme='teal'][data-theme='dark'] { /* teal, dark */ } - ``` - - A profile's two-attribute selectors outrank the default's single ones, and its one-attribute - selector comes later in the file than `:root`, so the order is part of the format. -- **The active profile is resolved on every use, never kept** — the application registers a resolver - once (`Scheme::resolveProfileUsing(fn (): ?string => …)`); the head script, the mail theme and the - error pages call it each time they draw. A name that is not a generated profile, or no resolver, - falls back to the JSON's `default` — the `profile` config (else the first profile) when the scheme - was generated. Nothing request-specific is - stored on a static, so Octane workers stay clean. -- **The JSON keeps its old top-level shape** — `light` and `dark` are still the default profile's - roles, beside `default` and `profiles.{name}.{label, seed, variant, spec, contrast, light, dark}`, - so a reader of the 1.0 format keeps working. -- **`material:scheme` with a seed is unchanged** — one scheme, as in 1.0. Without a seed it generates - every configured profile; without either it fails with a message naming both ways. -- **A `` component in the package** — native radios in a `radiogroup`, bound with - `wire:model` (or `x-model`), each labelled with the profile's name and its three colours from the - JSON; choosing one sets `` immediately (the preview). Errors for the bound property - show under it. -- **The showcase can preview every profile** — a profile menu in its app bar when profiles are - configured, recolouring the showcase without storing anything. -- **Release** — Livewire Material 1.1.0 (a feature), then SealShare's lock, all before 2.0.0. - SealShare's changelog lists it under 2.0.0 "Added". - -## Out of scope - -- A colour picker for any colour, extracting a colour from the logo, or per-profile contrast levels. -- Per-user or per-visitor profiles, or a profile switch outside Admin settings. -- Changing the website's colours (it stays indigo) or adding profile screenshots. -- New success/warning/info sources per profile — they stay the package defaults. - -## Implementation steps - -### Livewire Material 1.1.0 (`../livewire-material`) - -1. **Config.** `config/livewire-material.php`: `'profiles' => []` (name ⇒ `label`, `seed`, - `variant`, optional `contrast`) and `'profile' => null` (the fallback name), documented in the - config comment beside `scheme`. -2. **Generator.** `SchemeCommand`: `seed` becomes optional. Without it, read `profiles`; for each - run `scheme.mjs` as today (validating seed, variant and contrast through the generator's own - errors), then write the stylesheet in the format under Decisions — the default profile (the - `profile` config, else the first) as the plain blocks, then every profile's blocks in config - order — and the JSON with `default`, `profiles` and the default's top-level `light`/`dark`. The - header comment names the command and says the profiles come from config. With neither a seed nor - profiles, fail naming both. -3. **Scheme.** `Support\Scheme`: `resolveProfileUsing(?Closure $resolver): void`, - `profiles(?string $path = null): array` (name ⇒ label and light/dark roles, from the JSON), - `profile(?string $path = null): ?string` (the resolver's answer if it names a profile in the - JSON, else `default` from the JSON, else null), and `load(?string $path = null, ?string $profile = null)` - returning that profile's roles merged over the package default (the active profile when - `$profile` is null; the top-level roles for a 1.0 file). `light()` follows, so the mail theme and - `ErrorPage::fallbackStyles()` draw the active profile without further change: the fallback's plain - `:root`/`[data-theme]` blocks carry that profile's roles, which is all a page without its build - needs. Every method reads the JSON on each call, as `load()` does today. -4. **Head script.** ``: when the JSON has profiles, write - `data-scheme="{active profile}"` on `` with the others, and keep it through `onSwap`. - `$store.theme` gains `scheme` (read from the attribute) and `previewScheme(name)` (sets the - attribute, stores nothing). -5. **Picker.** `resources/views/components/scheme-picker.blade.php` as under Decisions: props - `label`, `hint`, `profiles` (default `Scheme::profiles()`), `name`; labels through `__()`. Each - swatch is a label around a visually hidden native radio, drawn with Tailwind utilities (a - `surface-container` tile, `outline` when checked, a check icon); its three dots are the only inline - styles — `background-color` from that profile's light roles, which `Scheme` has already checked - are `#rrggbb` — because they show another profile's colours than the page's. `x-on:change` calls - `$store.theme.previewScheme($event.target.value)`. With no profiles it renders nothing. -6. **Showcase.** A profile menu in `resources/views/showcase/layout.blade.php`'s app bar when profiles - exist, calling `previewScheme`; the colour section already reads the variables, so it follows. - `src/Showcase/Sections.php` gains the picker as an example (and the search index with it). -7. **Docs.** `resources/boost/skills/livewire-material-development/SKILL.md` (Colour scheme: profiles, - resolver, picker; the new component in Components), `resources/boost/guidelines/core.blade.php` - (one line), `README.md` (Colour scheme and Configuration). -8. **Release.** Verify in `.verify` (Feature + Browser in chrome, firefox, safari), push, watch CI, - tag `1.1.0`. - -### SealShare - -9. **Package.** `composer update nonameweb/livewire-material` to 1.1.0. -10. **Profiles.** `config/livewire-material.php`: the eight profiles under Decisions and - `'profile' => 'indigo'`. Run `php artisan material:scheme` to regenerate - `resources/css/material-scheme.css` and `.json`; `npm run build`. Update `.ai/rules/css.md`: the - scheme is regenerated with `php artisan material:scheme` from the profiles in config, never - hand-edited. -11. **Resolver.** `AppServiceProvider::boot()`: `Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile'))`. -12. **Admin settings.** `AdminSettings`: `public string $colorProfile`, mounted from - `Scheme::profile()`; validated with `Rule::in(array_keys(Scheme::profiles()))` in - `saveSettings()` — the profiles actually generated into the stylesheet, not merely listed in - config; saved with `Setting::set('color_profile', $this->colorProfile)`. - `admin-settings.blade.php`: a "Colour profile" card first in the form with - `` and a hint that - the choice applies to every page, mail and error page after saving. -13. **Docs.** README features: "Colour Profiles — eight colour profiles, chosen by the admin". - CHANGELOG `2.0.0` "Added". `composer screenshots` again (the admin settings shot shows the new - card); the website's feature list gains the same line. - -## Testing - -**Package** - -- `SchemeCommandTest`: with profiles configured and no seed, the JSON has `default` and every profile - with light and dark roles, top-level `light`/`dark` equal the default's; the stylesheet has the - default's plain blocks first and each profile's `[data-scheme='…']` blocks after, with its hexes; - a seed still writes the 1.0 format; neither fails with the message. -- `SchemeTest` (new, Feature): `profile()` follows a resolver naming a profile, falls back on an - unknown name, on no resolver and on a 1.0 file; `load()` returns the chosen profile's roles. -- `MailThemeTest`: the mail's primary is the resolved profile's. `ErrorPagesTest`: the fallback - styles carry the resolved profile's roles. -- Components: `` renders `data-scheme` for the resolved profile and none without - profiles; `` renders a radio per profile, checked from `wire:model`, with the - labels. -- Browser (three engines): `--md-sys-color-primary` on `` is the profile's in light and in - dark, and the default's without the attribute; choosing a swatch changes it at once; the attribute - survives `wire:navigate`; the showcase menu previews a profile. - -**SealShare** - -- `AdminSettingsTest`: a valid profile is saved and a toast dispatched; an unknown one fails - validation and saves nothing. -- `ColourProfileTest` (new, Feature): a guest's upload page renders `data-scheme` from the saved - setting and the default without one; the reset-password mail uses the profile's primary. -- `tests/Browser/SealShareTest.php`: in Admin settings a swatch recolours the page before saving; - after Save and a reload, and on a guest's download page, the profile stays. -- `DesignLanguageTest` and `WebsiteTest` keep passing. - -## Risks and open questions - -- **Stylesheet size.** Eight profiles × two themes × ~60 roles is about 60 KB before compression - (a few KB gzipped); acceptable, and the CSS stays cacheable. -- **A query per page for the setting.** `Setting::get('color_profile')` runs when the head script - renders, like the site title already does; cache it later if it ever shows. -- **Swatch colours are inline styles.** The design guard does not look at `style` attributes, so - nothing stops them spreading; they stay inside `` and come only from `Scheme`'s - checked hexes, which the component test asserts. -- **A profile removed from config** while saved leaves the setting pointing nowhere; the resolver's - fallback to the default covers it, and Admin settings shows the default as chosen. -- **Config and stylesheet out of step.** A profile added to config but not generated is not offered: - the picker, the resolver and the validation all read the generated JSON. `.ai/rules/css.md` says to - regenerate after changing profiles. -- **Open tabs** keep the profile they loaded (or previewed) until their next full load; - `wire:navigate` carries the page's current attribute forward. -- **Error pages without a build** use the fallback styles, which draw the active profile directly; - covered by `ErrorPagesTest`. diff --git a/docs/plans/livewire-material.md b/docs/plans/livewire-material.md deleted file mode 100644 index 37f47f5..0000000 --- a/docs/plans/livewire-material.md +++ /dev/null @@ -1,223 +0,0 @@ -# SealShare on Livewire Material (2.0.0) - -> The package itself — its decisions, the wave plan (Phases 1–10) and its tests — moved to -> the package repo on 2026-09-13: [noNameWEB/livewire-material · docs/plans/livewire-material.md](https://gitea.nonameweb.ch/noNameWEB/livewire-material/src/branch/main/docs/plans/livewire-material.md). -> This file keeps what SealShare does once the package reaches `1.0.0`. - -## Goal - -SealShare's UI is maryUI 2.9 on daisyUI 5 — a generic web-page look. After this change it runs -on **`nonameweb/livewire-material` `^1.0`**: a clean, calm indigo Material 3 Expressive app with -a top app bar, light / dark / system theme, and two Expressive moments — the upload drop zone -and "link ready" — shipped as SealShare 2.0.0. - -## Context - -**Stacks.** SealShare: Laravel 13.31, Livewire 4.4, maryUI 2.9.10 (no prefix), daisyUI 5.7, -Tailwind 4.3, Pest 5.1, Octane on FrankenPHP, PHP 8.5; public on GitHub under MIT, image -published to `ghcr.io/surtic86/sealshare`. ReStride: same Laravel / Livewire / Tailwind / Pest, -private on `gitea.nonameweb.ch`, CI through Gitea act_runner. - -**SealShare's UI surface** (inventory, 2026-09-13): - -- maryUI tags: `button` 30, `input` 18, `password` 16, `icon` 14, `card` 9 (6 `actions` - slots), `menu`/`menu-item` 1/4 (settings nav), `theme-toggle` 3, `toggle` 2, `select` 2, - `modal` 2, `table` 1 (`:headers :rows :sort-by with-pagination`, `@scope`), `textarea` 1, - `toast` 1 (never triggered). -- Raw daisyUI: `btn` (+ `-primary/-ghost/-sm/-xs/-error/-outline/-disabled`), `alert` ×6, - `card`/`card-body` (4 admin stat tiles), `join` (2 copy fields), `progress` ×2, - `loading` ×2, `badge-success/-error`, `divider`, `link link-primary` ×5, `label`, - `file-input`, `checkbox`; tokens `bg-base-*`, `border-base-300`, `text-error/success`, - `border-primary(/50)`, `bg-primary/5`; raw `text-green-600`, `bg-white` (QR code). - Secondary text is `opacity-50/60/70`. -- 20 Heroicons (outline), through `blade-heroicons` pulled in transitively by maryUI. -- No `Mary\` PHP coupling. Admin settings flashes `session('message')` into an alert - (`AdminSettings.php:116,128,135`). 3 `wire:confirm`. -- Layouts: `layouts/app` → `app/sidebar` (centered `max-w-5xl` + footer nav), used by the - Livewire pages and all settings SFCs (`config/livewire.php:47`); `layouts/auth` → - `auth/simple`. The theme script sits *outside* `` and hard-codes dark, while maryUI's - toggle defaults from the OS. `partials/head` loads Instrument Sans from fonts.bunny.net. -- Dead: `/dashboard` (starter placeholder, and Fortify's `home`), `welcome`, - `pages/auth/register` (still referenced by `Fortify::registerView`, - `FortifyServiceProvider.php:52`), `layouts/app/header`, `layouts/auth/{card,split}`, - `components/app-logo`, `components/desktop-user-menu`, `components/placeholder-pattern`; - the `alpinejs` npm dependency; the Flux credentials step in `tests.yml` and `docker.yml`. -- Settings `profile` and `password` show "Saved." through `components/action-message`, - listening for `profile-updated` / `password-updated`; `partials/settings-heading` uses a - daisyUI `divider`. The 3 `wire:confirm` are admin settings (remove logo, clear system - password) and admin dashboard (delete share). `AdminDashboard::headers()` exists only for - maryUI's table. -- Tests assert text only, never markup; no browser tests. -- Docker: the image's caches run in `docker/entrypoint.sh` (`config:cache`, `route:cache`, - `view:cache`); `docker/dev-entrypoint.sh` runs `npm run build` against the host's mounted - `vendor/` without a `composer install`. The Flux credentials step is in `tests.yml`, - `docker.yml` **and** `lint.yml`. -- Screens: setup, system password, upload, share created, share download, admin dashboard, - admin settings, settings (profile, password, appearance, two-factor), Fortify pages (login, - forgot, reset, 2FA challenge, confirm, verify email). Stock Laravel error pages and mails. - -**Constraints found.** - -- SealShare's `Dockerfile` builds assets (stage 1) **before** `composer install` (stage 2); - CSS imported from `vendor/` needs the order swapped. -- Laravel **replaces** the `errors` view namespace at render time with - `config('view.paths')` + `/errors` and the framework's own - (`Illuminate/Foundation/Exceptions/RegisterErrorViewPaths.php`), so error views a package - adds with `addNamespace('errors', …)` are wiped; only a path in `view.paths` survives. -- The package lives at `https://gitea.nonameweb.ch/noNameWEB/livewire-material.git` (public, - anonymous reads verified 2026-09-13). - -## Decisions - -The package's decisions are in its own plan. SealShare's: - -- **Converts after `1.0.0`, in one pass, by hand** (~150 tags; no codemod), on branch - `material`, released as **2.0.0**. -- **Moving SealShare to Gitea is a separate plan** — this plan works wherever it is hosted. -- **Seed `#4f46e5` (the favicon's indigo), Vibrant** — chosen after comparing it with Tonal Spot - on the upload page in both themes (2026-09-13): Tonal Spot read grey-lavender on this seed. -- **Theme default `system`**, storage key `sealshare-theme`, legacy `mary-theme` adopted once. - Appearance is a Light / Dark / System connected button group. -- **One top app bar everywhere** — logo and site title; a theme toggle for guests, an avatar - account menu (Upload, Admin dashboard, Admin settings, Settings, theme, Log out) for users; - centered content; Admin and Settings sub-pages as secondary tabs (menu picker on a phone); - auth pages a centered card under the same bar. No rail, no bottom bar. -- **Expressive components plus two hero moments** — an Expressive shape behind the upload icon - that morphs while files are dragged over, the wavy progress indicator for uploads, a - shape-backed check when the link is ready; admin stats count up once. Instant under - `prefers-reduced-motion`. -- **The public download page uses no anchored components** (no menus, no tooltips) — it must - work for recipients on iOS below 18.4. -- **Starter-kit cleanup during the conversion** — delete the placeholder `/dashboard`, point - Fortify `home` at the admin dashboard, delete the unused views and the `registerView` - binding, drop `alpinejs` from npm and the Flux step from CI. -- **Confirmations become M3 basic dialogs** (the 3 `wire:confirm`) — the browser's native - confirm cannot be themed and reads as a different app. *(Not asked in the interview; object - in review if you prefer the native confirm.)* -- **Save feedback becomes a snackbar** through the package's `Toasts` concern — admin - settings' flashed `session('message')` alert and settings' "Saved." `action-message` alike. - *(Follows from the snackbar; not asked separately.)* -- **The font is self-hosted** — the fonts.bunny.net request goes, which also suits a - privacy-minded self-hosted app. -- **Tests: updated feature tests, the package's guard as `DesignLanguageTest`, Livewire tests - for changed behaviour, and four browser tests** with `pestphp/pest-plugin-browser` (new dev - dependency, approved). - -## Out of scope - -- ReStride adopting the package — its own plan, after `1.0.0`. -- Moving SealShare's repository, CI and image registry to Gitea — its own plan. -- Everything the package plan puts out of scope. -- Changes to SealShare's features, routes or information architecture beyond the cleanup above. - -## Implementation steps - -Step numbers continue the original plan's, so references elsewhere stay valid. - -### Phase 11 — SealShare 2.0.0 (after `1.0.0`) - -34. **Branch** `material` from `main`; open the PR so CI runs. -35. **Dependencies.** Add the `vcs` repository and `composer require nonameweb/livewire-material:^1.0`; - `composer remove robsontenorio/mary` (drops `blade-heroicons` with it); - `npm remove daisyui alpinejs`; `composer require --dev pestphp/pest-plugin-browser`. - maryUI goes **first** because its class components would shadow the package's same-named - anonymous ones; the branch is therefore red from here until step 44, which is accepted — - it merges once, green (Decisions: one pass). -36. **CI and Docker.** Remove the Flux credentials step from `.github/workflows/tests.yml`, - `docker.yml` and `lint.yml`; install Playwright browsers in `tests.yml`. `Dockerfile`: run - the Composer stage first and `COPY --from=vendor /app/vendor ./vendor` into the Node stage - before `npm run build`. `docker/dev-entrypoint.sh`: run `composer install` when `vendor/` is - missing, before `npm run build`. No `icons:cache` anywhere: the package draws its symbols - without blade-icons. -37. **Styles and scheme.** `resources/css/app.css`: `@import 'tailwindcss'`, the package entry - from `vendor/`, `./material-scheme.css`, `@source '../views'` and the package's views; drop - the daisyUI plugin, maryUI and pagination `@source`s and the swap safelist. - `resources/js/app.js` imports the package JS. Run - `php artisan material:scheme "#4f46e5" --variant=vibrant` (chosen over Tonal Spot after - comparing both on the upload page in both themes). -38. **Head and theme.** `partials/head`: remove fonts.bunny.net; include `` - before `@vite` (it currently sits outside ``). Publish the config with - `theme.default = system`, `storage_key = sealshare-theme`, `legacy_keys = ['mary-theme']`. -39. **Layouts.** Rebuild `layouts/app.blade.php` (absorbing `app/sidebar`): `` with - `app-logo-icon` / branding logo and site title, `` for guests or - `` for users (Upload, Admin dashboard, Admin settings, Settings, theme, Log - out through `App\Livewire\Actions\Logout`), centered content, ``. - `layouts/auth.blade.php` (absorbing `auth/simple`): the same bar and a centered card. -40. **Cleanup.** Delete the `/dashboard` route, `dashboard.blade.php`, `placeholder-pattern`, - `welcome`, `pages/auth/register` and its `Fortify::registerView` line, - `layouts/app/{header,sidebar}`, `layouts/auth/{card,split,simple}`, `app-logo`, - `desktop-user-menu`. Fortify `home` → `/admin/dashboard`. Update `AuthenticationTest:22` - and `EmailVerificationTest:32,63` to the new redirect; `DashboardTest` is rewritten to - assert that a signed-in admin lands on the admin dashboard and `/dashboard` is gone - (replacing its placeholder tests, approved in the interview). `RegistrationTest` stays. -41. **Public pages.** `livewire/file-uploader`: drop zone with `` behind the upload - icon morphing while `dragging`, existing Alpine folder walking and `livewire-upload-*` - wiring kept, wavy ``, `` for processing, selected files as - ``, Share Options `` (``, ``, number ``s), - `` for storage full, filled primary "Create Share Link". - `share-created`: shape-backed check, `` for the link, four ``, - info ``, "Upload More". `share-download`: password `` with - ``, files as `` with download icon buttons, "Download All" — no menus - or tooltips. `system-password-prompt`, `setup-wizard` onto fields and buttons. -42. **Auth pages** (`login`, `forgot-password`, `reset-password`, `two-factor-challenge`, - `confirm-password`, `verify-email`): fields, `` for remember me, `link` utility - for text links, `auth-session-status` onto `` (drops `text-green-600`). -43. **Settings.** `pages/settings/layout` → ``; `partials/settings-heading` - drops the daisyUI divider for ``; `profile` and `password` show "Saved." as a - snackbar through `Toasts` (the `profile-updated` / `password-updated` dispatches stay for - any listener) and `components/action-message` is deleted; - `appearance` → Light / Dark / System `` on `$store.theme` (the only toggle on the - page); `two-factor` → `` status, `` setup with the QR on a - white token surface, `` key, recovery codes; `delete-user-form` → - `` with a `danger` action. -44. **Admin.** `admin-dashboard`: four `` (counting up once), disk usage - ``, hand-written `` with `` and pagination (the - `@scope` cells become plain Blade and `AdminDashboard::headers()` goes), view and delete - icon buttons, delete confirmation in a basic `` instead of `wire:confirm`. - `admin-settings`: cards, ``, `` for the logo with preview, - ``, ``, ``; "Remove the logo?" and "Remove the system - password?" become basic dialogs instead of `wire:confirm`; `AdminSettings` uses `Toasts` - instead of `session()->flash('message')` (3 places) and the alert block goes. Keep every - existing `data-test` attribute on the element that now plays its role. -45. **Error pages and mail.** Confirm the package's error views render in SealShare's theme; - set `config/mail.php` `markdown.theme` to `livewire-material::mail.theme`; check the - password-reset and verify-email mails. -46. **Guards.** `tests/Feature/DesignLanguageTest.php` using `DesignGuard` over - `resources/views` and `app/` — no maryUI, no daisyUI, only declared colours, only existing - icons. A grep for `base-content|bg-base|btn|mary` returns nothing. -47. **Rules and AI.** `php artisan boost:update --discover` to install the package guideline and - skill; `record-rule` for SealShare: the scheme is regenerated with `material:scheme`, - never hand-edited; the download page stays free of anchored components; the theme key. -48. **Docs.** README tech stack and the "Dark Mode" feature line; CHANGELOG `2.0.0`. -49. **Ship.** Full suite green on the PR; merge; tag `v2.0.0` (publishes the image through - `docker.yml`). - -## Testing - -- Feature tests updated where redirects or text change: `AuthenticationTest`, - `EmailVerificationTest`, `DashboardTest` (rewritten), `AdminSettingsTest` (asserts the - toast is dispatched instead of the flash), `TwoFactorAuthenticationTest`, - `AdminDashboardTest`, `ShareDownloadTest`. -- `DesignLanguageTest` through the package guard. -- Livewire tests: admin settings save/remove-logo/clear-password dispatch toasts; profile and - password updates dispatch the "Saved." toast (`ProfileUpdateTest`, `PasswordUpdateTest`); - delete share, remove logo and clear system password go through their dialogs' confirm - actions. -- Browser tests (`tests/Browser`): upload by drop and by Browse → progress → share created → - copy link; the password-protected download page at 393px; admin table sort and delete - dialog; a first visit follows the OS theme and Appearance switches it. -- Narrow runs per step; the full suite on the PR's CI. - -## Risks and open questions - -- **Scope and time.** The whole catalogue (~45 components plus extras) comes before SealShare - changes at all, so its starter-kit bugs (the placeholder `/dashboard`) stay until then. - Mitigation: waves tagged `0.x`, each reviewed in the showcase; SealShare keeps working - meanwhile. -- **Gitea becomes a build dependency.** Every SealShare CI run and Docker build fetches the - package from `gitea.nonameweb.ch`; an outage or a sign-in setting reverting breaks builds. - Mitigation: dist archives cached by Composer in CI; revisit Packagist if it bites. -- **iOS / Safari below 18.4.** Anchored menus and tooltips do not position there. Mitigation: - SealShare's download page uses none; native `