Releases: smashkins/paperstand
Release list
Paperstand 0.6.0
Added
paperstand retry-coversresets every issue flagged as not a valid PDF back topending
and touches the scan trigger when it changed at least one row. A replaced file never needs
it — new bytes are a new issue, catalogued by the next scan on its own. It is for a verdict
there is reason to doubt, such as rows stamped before the fix below told the two cases
apart.
Fixed
- A cover that failed because the server could not read the file — permissions, I/O, a full
cache — used to be stampederrorfor good, the same as a file that genuinely is not a
PDF. It now stayspendingand is retried by every scan on its own, and the maintenance
page's Unreadable list says in words which of the two cases a row is, so a reader knows
whether to fix the file's permissions or replace it.
Paperstand 0.5.0
Changed
paperstand organize --applynow removes the inbox folders a run emptied, and only those.
Anything that writes each issue into a folder of its own used to leave that folder behind
on every import, until the inbox was a wall of empty directories. The removal climbs from
the file's own parent withrmdiralone, stops at the inbox root and atunsorted/and
duplicates/, and leaves any folder still holding something exactly where it is;
--keep-empty-foldersturns it off, as it already did formigrate. No file is ever
deleted. The removal never leaves the inbox: a walk descends into a symlinked folder while
it points inside, so every candidate is resolved first and removed by its resolved path,
and the symlink itself is never removed either.
Paperstand 0.4.4
Changed
- The organizer's parked-file tables show the name and the reason only. The size and the
modification time are gone: their headings were the widest thing in the row and neither
answers the question the table is for. Both are still in the run's own report under
<data>/organizer/. The table now fits a phone screen without scrolling sideways.
Paperstand 0.4.3
Fixed
- In the organizer's parked-file tables, the file name column is no longer squeezed by the
reason next to it. A longduplicate of …path had no place to break, so it took the
width and left the name a narrow strip five lines tall; it now wraps like the rest.
Paperstand 0.4.2
Fixed
- A long renderer error in the unreadable list no longer pushes its row past the right edge
of a phone screen, where the text was cut off. The message now wraps inside the card.
Paperstand 0.4.1
Fixed
- The maintenance sections no longer stretch wider than a phone screen, which pushed each
section's See all link off the right edge where it could not be reached. A parked-file
table scrolls on its own again, and a long file name or renderer error in the unreadable
list wraps instead of widening the page.
Paperstand 0.4.0
The maintenance page stops being one long scroll. Each of its four categories shows its
five most relevant rows and a link to a page of its own that carries the whole list, so a
library with hundreds of unreadable files opens as fast as one with none. Missing and
Gaps are renamed, because the two were being read as the same thing: one is a file that
was catalogued and is no longer on disk, the other an issue that was never in the library.
Added
- Four pages behind the maintenance overview, one per category, each listing everything
the overview shows five of:/maintenance/missing,/maintenance/unreadable,
/maintenance/inboxand/maintenance/gaps. The two issue lists page to the end with
Load more.
Changed
- The maintenance overview shows at most five rows per category, with its real total next
to the heading and a See all link once there are more. It asks the API for five rows
instead of two hundred. - Missing is now Files gone missing and Gaps is now Gaps in a series, each with a
line saying what it means, so the two stop reading as the same thing.
Paperstand 0.3.0
The library becomes a place Paperstand can tidy. An issue is now identified by its bytes
rather than its path, so a file can be renamed or moved and keeps its id, its cover, its
cached pages and where you had got to in it. On that footing a folder can declare itself a
title with a publication.yml, the new organizer can import PDFs from a writable inbox into
a canonical layout, and paperstand migrate can bring a collection that grew up in any of
the tolerant layouts into that one shape, in place, without the server ever writing inside
the library. A file that vanishes is marked missing and kept for a grace period instead of
being forgotten on the spot, a root marker tells an unmounted share apart from an emptied
library, and a new maintenance page shows what needs a hand: missing issues, files the
renderer cannot open, whatever the organizer parked, and the holes in a title's numbering
or its cadence.
Upgrading rewrites every issue id once, so a bookmarked reader URL and an OPDS
urn:paperstand:issue: change one time; covers are renamed, never re-rendered. Create
.paperstand-library in the library root before the first scan if you want the root-marker
protection, and run that first scan before asking migrate to move anything.
Added
- A dry-run
organize-plancommand that prints where each file would live
under a canonical<Title>/<YYYY>/<Title> - <ISO date>[ - n<number>].pdf
layout; nothing it does reaches the running server yet. publication.yml: a folder anywhere under a library can now declare itself
a title — a slug, a display title, a kind, a frequency, a language, what
counts as a duplicate, its declared supplements, a parent — and every PDF
beneath it, at any depth, is catalogued under that title, never Unsorted.
Seefolder-layout.mdand
configuration.md.- The canonical grammar now reads a volume and a variant off a name —
<Title> - <ISO date>[ - [v<volume> ]n<number>][ - <variant>]— exposed as
volumeandvarianton an issue through the API; the variant is also part
of the issue label, which OPDS entries show. Titlegainsslug,frequency,language,issue_key,parent_slug
andsupplements;Issuegainsvariantandvolume; all nullable, and
set only for a declared publication.- OPDS entries carry
dc:languagewhen their title declares one. organize-planplans a declared title's files into its own folder, named
after the folder's own basename, and prints-> in placefor a file
already shaped that way; the summary gains that count.Issuegainscontent_hash, the file's full SHA-256;ScanProgressgains
hashed, how many files the running scan has read in full to compute a
content hash — new, touched or genuinely changed, and a legacy row
backfilled once.paperstand organizeand the writable inbox it reads: imports PDFs into the
canonical layout, atomically and never overwriting a file already in the
library; a file it cannot place is parked underunsorted/and re-read on
every run, a byte-identical copy of a catalogued file underduplicates/.
--everyrepeats the run on an interval until stopped. See
organizer.md.PAPERSTAND_INBOX(default/inbox), the folderpaperstand organizereads.- An optional
organizercompose service, off by default, mounting the
library read-write and runningorganize --apply --every PAPERSTAND_ORGANIZE_INTERVALon a loop — the only container that ever
writes into the library. - The image now accepts a command after the entrypoint, so
docker run(or
the new compose service) can runorganizeinstead of the defaultserve. - A file that vanishes from the library is no longer removed on the spot: a
scan marks it missing instead, hidden from the storefront, the calendar,
Today, search, OPDS and every count — its row, cover, pages and reading
position stay — and only actually forgets it once
PAPERSTAND_MISSING_GRACE_DAYS(default 7,0restores the previous
immediate removal) has passed since the scan that first noticed. The same
bytes turning up again, at the same path or anywhere else, clears the mark
without any re-render.Issue.missing_since,GET /api/issues?missing=true
andStats.missing_countexpose the state;missingon the scan progress,
the scan summary and thescanstable counts it. See
folder-layout.md. - A
.paperstand-librarymarker at the library root, created by hand and
remembered for good by the first scan that sees it: once remembered, a
scan whose root can be listed but has lost the file is refused outright —
nothing is touched, thescansrow closes as an error naming the marker —
telling an unmounted share apart from a library emptied on purpose.
HealthResponse.library_markerreports the same three states (never set
up, present, lost);paperstand organize --applymakes the same check
before it moves a single file. See
The root marker. - A maintenance page,
/maintenance, linked from Settings and from an icon
in the top bar with a badge for how many items need attention: issues gone
missing, files the renderer could not open, what the organizer parked
underunsorted/andduplicates/, and every title with a hole in its
numbering or its declared cadence. Seemaintenance.md. GET /api/maintenance(missing, unreadable and unsorted counts, the
organizer's last run, the attention total) and
GET /api/maintenance/gaps(holes in a title's numbering, and in a
declared daily, weekly or monthly cadence, with an overdue flag).GET /api/issues?unreadable=trueandIssue.cover_error, the renderer's
own message for a file it could not open at all.- The organizer writes a report of every run to
<data>/organizer/last-run.json— mode, counts, this run's moves, and an
inventory ofunsorted/andduplicates/with each file's sidecar
reason — read back by the server on everyGET /api/maintenance. See
The run report and the scan trigger. <data>/scan.request, a trigger file the scheduler polls for and consumes:
a third way to ask for a scan, next to Rescan now andPOST /api/scan,
that needs no server URL.paperstand organize --applytouches it after a
run that moved at least one file.paperstand migrate: renames and moves a file already inside the library
to the canonical layout, in place — the same planorganize-planprints,
performed rather than previewed.--applymoves every unambiguous file
with the same atomic mover asorganize, leaves a collision group
untouched, removes a folder its own moves left empty
(--keep-empty-foldersto keep them), refuses before touching anything if
the catalogue has not caught up with a movable file's content hash yet,
and writes its own report to
<data>/organizer/migrations/<started_at>.json, one file per applied run,
never overwritten. Seeorganizer.md.
Changed
-
Every cover, thumbnail and rendered page now lives under a version
directory —<data>/cache/covers/v1/…,<data>/cache/pages/v1/…— and
every image URL carries?v=<version>instead of the file's own
modification time, so a touch of a PDF changes neither the URL nor the
ETag. Bumping the version, the whole of a cache invalidation, deletes the
previous version's directory and lets the next scan or request re-render;
a cache in the earlier, unversioned layout is moved intov1/in place at
start-up, file by file, without re-rendering anything. -
organize-plannow plans an undeclared configured title inside its own
library, at<library path>/<Title>/<YYYY>, rather than at the library
root — a spot the scanner would never have catalogued. -
The bundled profile now tries the canonical declared-publication grammar
before its other patterns, so a name shaped<Title> - <ISO date>is read
by it first even outside a declared folder. A file whose name disagrees
with the folder it sits in now has the name win:Title - 2026-03.pdfin a
folder-titled layout used to take its title from the folder, and now takes
it from the name, matching every other layout. -
An issue's id now derives from its content, not its path — see
folder-layout.md. The first scan after
upgrading rewrites every id once: a bookmarked reader URL and an OPDS
entry'surn:paperstand:issue:change once, but every cover is renamed
rather than re-rendered. After that, renaming or moving a file anywhere in
its own library keeps its id, its cover, its cached pages and its reading
position; a byte-identical copy is a duplicate of the original wherever it
sits in the library, whatever it is called; touching a file without
changing its bytes no longer re-renders anything; a file replaced with
different bytes is a new issue. -
The scheduler's background loop now always runs, even with automatic
scanning off entirely (PAPERSTAND_SCAN_INTERVAL=0and
PAPERSTAND_SCAN_ON_START=false), so that the scan trigger file keeps
working in that deployment too.
Paperstand 0.2.1
The reader moves to pdf.js 6, and a tap in the middle of a page does what a tap should.
Changed
- The reader renders with pdf.js 6, shipped as its
legacybuild — the one
pdf.js states a minimum browser for. The in-browser reader now needs Chrome
125, Safari 18 or a browser of the same generation or newer; the storefront
and the OPDS feed are unaffected. Range requests, rendering and memory
behaviour were checked against the previous major and are unchanged.
Fixed
- A tap in the middle of the page no longer flashes the reader toolbar before
hiding it again: it now shows the toolbar when it is hidden and hides it
when it is shown, as a toggle should. An edge tap, a swipe, a pinch, a pan
while zoomed, a double tap and a moving mouse or pen still bring the toolbar
up as before.
Paperstand 0.2.0
The catalogue tells you what it is doing, and the parser reads two more numbering shapes.
Added
- Live scan progress:
GET /api/scan/statusreports the running scan's phase,
its counters and the elapsed time while it works, refreshed every two seconds;
Settings shows the phase, the live counters, the elapsed time and a progress
bar — indeterminate while cataloguing, a fraction of the covers rendered once
the total is known. POST /api/scanandGET /api/scan/statusnow document their bodies with
named OpenAPI schemas, andGET /api/healthwith its own; the frontend's
hand-written scan and health types are gone in favour of the generated ones.- The
defaultprofile reads two more real-world shapes: a year-stamped volume and a
running issue number,Title vYYYY cNNNandTitle cNNN - vYYYY, in either order.
Fixed
- A pattern that captures an issue number, or any other named group, no longer has it read
as a day by the generic date rules: every span the pattern captured is masked before they
run, not only its date groups.
Image: ghcr.io/smashkins/paperstand:0.2.0 (linux/amd64, linux/arm64).