47 Commits
Author SHA1 Message Date
Andreas Reinhold / reiniandClaude Opus 5 eb25631799 Release 2.1.0
docker / test (8.5) (push) Successful in 3m18s
docker / build-and-push (push) Waiting to run
docker / release (push) Blocked by required conditions
linter / quality (push) Successful in 1m4s
tests / ci (8.5) (push) In progress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v2.1.0
2026-09-16 21:52:04 +02:00
Andreas Reinhold / reiniandClaude Opus 5 c552ee9f9d Update PHP and npm dependencies
Everything stays within its existing constraint: Laravel 13.32, Livewire
4.4.5, Livewire Material 2.1.0, Pest 5.2, Boost 2.9, Pint 1.32.1, Vite
8.3 and autoprefixer 10.6. Boost's copy of the guidelines and skills
follows Livewire Material 2.1, which is plain CSS without utilities.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 21:52:04 +02:00
Andreas Reinhold / reiniandClaude Opus 5 5853f1f7d6 Give every page the share pages' layout, at one width
The pages were built five ways: two layouts, seven widths from 28 to
64rem and four heading styles. Every page now looks like the share
pages: a centred heading over one 40rem column of outlined cards, with
the floating toolbar below.

- <x-page> (components/page.blade.php) is the root of every page. It
  draws the h1 and its line (`brand` takes the site's logo, title and
  description from Admin settings), an optional `mark` and `navigation`
  slot, then the content. It has no width prop: every page is the same
  <x-pane width="narrow">.
- The sign-in, password reset, confirm, verify email, two-factor
  challenge, setup and system password pages move onto layouts/app with
  the brand heading and their form in a card titled with the task.
  layouts/auth, auth-header and the settings heading partial are gone,
  and so is the per-page width CSS.
- Settings put their section nav under the heading; the admin pages get
  a description line each. FileUploader and ShareDownload no longer pass
  the branding to their views.
- The admin dashboard's table needed about 49rem, so its shares are a
  list: created above the token, which opens the share, then files,
  size, downloads and expiry on two lines that wrap instead of clipping,
  and one delete button. A "Sort by" select replaces the column headers
  (newest, oldest, expiring soonest with never-expiring last, largest,
  most downloads, most files) and resets the page. The stats stay two
  by two. table.css and sort-header.css are no longer imported.
- Branding hints in Admin settings name every page the title shows on.
- Tests: PageTemplateTest renders every page once and checks one page
  template, one h1 and the width, and the brand heading with its
  fallbacks. FrameTest measures the page column instead of the auth
  card and the 64rem main; dashboard tests follow the list and the sort
  select, including expiry order. .ai/rules/views.md records <x-page>,
  the CHANGELOG notes the change and the website screenshots are
  regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 21:42:04 +02:00
Andreas Reinhold / reiniandClaude Opus 5 40e35bab0e Encrypt uploads in the browser and send them in chunks
A 6 GB upload kept a customer waiting long after its progress bar
reached 100%. The server wrote every upload three times: PHP's
temporary file, Livewire's copy of it ("Processing files...") and the
encrypted file ("Create Share Link"), each a full rewrite of a slow
disk. The unencrypted copy also stayed behind in livewire-tmp.

Now the uploader's browser encrypts each file in 16 MB chunks with
WebCrypto and PUTs them one at a time; the server checks each chunk in
memory and writes it once, already encrypted. Creating the share only
wraps its key and saves the options. A 200 MB upload through the
Docker image took 2.8 s, and its download matched byte for byte.

- SEALCHK2: a 19-byte header (chunk size, 7-byte nonce prefix), then
  ciphertext and tag per chunk. Each nonce holds the chunk index and a
  last-chunk flag (the STREAM construction), so cut or reordered files
  fail to decrypt. SEALCHK1 and the single-block format still read.
