Skip to content

Releases: smashkins/paperstand

Paperstand 0.6.0

Choose a tag to compare

@smashkins smashkins released this 17 Sep 21:52

Added

  • paperstand retry-covers resets every issue flagged as not a valid PDF back to pending
    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 stamped error for good, the same as a file that genuinely is not a
    PDF. It now stays pending and 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

Choose a tag to compare

@smashkins smashkins released this 15 Sep 10:43

Changed

  • paperstand organize --apply now 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 with rmdir alone, stops at the inbox root and at unsorted/ and
    duplicates/, and leaves any folder still holding something exactly where it is;
    --keep-empty-folders turns it off, as it already did for migrate. 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

Choose a tag to compare

@smashkins smashkins released this 13 Sep 19:55

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

Choose a tag to compare

@smashkins smashkins released this 13 Sep 15:19

Fixed

  • In the organizer's parked-file tables, the file name column is no longer squeezed by the
    reason next to it. A long duplicate 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

Choose a tag to compare

@smashkins smashkins released this 13 Sep 14:33

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

Choose a tag to compare

@smashkins smashkins released this 13 Sep 14:12

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

Choose a tag to compare

@smashkins smashkins released this 13 Sep 13:49

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/inbox and /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

Choose a tag to compare

@smashkins smashkins released this 12 Sep 18:48

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-plan command 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.
    See folder-layout.md and
    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
    volume and variant on an issue through the API; the variant is also part
    of the issue label, which OPDS entries show.
  • Title gains slug, frequency, language, issue_key, parent_slug
    and supplements; Issue gains variant and volume; all nullable, and
    set only for a declared publication.
  • OPDS entries carry dc:language when their title declares one.
  • organize-plan plans a declared title's files into its own folder, named
    after the folder's own basename, and prints -> in place for a file
    already shaped that way; the summary gains that count.
  • Issue gains content_hash, the file's full SHA-256; ScanProgress gains
    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 organize and 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 under unsorted/ and re-read on
    every run, a byte-identical copy of a catalogued file under duplicates/.
    --every repeats the run on an interval until stopped. See
    organizer.md.
  • PAPERSTAND_INBOX (default /inbox), the folder paperstand organize reads.
  • An optional organizer compose service, off by default, mounting the
    library read-write and running organize --apply --every PAPERSTAND_ORGANIZE_INTERVAL on 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 run organize instead of the default serve.
  • 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, 0 restores 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
    and Stats.missing_count expose the state; missing on the scan progress,
    the scan summary and the scans table counts it. See
    folder-layout.md.
  • A .paperstand-library marker 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, the scans row closes as an error naming the marker —
    telling an unmounted share apart from a library emptied on purpose.
    HealthResponse.library_marker reports the same three states (never set
    up, present, lost); paperstand organize --apply makes 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
    under unsorted/ and duplicates/, and every title with a hole in its
    numbering or its declared cadence. See maintenance.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=true and Issue.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 of unsorted/ and duplicates/ with each file's sidecar
    reason — read back by the server on every GET /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 and POST /api/scan,
    that needs no server URL. paperstand organize --apply touches 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 plan organize-plan prints,
    performed rather than previewed. --apply moves every unambiguous file
    with the same atomic mover as organize, leaves a collision group
    untouched, removes a folder its own moves left empty
    (--keep-empty-folders to 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. See organizer.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 into v1/ in place at
    start-up, file by file, without re-rendering anything.

  • organize-plan now 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.pdf in 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's urn: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=0 and
    PAPERSTAND_SCAN_ON_START=false), so that the scan trigger file keeps
    working in that deployment too.

Paperstand 0.2.1

Choose a tag to compare

@smashkins smashkins released this 08 Sep 19:17

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 legacy build — 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

Choose a tag to compare

@smashkins smashkins released this 08 Sep 14:59

The catalogue tells you what it is doing, and the parser reads two more numbering shapes.

Added

  • Live scan progress: GET /api/scan/status reports 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/scan and GET /api/scan/status now document their bodies with
    named OpenAPI schemas, and GET /api/health with its own; the frontend's
    hand-written scan and health types are gone in favour of the generated ones.
  • The default profile reads two more real-world shapes: a year-stamped volume and a
    running issue number, Title vYYYY cNNN and Title 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).