Files
SealShare/docs/plans/screenshots-and-website.md
T
Andreas Reinhold / reiniandClaude Opus 5 606cc766f2
docker / test (8.5) (push) Successful in 2m5s
docker / build-and-push (push) Failing after 9m38s
docker / release (push) Has been skipped
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m4s
Add the SealShare website for sealshare.nonameweb.ch
website/ is hand-written HTML and CSS, uploaded as it is, like the
MailifySMS site: SealShare as software a company installs for its own
upload platform, how it works, features, a desktop/phone and light/dark
gallery of the generated screenshots, a dated and sourced comparison with
hosted transfer services and self-hosted tools, the Docker quick start,
FAQ and a privacy page for Plausible. Colours come from the app's scheme,
Google Sans Flex is served locally, nothing else loads from other hosts.

The README shows three screenshots and no longer calls the encryption
end-to-end. WebsiteTest guards missing files, other hosts, the screenshot
set and the encryption wording.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 12:38:49 +02:00

258 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Screenshots and the SealShare website
## Goal
Two things that feed each other. First, one command — `composer screenshots` — produces every
screenshot of SealShare from fixed demo data, desktop and phone, light and dark, ready for the web.
Second, a static website at **sealshare.nonameweb.ch**, made the way mailifysms.nonameweb.ch is:
hand-written HTML and CSS in `website/`, uploaded by hand. The site presents SealShare as what it
is — software a company installs to run **its own upload platform**, so it exchanges files with
customers securely without relying on an outside service — shows the screenshots, compares
SealShare with hosted transfer services and with other self-hosted tools, and tells how to install
it. It goes live with 2.0.0. The README gets a few of the same screenshots, and its encryption
wording is corrected.
## Context
**MailifySMS, the model** (`../MailifySMS`):
- `website/` holds `index.html`, `privacy_policy.html`, `terms_and_conditions.html`,
`css/theme.css` (a palette sampled from the app's screenshots), `css/device-frame.css` (a phone
bezel shared with the store canvases), self-hosted Poppins (`fonts/`, OFL) and `img/`
(`icon.png`, `hero.jpg`, `screenshots/{light,dark}/NN-name.png` at 540px).
- `index.html`: sticky nav with a phone toggle, hero, "How it works", "Key features", a screenshot
gallery with a Light/Dark switch (`data-light`/`data-dark` on each `<img>`), FAQ accordion,
contact card (`surtic86@gmail.com`), footer (quick links, legal). Plausible:
`<script defer data-domain="mailifysms.nonameweb.ch" src="https://plausible.io/js/script.js">`.
The page's JS is one inline `<script>` at the end.
- `CLAUDE.md` records that `website/` "is a faithful copy of what is deployed, images included, so
it can be uploaded wholesale". There is no deploy automation.
- `tools/screenshots.sh` (macOS only) drives an emulator and headless Chrome; documented in
`CLAUDE.md` § Screenshots, with the reasons behind each quirk.
**Hosting.** `*.nonameweb.ch` is a wildcard DNS record to `80.74.140.2` (METANET shared hosting,
nginx), the same as mailifysms. `sealshare.nonameweb.ch` resolves already; HTTP serves the host's
placeholder, HTTPS has no certificate. Creating the site and its Let's Encrypt certificate is done
in the hosting panel.
**SealShare.**
- Laravel 13.31, Livewire 4.4, Livewire Material 1.0.1, Pest 5.1 with `pestphp/pest-plugin-browser`
(Playwright 1.63). The browser tests run the app in-process, so factories, `Storage::fake()` and
`travelTo()` shape what the browser sees. `tests/Pest.php` applies `Tests\TestCase` and
`RefreshDatabase` to `Feature` and `Browser`, and creates an admin in `beforeEach` (the setup
gate).
- Device presets: `visit()->on()->macbook14()` is 1512×982 at 2× (a 3024×1964 capture);
`on()->iPhone15Pro()` is 393×852 at 3× (1179×2556). `inLightMode()` / `inDarkMode()`,
`screenshot(fullPage, filename)`. Screenshots are written to `tests/Browser/Screenshots/<name>.png`;
the directory is created, subdirectories in the name are not — names must be flat. Pest empties
that directory when a browser run starts, and has no reduced-motion emulation.
- Pest's in-process server does not store a multipart upload, so a browser test cannot select files
through the file input.
- PHP here has GD with WebP and PNG support; the production image does not need it (this is a
development tool).
- Colours: `resources/css/material-scheme.json` (seed `#4f46e5`, Vibrant) holds the light and dark
roles as hexes. Font: Google Sans Flex, `vendor/nonameweb/livewire-material/resources/fonts/google-sans-flex/GoogleSansFlex-Latin.woff2`
with its `OFL.txt`. Logo: `resources/views/components/app-logo-icon.blade.php` (SVG).
- **Encryption, as the code does it:** `ShareService::createShare()` encrypts each uploaded file on
the server with AES-256-GCM (chunked) through `FileEncryptionService`. Without a share password
the key is stored in `shares.encryption_key`; with one, the key is derived with PBKDF2-SHA256 and
never stored. The server sees the plaintext while uploading and downloading. The README calls
this "End-to-End Encryption", which it is not.
- The upload page is public, optionally behind the system password (`SystemPasswordGate`); a
customer given that password can upload and send the link back.
- Install today (README): `ghcr.io/surtic86/sealshare`, clone from GitHub. Gitea
(`gitea.nonameweb.ch/noNameWEB/SealShare`) is now the public repository; the image registry for
2.0.0 is settled separately.
- `.gitignore` does not ignore `tests/Browser/Screenshots`; `.dockerignore` excludes `tests` and
`*.md` but would copy a `website/` directory into the image.
**Peers.** Pingvin Share has been archived since June 2025 (its README points to forks such as
Pingvin Share X). PsiTransfer, Gokapi and Erugo are single-purpose self-hosted share tools; Gokapi
advertises end-to-end encryption.
## Decisions
- **Positioning: software you host, not a service** — the site says plainly that SealShare is not
hosted by anyone but the company that installs it: its own upload platform for exchanging files
with customers, data on its own server, no dependence on an external service.
- **Pages: `index.html` and `privacy.html`** — one landing page, and a short privacy page because
the site uses Plausible. No terms page: the software is MIT-licensed and no service is offered.
- **A comparison with hosted transfer services and with self-hosted share tools** — two tables on
the landing page. Cloud suites (Nextcloud-style) are left out.
- **Hand-written HTML and CSS, like MailifySMS** — no build step; `website/` is uploaded as it is.
- **Colours copied from `material-scheme.json`, not sampled** — `website/css/theme.css` lists the
roles it uses with the scheme's hexes, light by default and dark under
`@media (prefers-color-scheme: dark)`; it names the seed and variant it was copied from, so a
regenerated scheme is copied again. The site follows the visitor's system theme and has no
toggle of its own.
- **Google Sans Flex, self-hosted** — the app's font, copied with its `OFL.txt` into
`website/fonts/`; nothing is loaded from Google.
- **Uploaded by hand, like MailifySMS** — `website/` is a faithful copy of what is live. You create
the subdomain and certificate once in the hosting panel and upload the folder when it changes.
No hosting credentials anywhere in the repository or CI.
- **Plausible** — `data-domain="sealshare.nonameweb.ch"`, the same script as MailifySMS; the site
must be added in the Plausible account.
- **English only.**
- **Screenshots: desktop and phone, each in light and dark (20 images)** —
desktop (MacBook 14, 2×): `01-upload` (files selected, options filled), `02-share-created`,
`03-qr-code` (the dialog), `04-download` (the recipient's file list), `05-admin-dashboard`,
`06-admin-settings`; phone (iPhone 15 Pro, 3×): `01-upload`, `02-password` (the recipient's
password prompt), `03-download`, `04-qr-code`.
- **Screenshots run as Pest browser tests in `tests/Screenshots/`, started by `composer screenshots`**
— the directory is not one of phpunit.xml's test suites, so `php artisan test`, the Browser
suite and CI never run it. It reuses Playwright and the in-process server.
- **Fixed demo data** — factories and `ShareService` with fixed names, sizes and tokens, time
frozen with `travelTo()`, the site title and branding at their defaults, one admin
("Alex Morgan"). Every run produces the same images unless the UI changed.
- **Images published as WebP by the test run itself** — after each capture a small helper resizes
it with GD into `website/img/screenshots/{desktop,phone}/{light,dark}/NN-name-<width>.webp` at two widths
(desktop 1600 and 800 px, phone 1080 and 540 px) for `srcset`. Raw PNGs stay in
`tests/Browser/Screenshots/`, which is gitignored; only the WebP files are committed.
- **Device frames in CSS** — `website/css/device-frame.css` draws a laptop and a phone around the
screenshots; the hero shows the desktop upload and the phone download screenshots framed, in
the visitor's theme. No generated hero image.
- **README shows three screenshots** — desktop upload, desktop share created, phone download (light),
referenced from `website/img/screenshots/…`, so each image exists once in the repository.
- **Encryption is described accurately, on the site and in the README** — "encrypted at rest with
AES-256-GCM; with a share password the key is never stored". The comparison marks end-to-end
encryption "no" for SealShare. The README's "End-to-End Encryption" line is corrected.
- **Built on `material`, live with 2.0.0** — screenshots show the 2.0.0 interface; the site links
the Gitea repository, and its install commands are the README's at release, whatever registry
2.0.0 ships with.
- **Comparison facts are researched, dated and sourced** — from each product's own site,
documentation or repository; the tables say "as of <month year>" and link every source. Hosted
services: WeTransfer, SwissTransfer, Dropbox Transfer, Google Drive links. Self-hosted tools:
open source, single-purpose, installable with Docker, with a release in the 12 months before the
research — expected Pingvin Share X, PsiTransfer, Gokapi and Erugo; any that fails the rule is
dropped and named in the commit message. Criteria (rows): where files are stored, who operates
it, recipient needs an account, password protection, expiry, download limit, encryption at rest,
end-to-end encryption, folder upload, custom branding, maximum file size, licence and cost. A
value that cannot be sourced is "—", never guessed.
## Out of scope
- A terms page, a documentation section, a German version.
- Deploy automation (Gitea Actions, SFTP scripts), and creating the subdomain, certificate or
Plausible site — those are yours in the hosting panel and the Plausible account.
- Comparing with Nextcloud, ownCloud or other cloud suites.
- Store-style canvases with captions, a generated hero image, video or animated screenshots.
- Running the screenshots in CI, or checking them against earlier runs (visual regression).
- The image registry move and the install commands' final form (settled with 2.0.0).
- Implementing end-to-end encryption.
## Implementation steps
1. **Housekeeping.** `.gitignore`: `/tests/Browser/Screenshots`. `.dockerignore`: `website`.
2. **Screenshot helper.** `tests/Screenshots/Publisher.php` (`Tests\Screenshots\Publisher`):
`publish(string $capture, string $device, string $theme, string $name, array $widths): void`
reads `tests/Browser/Screenshots/<capture>.png` with GD, and for each width writes
`website/img/screenshots/<device>/<theme>/<name>-<width>.webp` (quality 82, aspect kept,
directories created). It throws when the capture is missing, so a failed shot fails the run.
3. **Demo data.** `tests/Screenshots/DemoData.php`: `admin()`, `shares()` (eight shares with fixed
tokens, file names such as `Q3-report.pdf`, `Contract 2026.pdf`, `Product photos/…`, sizes,
download counts and expiries, one password-protected, one expired), created through factories
and `ShareService` with `Storage::fake('shares')` so the files exist encrypted.
4. **Screenshot tests.** `tests/Screenshots/ScreenshotsTest.php`, with `tests/Pest.php` extended to
`->in('Feature', 'Browser', 'Screenshots')`:
- `beforeEach`: `config(['session.driver' => 'file'])`, `travelTo('2026-10-01 09:30')`, demo
data; a `ready()` wait as in `SealShareTest`.
- One test per device and theme (four tests), each visiting the pages in turn, waiting for
`networkidle` and fonts (`document.fonts.ready`), hiding the text caret, capturing
viewport-sized (not full-page) shots, and calling `Publisher::publish()` right after each.
- The upload shot with files selected: create Livewire temporary uploads on the fake
`livewire-tmp` disk and set the uploader's property through `$wire.$set` with
`livewire-file:` references, then fill the options. If Livewire refuses that, the shot shows
the drop zone with the options filled instead, and the plan's risk note is updated.
- QR dialog: `click('[data-test="show-qr-code"]')`; password prompt: the protected share on the
phone; admin pages as the admin.
5. **Command.** `composer.json` script `"screenshots"`: `Composer\\Config::disableProcessTimeout`,
`npm run build`, `@php vendor/bin/pest tests/Screenshots` — the build first, so the shots show
the current assets. Playwright's Chromium must be installed (`npx playwright install chromium`),
as for the browser tests.
6. **Website scaffold.** `website/`:
- `css/theme.css` — tokens copied from `material-scheme.json` (with seed and variant noted),
Google Sans Flex `@font-face`, layout, nav, hero, sections, cards, tables (scrolling
sideways on a phone), FAQ (`<details>`), footer; light and dark through
`prefers-color-scheme`.
- `css/device-frame.css` — laptop and phone frames.
- `fonts/GoogleSansFlex-Latin.woff2`, `fonts/OFL.txt`; `img/logo.svg` (from `app-logo-icon`),
`img/icon.png` (favicon, from `public/`).
7. **Landing page.** `website/index.html` (Plausible in `<head>`, one inline script at the end):
- nav: Why, Features, Screenshots, Compare, Install, FAQ, Gitea;
- hero: "Your own secure upload platform" — self-hosted file exchange with customers, no
outside service; buttons "Install" (to #install) and "Source on Gitea"; framed desktop and
phone screenshots as `<picture>` elements whose `<source media="(prefers-color-scheme: dark)">`
picks the dark captures;
- "Why run your own": your server, your domain and branding, customers upload and download
without accounts, encrypted at rest, no per-seat pricing;
- "How it works": upload → link or QR code → the customer downloads, with expiry, download
limit and password;
- features (from the README, accurate encryption wording);
- screenshots: Desktop/Phone and Light/Dark switches over one gallery (Light/Dark starting on
the visitor's system theme), `srcset` for both widths, `loading="lazy"`, descriptive `alt`;
- compare: the two dated tables with sources (step 8);
- install: the README's Docker quick start and a link to the full instructions on Gitea;
- FAQ: "Is it end-to-end encrypted?" (no — at rest, and what a password adds), "Can customers
send files to us?" (yes, through the upload page, optionally behind the system password),
"How big can files be?" (the README's large-file limits), "What does it cost?" (MIT, your
hosting), "Who runs it?" (you);
- contact (`surtic86@gmail.com`, as MailifySMS) and footer (Gitea, licence, privacy, noNameWEB).
8. **Comparison research.** For each product, record every criterion with its source URL and the
date checked; apply the self-hosted selection rule; fill the tables. Keep the notes in the
commit message, not in the repository.
9. **Privacy page.** `website/privacy.html`: who runs the site (contact), the host (METANET, server
logs), Plausible (cookieless, no personal data, EU-hosted, link to its data policy), no other
third parties, fonts served locally, contact for questions; dated.
10. **README and changelog.** Correct the encryption lines (the intro sentence stays accurate;
"End-to-End Encryption" becomes "Encryption at Rest", described as in Decisions), add a
Screenshots section with the three images, add the website link. CHANGELOG `2.0.0` "Fixed":
the README no longer calls the encryption end-to-end. The website and the screenshot tooling
get no changelog entry — they do not change the application.
11. **Project notes.** `record-rule` for `website/**`: `website/` is a faithful copy of what is live,
uploaded by hand; its colours are copied from `material-scheme.json` and must be copied again
when the scheme is regenerated; the comparison is dated and every value sourced. And for
`tests/Screenshots/**`: run with `composer screenshots` whenever the interface changes, before
a release; the demo data is fixed so runs are reproducible.
## Testing
- `tests/Unit/ScreenshotPublisherTest.php`: a generated PNG is written as WebP at each requested
width with the aspect ratio kept, into the device/theme directory; a missing capture throws.
- `tests/Feature/WebsiteTest.php` guards `website/` without a browser:
- every local `src`, `href`, `srcset` entry and CSS `url()` resolves to a file in `website/`;
- every screenshot the gallery or README references exists for both widths and both themes;
- no request goes to a host other than `plausible.io` (no Google Fonts, no CDN);
- `index.html` and `privacy.html` have a `<title>`, `lang="en"` and a meta description;
- the README has no "End-to-End Encryption" line, the site's features section does not say
"end-to-end", and SealShare's end-to-end cell in the comparison (marked
`data-compare="sealshare-e2e"`) reads "No" — the FAQ may still ask the question.
- The screenshot run itself is the test of step 4: it fails when a page, selector or capture
breaks. It is run by hand before a release, not in CI.
- The site is looked at in Chrome, Firefox and Safari, light and dark, at phone width, before
uploading.
## Risks and open questions
- **Selecting files in the upload shot** works: the files are stored with Livewire's own
`FileUploadConfiguration::storeTemporaryFile()` and handed to `_finishUpload` by their signed
names; Livewire's temporary-upload cleanup is turned off for the run, because under the frozen
clock it deletes them.
- **Comparison accuracy and fairness.** Other products change; the tables are dated and sourced,
and re-checked when the site is updated. Swiss unfair-competition law expects comparisons to be
accurate and not misleading — values that cannot be sourced stay "—".
- **Install commands depend on the registry move.** Until it is settled, the install section copies
the current README; it is updated before the site goes live with 2.0.0.
- **Screenshot determinism.** Relative dates ("in 3 days") depend on `travelTo()`; animations
(the share-created shape, counting stats, dialog entry) are waited out — Pest has no
reduced-motion emulation — by waiting on `document.getAnimations().length === 0` before each
capture.
- **The host's name in the privacy page** (METANET) is inferred from the server's reverse DNS
(`urbanus.ch-meta.net`); confirm it before the page goes live.
- **The contact address** is the one MailifySMS publishes (`surtic86@gmail.com`); change it in
step 7 if SealShare should have its own.
- **Image weight.** Twenty screenshots at two widths as WebP should stay under ~4 MB in total; if
not, lower the quality or drop the larger phone width.
- **Colours drift** when the scheme is regenerated; the rule in step 11 and the note in
`theme.css` are the guard.