- Envelope encryption: one random key per share. With a password it is
  wrapped with Argon2id (sodium, libsodium's interactive limits) in
  shares.wrapped_key, which names its parameters. Password shares from
  before keep their PBKDF2-derived key.
- The upload page registers each selection with FileUploader into a
  pending share of its own, lists the files with their progress, retries
  a failed chunk after 1-16 s, then offers Retry; Remove and Cancel
  abort. UploadChunkController only accepts chunks from the session that
  started the share: a repeat is acknowledged, a skip gets 409 with the
  count stored. Chunks go out as Blobs, which Chromium sends about eight
  times faster than ArrayBuffers.
- Uploads need a secure context: over plain HTTP the page says HTTPS is
  needed and takes no files. The Docker image gains AUTO_HTTPS, which
  serves Let's Encrypt on 443 for SERVER_NAME and redirects 80; without
  it the container stays on HTTP 80 behind a proxy. docker/Caddyfile was
  never loaded and is gone; docker/healthcheck.sh covers both modes.
- "Download all" streams the ZIP with maennchen/zipstream-php (STORE,
  ZIP64) instead of decrypting whole files into memory and writing the
  archive unencrypted to /tmp.
- Pending shares count towards the quota, stay out of the admin
  dashboard and 404 everywhere else. shares:cleanup deletes uploads idle
  for 4 hours and Livewire temporary files older than that.
- PHP's upload limits no longer cap the admin's max file size and
  default to 64M; LIVEWIRE_MAX_UPLOAD_TIME is gone and
  UPLOAD_CHUNK_SIZE_MB is new.
- Tests cover the format, key wrapping, registration limits, the chunk
  endpoint's answers, completing a share, the streamed ZIP, cleanup,
  and in Chromium a real chunked upload and the HTTPS warning; the
  selected-files overflow test runs again. README, website, CHANGELOG
  and .ai/rules follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 20:49:17 +02:00
Andreas Reinhold / reiniandClaude Opus 5 504971ad7f Generate share passwords and offer them again beside the new link
Uploaders no longer have to make up a share password. With "Password
protect" on, the upload page has Generate and Copy under the field, and
the page the upload leads to offers the password once more beside the
link: masked, with the same copy button at the end of the field as the
link's. The password also derives the share's encryption key and only
its hash is stored, so a lost one means files nobody can open.

- PasswordGeneratorService draws from Random\Randomizer's secure engine.
  Characters are drawn uniformly and redrawn until every chosen set
  appears; passphrases come from EFF's large word list (CC BY 3.0 US,
  credited in the README), without its four hyphenated words.
- Admin settings gain a "Share Passwords" card: mode (off, on request,
  prefilled as protection is switched on), kind (characters: length
  12–64, the sets, look-alikes left out; passphrase: 4–10 words and a
  separator), and an example with its estimated entropy that follows the
  form before saving. Fields the chosen mode or kind hides are excluded
  from validation and keep their saved value. The default is on
  request, 20 letters and numbers without look-alikes.
- FileUploader flashes the password encrypted with the share's token;
  ShareCreated shows it only when the token matches, so a reload or any
  other visitor sees nothing. Crypt covers installs without
  SESSION_ENCRYPT, which the Docker setup does not set.
- The symbol set leaves out what chat apps turn into formatting and
  what breaks inside quotes, so a pasted password arrives unchanged.
- app.css imports group.css for <x-group>; .ai/rules/views.md records
  that <x-group> drops data-test and other attributes.
- Tests cover the generator, the admin card's saving, validation and
  example, prefill and generate on the upload page, the flash, and in
  Chromium Generate and Copy on the upload page and the masked copy on
  the share page. The admin settings page now has six headed sections.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 11:00:11 +02:00
Andreas Reinhold / reiniandClaude Opus 5 a88a052d9a Move onto Livewire Material 2.0.0 and leave Tailwind behind
Livewire Material 2.0.0 aligns every component with Material 3
Expressive and carries no Tailwind anywhere, so SealShare drops
tailwindcss and its Vite plugin and writes its views in the package's
vocabulary: layout components (<x-pane>, <x-stack>, <x-row>, <x-grid>,
<x-form>) with M3's spacing tokens, the md-type-*/md-ink-* text classes,
and --md-sys-* tokens in its own small stylesheet.

- resources/css/app.css opens with the package's layer order, imports
  foundation.css and the stylesheet of each component the views render,
  then the scheme, regenerated with the 2025 colour rules at M3's three
  contrast levels. The app's own rules follow, one section per view,
  on tokens and on M3's breakpoints (600/840/1200/1600px) only.
- Every view is rewritten in that vocabulary while SealShare keeps the
  shape it had: the admin table, the admin settings, the recovery codes,
  the share options and the download page are cards, and their fields
  fill them rather than stopping at the 40rem bound a card already
  bounds. The user settings pages became cards too, to match the
  admin's, each with the sections that stand apart from its one subject
  — deleting the account, the recovery codes — in a card beside it.
  Material 3 decides how a component behaves, not whether a container
  survives: buttons keep their label's width, a form's actions end it,
  and each heading level keeps one type role.
- The layouts clear the floating toolbar by the --material-bottom-toolbar
  the package publishes, and the snackbar clears it by itself.
- The two-factor setup QR code comes from QrCodeService, so it keeps a
  white field and quiet zone in the dark theme and still scans.
- Browse Files is a real button that opens the file input, reachable
  and visibly focused from the keyboard.
- Tests: the design test scans views, JS, CSS and app/ and checks the
  CSS entry both ways (no missing, no unused import). New Chromium
  suites cover the frame, settings and admin, the share flow, and every
  page at M3's breakpoint edges (599/600, 839/840, 1199/1200, 1600px).
  The package's renamed data-md-* hooks replace the 1.x ones.
- Boost's update brings the material-3 guideline and skill and drops
  the Tailwind skill.

composer.json requires nonameweb/livewire-material ^2.0 from the Gitea
repository, resolved at the 2.0.0 tag. The CHANGELOG records the move as
2.1.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 22:30:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 461bc23f0a Fix the Docker image not starting: update Livewire Material to 1.1.1
tests / ci (8.5) (push) Successful in 2m16s
linter / quality (push) Successful in 1m1s
docker / build-and-push (push) Successful in 7m5s
docker / test (8.5) (push) Successful in 2m16s
docker / release (push) Successful in 4s
The entrypoint's php artisan view:cache failed on the package's showcase
views, whose components were only registered with the showcase enabled.
1.1.1 registers them always. ProductionBootTest caches every view with
the showcase off, as the entrypoint does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
v2.0.1
2026-09-13 17:59:34 +02:00
Andreas Reinhold / reiniandClaude Opus 5 cd97612b4d Keep planning notes out of the repository
docker / test (8.5) (push) Successful in 2m22s
linter / quality (push) Successful in 1m7s
tests / ci (8.5) (push) Successful in 2m24s
docker / build-and-push (push) Successful in 7m26s
docker / release (push) Skipped
docs/plans is ignored from now on; the notes stay on the machine that
wrote them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 16:24:30 +02:00
Andreas Reinhold / reiniandClaude Opus 5 c95d0c43c2 Load the website's Plausible site script
linter / quality (push) Successful in 1m7s
tests / ci (8.5) (push) Successful in 2m16s
docker / build-and-push (push) Successful in 7m5s
docker / test (8.5) (push) Successful in 2m25s
docker / release (push) Successful in 5s
Plausible gives each new site its own script and an init call instead
of the generic script with data-domain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
v2.0.0
2026-09-13 16:21:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 09e24ade14 Build the amd64 image with QEMU 9.2.2 and only emulate the final stage
docker / test (8.5) (push) Successful in 2m20s
linter / quality (push) Successful in 1m5s
tests / ci (8.5) (push) Successful in 2m16s
docker / build-and-push (push) Successful in 20m13s
docker / release (push) Skipped
QEMU 8.x crashes running x86_64 programs on an arm64 host (QEMU issue
2168), which the 8.1.5 pin walked into, and 10.2 segfaults on the runner
as well; 9.2.2 runs node, composer and install-php-extensions on the
runner's host. The Composer and npm stages now build on the build
machine's platform: their output is the same for every target, so a
multi-arch build runs them once and natively.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:52:58 +02:00
Andreas Reinhold / reiniandClaude Opus 5 21bea9646d Let the admin choose one of eight colour profiles
linter / quality (push) Successful in 1m3s
tests / ci (8.5) (push) Successful in 2m8s
docker / test (8.5) (push) Successful in 2m15s
docker / build-and-push (push) Failing after 7m22s
docker / release (push) Skipped
Indigo (the default), Blue, Teal, Green, Amber, Rose and Violet in the
Vibrant style and Graphite in the Neutral style are generated from
config into the stylesheet. Admin settings opens with a colour profile
card: a swatch previews the profile on the page, and Save Settings
stores it as color_profile, which AppServiceProvider hands to the
package's resolver, so every page, mail and error page wears it. An
unknown profile is refused, and a saved one that disappears falls back
to indigo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:06:47 +02:00
Andreas Reinhold / reiniandClaude Opus 5 27e322c352 Update Livewire Material to 1.1.0
It brings colour profiles: several generated schemes under
<html data-scheme>, a resolver for the active one, and a picker. Boost's
copy of the package guideline and skill follows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:06:47 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ca0dfa9396 Emulate the amd64 image with QEMU 8 on the arm64 runner
docker / test (8.5) (push) Successful in 2m9s
linter / quality (push) Successful in 1m4s
tests / ci (8.5) (push) Successful in 2m10s
docker / build-and-push (push) Failing after 9m53s
docker / release (push) Has been skipped
Recent QEMU segfaults compiling PHP extensions for amd64 on the runner's
6.8 kernel (docker/buildx#3170); QEMU 8.1.5 is the version reported to
work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 14:31:45 +02:00
Andreas Reinhold / reiniandClaude Opus 5 d860a16551 Move SealShare from GitHub to Gitea only
docker / test (8.5) (push) Successful in 2m13s
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m7s
docker / build-and-push (push) Failing after 9m54s
docker / release (push) Has been skipped
The workflows live in .gitea/workflows. The Docker workflow logs in to
the Gitea container registry with REGISTRY_TOKEN (Gitea's job token cannot
publish packages yet), publishes gitea.nonameweb.ch/nonameweb/sealshare
with a registry build cache, and makes the release on Gitea with the
version's section of the changelog as its notes.

The README, the website's quick start, both compose files and the image
label point to Gitea; the changelog announces the new image name and
links Gitea. GiteaOnlyTest keeps GitHub and ghcr.io out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 13:59:07 +02:00
Andreas Reinhold / reiniandClaude Opus 5 606cc766f2 Add the SealShare website for sealshare.nonameweb.ch
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
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
Andreas Reinhold / reiniandClaude Opus 5 e49bdf3eb3 Take the website's screenshots with one command
composer screenshots runs Pest browser tests in tests/Screenshots, outside
every test suite: fixed demo data under a frozen clock, desktop (MacBook
14, 2x) and phone (iPhone 15 Pro, 3x) in light and dark, each capture
published at once as WebP at two widths into website/img/screenshots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 12:30:53 +02:00
Andreas Reinhold / reiniandClaude Opus 5 4470c2ed87 Give the dev container its own node_modules
npm installs the build tools' native binaries for the platform it runs
on, so a folder shared with the host only ever held Linux's or macOS's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 12:30:53 +02:00
Andreas Reinhold / reiniandClaude Opus 5 1352533667 Offer a new share as a QR code and through the share sheet
The share created page gets "Show QR code", a dialog with the link as a
QR code (black on white, full screen on a phone) that downloads as a PNG
drawn in the browser, with a password reminder for protected shares; and
"Share…", which opens the device's share sheet where there is one. Both
carry only the link.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:46:07 +02:00
Andreas Reinhold / reiniandClaude Opus 5 fbe358b2e5 Require Bacon QR Code directly and update Livewire Material to 1.0.1
Bacon QR Code stays at the v3.1.1 Fortify already installed; SealShare
now draws QR codes with it too. Livewire Material 1.0.1 compiles its
components to the same view on every machine and keeps a full-screen
dialog's subtitle on a phone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:46:07 +02:00
Andreas Reinhold / reiniandClaude Opus 5 9fd600571d Keep the dev container's compiled views out of the shared storage
The checkout is mounted at /app, so views compiled in the container and
on the host landed in one storage/framework/views, and each side read the
other's absolute paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:17:38 +02:00
Andreas Reinhold / reiniandClaude Opus 5 accbc17c1c Move the navigation into a floating toolbar at the bottom
The top app bar repeated the site's name, which already heads the upload
and download pages, and spread its actions to the window's far edge. A
floating toolbar centred above the bottom edge, as wide as its buttons,
now holds them: Upload and the admin pages with the current one filled,
and the account menu; guests get Upload, the theme toggle and Log in,
without tooltips. On a phone the sign-in card no longer stretches to the
full height, and the admin dashboard's empty state sits outside the
scrolling table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:12:12 +02:00
Andreas Reinhold / reiniandClaude Opus 5 2a363defd1 Install Playwright before the Docker workflow's tests
Its test job runs the whole suite, browser tests included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:02:28 +02:00
Andreas Reinhold / reiniandClaude Opus 5 3604005cc7 Describe 2.0.0 in the README and changelog
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:02:03 +02:00
Andreas Reinhold / reiniandClaude Opus 5 a86f035add Install Livewire Material's Boost guideline and skill, and record rules
The scheme is regenerated, never hand-edited; the download page stays
free of anchored components; the theme key stays sealshare-theme.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:02:03 +02:00
Andreas Reinhold / reiniandClaude Opus 5 eb33bf77cc Send Markdown mail in the Material theme
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:02:03 +02:00
Andreas Reinhold / reiniandClaude Opus 5 bc69066ead Add browser tests for the Material interface
Upload drop zone, copying a share link, unlocking a download on a phone,
sorting and deleting on the admin dashboard, and the system theme with
the Appearance picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 11:02:03 +02:00
Andreas Reinhold / reiniandClaude Opus 5 c4a17b65c8 Move SealShare onto Livewire Material
Replaces maryUI and daisyUI with nonameweb/livewire-material: the Vibrant
indigo scheme, a system/light/dark theme under sealshare-theme, one top
app bar with the account menu, the upload drop zone and link-ready
moments, M3 fields, dialogs instead of wire:confirm, snackbars instead of
flashed messages, a sortable admin table, and the starter-kit cleanup.
Docker builds assets after Composer; CI drops the Flux step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 10:49:38 +02:00
Andreas Reinhold / reiniandClaude Opus 5 6d7120e8e2 Drop icons:cache from the Livewire Material adoption steps
docker / test (8.5) (push) Successful in 2m13s
linter / quality (push) Successful in 59s
tests / ci (8.5) (push) Successful in 1m21s
docker / build-and-push (push) Failing after 12m32s
docker / release (push) Has been skipped
The package draws its Material Symbols without blade-icons, so there is no icon manifest to cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 05:24:13 +02:00
Andreas Reinhold / reiniandClaude Opus 5 2eeaa7c139 Add the plan for moving SealShare onto Livewire Material
SealShare leaves maryUI and daisyUI for nonameweb/livewire-material, a
shared Material 3 Expressive component package, once it reaches 1.0.0.
This plan holds SealShare's decisions and its adoption steps; the
package's own plan lives in the package repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 04:59:30 +02:00
Andreas Reinhold / reiniandClaude Opus 5 08667c617d Release 1.2.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:22 +02:00
Andreas Reinhold / reiniandClaude Opus 5 92b3b3de56 Regenerate Boost guidelines and skills
Generated by boost:update for Boost 2.8, which replaces the pest-testing
skill with testing-best-practices and adds infer-conventions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:22 +02:00
Andreas Reinhold / reiniandClaude Opus 5 3638455167 Upgrade frontend dependencies
Alpine.js 3.17.2, DaisyUI 5.7.32, Vite 8.2.2, laravel-vite-plugin 3.2,
autoprefixer 10.5.5 and concurrently 10.0.5. npm audit reports 0
vulnerabilities.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:21 +02:00
Andreas Reinhold / reiniandClaude Opus 5 5be3cbc1a7 Upgrade PHP dependencies and move to Pest 5
Pest 5.1 brings PHPUnit 13.3; phpunit.xml and the tests needed no
changes. Everything else stays within its existing constraint: Laravel
13.31, Livewire 4.4.4, Octane 2.19.1, Fortify 1.39, Mary 2.9.10, Boost
2.8 and Pint 1.32. Guzzle moves to 8.2 as a transitive dependency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:21 +02:00
Andreas Reinhold / reiniandClaude Opus 5 126c0a5cdb Fix uploads over 4 GB failing with a misleading size error
Livewire's temporary upload rule was hard-coded to max:4194304 (4 GB),
so /livewire/upload-file rejected any larger file no matter how high
PHP_UPLOAD_MAX_FILESIZE or the admin's max file size were set.
_uploadErrored() then replaced Livewire's actual message with "file
exceeds the maximum size of N MB", quoting the admin limit the file was
under.

The cap is removed: PHP's upload_max_filesize is the hard limit and the
admin setting is still enforced in updatedFiles() and createShare(). A
rejected upload now logs the real validation errors and tells the user
the server could not accept the file.

max_upload_time is configurable via LIVEWIRE_MAX_UPLOAD_TIME, and the
README documents every limit large uploads depend on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:21 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 992f3da084 Add CHANGELOG and refresh stale README tech stack
Documents 1.0.0, 1.0.1 and the upcoming 1.1.0 in Keep a Changelog format.

The tech stack table still claimed Laravel 12 and listed
maennchen/zipstream-php, which was dropped in 1.0.1 when ZIP downloads
moved to native ZipArchive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:46:50 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 d2e8a135c5 Fix upload UI stuck on "Processing files..."
The `uploading` flag was only cleared by `x-init="uploading = false"` on the
file list, which Alpine runs when the element is initialized. Adding a second
batch of files to the same share only patches that existing element, so the
flag stayed true forever: the spinner never went away, the drop zone stayed
disabled and the submit button stayed disabled, making the share impossible
to create.

`updatedFiles()` now dispatches `files-processed`, which runs for every batch,
and the form resets its upload state on that event instead.

Also fixes two adjacent bugs in the drag & drop path: `uploadMultiple()` was
called without callbacks so dropped files showed no progress at all, and
`relativePaths` was replaced instead of appended, shifting every earlier
file's path onto the wrong file on a second drop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:44:35 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 084cefe023 Force patched shell-quote via npm override
Clears the last outstanding advisory (GHSA-395f-4hp3-45gv, quadratic
complexity DoS in shell-quote's parse()). npm audit now reports 0
vulnerabilities, down from 3 before this branch.

concurrently pins "shell-quote": "1.8.4" exactly, and the maintainers
patched only the 9.x line (9.2.4 ships shell-quote 1.9.0) while 10.x still
carries 1.8.4. npm's only offered remedy was therefore a downgrade to
concurrently@9.2.4. The override keeps concurrently at 10.0.3 and resolves
shell-quote to 1.10.0 instead.

Overriding a deliberate exact pin warrants checking concurrently still
works, so this was verified functionally in node:24-alpine rather than
assumed: quoted and escaped arguments parse correctly (the shell-quote
code path), named prefixes render, and --kill-others-on-fail still
propagates a non-zero exit. npm install, npm ci and npm run build all
succeed, build hashes are unchanged, and the PHP suite passes 123 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:31:58 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 91dc4707af Upgrade frontend dependencies to match the Laravel 13 skeleton
Bump Vite 7 to 8 and laravel-vite-plugin 2 to 3. These are coupled:
laravel-vite-plugin@3.1.3 peer-requires vite ^8.0.0, so neither moves alone.
Also concurrently 9 to 10, tailwindcss and @tailwindcss/vite to 4.3.3,
daisyui to 5.7.0, alpinejs to 3.15.12 and autoprefixer to 10.5.4.

Drop axios. It had zero imports anywhere in resources/ or vite.config.js,
and Laravel 13 removed it from the skeleton along with resources/js/bootstrap.js.

Drop the @rollup/rollup-linux-x64-gnu optional dependency. Vite 8 builds with
rolldown instead of rollup, so the pin no longer refers to anything in the
build, and it was stuck at an exact 4.9.5 against a rollup that had since
reached 4.62.x. The oxide and lightningcss optional pins are kept, as those
native binaries are still used by tailwind and vite.

vite.config.js needed no changes: it sets no rollupOptions, esbuild or
manualChunks, so nothing hit the vite 8 config renames. Node engines
(^20.19 || >=22.12) are satisfied by the node:24-alpine build stage, the
dev container's Node 24.17 and CI's Node 24.

Verified in a node:24-alpine container matching the Dockerfile assets stage:
npm install, npm run build (vite 8.1.5, 3 modules, deterministic hashes) and
npm ci all succeed. The rendered /upload page references the new build hashes,
and the PHP suite still passes 123 tests.

npm audit fix patched a pre-existing picomatch advisory reached via
laravel-vite-plugin's vite-plugin-full-reload, with no downgrades. A
shell-quote advisory via concurrently remains and is also pre-existing;
concurrently is a local dev orchestration tool only and is not shipped
to browsers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:27:38 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 4391ad7fb9 Regenerate Boost guidelines and skills
Byproduct of the Laravel 13 upgrade: composer's post-update-cmd runs
`artisan boost:update`, and Boost went 2.1.3 to 2.4.x with the update.

Refreshes CLAUDE.md and boost.json for the Laravel 13 / Livewire 4 stack,
updates the livewire, pest and tailwindcss skills, replaces the
developing-with-fortify skill with fortify-development, and adds new
laravel-best-practices and octane-development skills.

Generated output, not hand-edited.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:17:04 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 9f929c7a78 Apply Pint 1.29 formatting
The Laravel 13 upgrade pulled Pint 1.27.1 to 1.29.3, which changed the
laravel preset's default rules. composer lint and the CI lint job fail
without this, so it is required rather than cosmetic.

Formatting only, applied by `vendor/bin/pint`. The rules that fired were
fully_qualified_strict_types, ordered_imports, single_blank_line_at_eof,
unary_operator_spaces, not_operator_with_successor_space, braces_position,
single_line_empty_body, single_line_after_imports and no_extra_blank_lines.

Kept separate from the upgrade commit to keep that diff reviewable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:16:59 +02:00
Andreas Reinhold / reiniandClaude Opus 4.8 1ebd51248e Upgrade to Laravel 13
Bump laravel/framework to ^13.0 and laravel/tinker to ^3.0 per the 12.x
to 13.0 upgrade guide. php ^8.5 already satisfied the new minimum.

Transitively picks up Fortify 1.37.3, Octane 2.18.0, Livewire 4.3.3,
Mary 2.9.7 and Pest 4.7.5, all of which already declare ^13.0 support.

Add 'serializable_classes' => false to config/cache.php. When the key is
absent it defaults to null, which leaves unserialize() unrestricted; false
blocks object deserialization so a leaked APP_KEY cannot be used to drive a
gadget chain through the cache. Safe here as no app code uses the cache.

Audited every breaking change in the guide against the codebase with no
hits: VerifyCsrfToken/ValidateCsrfToken (now PreventRequestForgery),
JobAttempted->exceptionOccurred, QueueBusy->$connection, pagination::default,
Js::from and array_first/array_last. config/cache.php and config/database.php
already used the new hyphenated prefixes, so cache keys and session cookies
are unchanged and nothing invalidates on deploy.

Verified: 123 tests pass (306 assertions), migrations apply cleanly, and
/up, /login and the Livewire asset all serve 200 over HTTP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:16:53 +02:00
surtic86andClaude Opus 4.6 1e07580bb2 Remove stale test for deleted decryptFileToCallback method
The method was removed in 729e014 but its unit test was not cleaned up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:52:44 +01:00
surtic86andClaude Opus 4.6 75e8ff3bfd Fix share page password unlock button and full-width buttons
Move form wrapper outside x-card so the submit button renders inside
the form DOM, and add w-full to all share page action buttons for
consistent full-width styling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:46:39 +01:00
surtic86andClaude Opus 4.6 a6f604078b Fix Docker build: add ext-zip, clean cache, fix DB_DATABASE default
- Add php zip extension to Dockerfile (required by ZipArchive)
- Remove bootstrap/cache/*.php during build to prevent stale
  dev-only service providers (e.g. BoostServiceProvider) from loading
- Default DB_DATABASE to /app/database/database.sqlite in
  docker-compose.yml so env() fallback works correctly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:34:16 +01:00
surtic86andClaude Opus 4.6 729e01462b Replace ZipStream with native ZipArchive for FrankenPHP compatibility
ZipStream writes via fwrite(php://output) which FrankenPHP silently
drops, resulting in 0-byte ZIP downloads. Switch to ZipArchive to build
the ZIP as a temp file on disk, then serve with response()->download().

- Rewrite download() to use ZipArchive + BinaryFileResponse
- Remove decryptFileToCallback() from FileEncryptionService
- Update tests for BinaryFileResponse instead of StreamedResponse
- Remove maennchen/zipstream-php dependency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:34:05 +01:00
surtic86andClaude Opus 4.6 e37b322dde Add SealShare file sharing application with encryption, branding, auth, Docker, and Octane support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:43:20 +01:00
Andreas Reinhold / reini 4790be8142 init 2026-02-12 16:11:57 +01:00