window.prompt() looks out of place and behaves badly in an installed
PWA, especially on iPad. The new dialog has width/height fields in mm,
preset chips and a swap button, validates the 50–5000 pt range, and
keeps preset and unchanged sizes exact instead of rounding them to
whole millimetres.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
A quick one-finger flick toward a free side of the active page, once
that edge is on screen, highlights and pulses the plus button on that
side for three seconds. It never adds a page by itself, so ordinary
panning can't create pages; a tap on the highlighted plus does.
Fling detection lives in ink/fling.ts with tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
A pointercancel (system gesture, notification, palm) used to apply the
strokes erased so far. Only a real pointerup commits the erase now, the
same as ink strokes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
Move the draw-or-navigate choice into touchIntent() so the rule the
canvas uses can be tested, and cover finger taps on the plus button:
accepted after writing and while the pen hovers, rejected while the
pen is down, for palm-sized contacts and right after pen activity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
The button is now a 32 px circle in a 48 px hit area, 8 px off the
page edge. Touch taps on it were palm-checked with the 'draw' policy,
which rejects every finger for 5 s after pen activity and while the pen
hovers, so fingers rarely got through. Use the 'navigate' policy
instead: pen-down, palm-sized contacts and touches right after pen use
are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
Replace the add-page bar with a 24 px plus-circle button centred on
each free edge of the active page, 6 px off the edge (constant in
screen pixels). Taps still go through the palm-checked gesture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
vite.config.ts stamps the package version, git commit and build time
into the bundle as __BUILD__, and the Settings sidebar shows
"v0.1.0 · <short sha>" (full sha and build time on hover). The Docker
build has no git binary, so the commit is read from .git/HEAD and the
refs directly; .dockerignore now lets just those files through.
GIT_COMMIT in the environment overrides detection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
The add-page ghost is now a thin bar along the active page's free edge
instead of a full page-sized block. Pointer taps on it go through the
stage gesture handling, so touches are evaluated by the palm rejector
with the same 'draw' policy as writing, including being cut off if the
pen shows up mid-tap. The native click only handles keyboard activation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each grid cell is now the active page's size, aligned to its edges, so
the grid marks where neighbouring pages would sit. Falls back to A4 at
the origin when no page is active.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pages now get a crisp 1 screen-px outline instead of a blurred shadow,
and the canvas background shows faint world-anchored grid lines that
pan and zoom with the pages (spacing steps up ×4 when zoomed out).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hashed assets are served as immutable for a year. Before the nginx MIME
fix, the pdf.js worker was served as application/octet-stream, and the
browser kept that response. Each new service worker then copied it from
the HTTP cache into its offline cache, so PDFs still failed to render
after the server was fixed. Fetching with cache: 'reload' always takes
the current response from the server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL
Neighbouring pages now touch, so the tree reads like a grid. Rows with
pages of different sizes are still centred, so it isn't a strict grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL
nginx has no MIME type for .mjs, so the pdf.js worker was served as
application/octet-stream and browsers refused to load it. Every imported
PDF page then showed "Source PDF ... is not available", which looked like
missing files. Serve .mjs as text/javascript.
The page background now also tells the two failures apart: "not stored on
this device" when the source bytes are missing, and "could not display"
(with the error logged) when pdf.js fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL
Multi-stage build: Node builds the static PWA, nginx serves it with an SPA
fallback, long-lived caching for hashed assets, no-cache for the app shell
and service worker, and the correct type for the web manifest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL
After writing with the pen, fingers could no longer navigate:
- every touch was ignored for 5 s after any pen activity (and for as long
as a hover-capable pen stayed near the screen),
- after that, a one-finger drag on a page drew ink instead of panning,
- a one-finger drag on the background did nothing while an ink tool was on.
Once a pen has been used, fingers now only pan and zoom. The 5 s session
and hover lockouts only block finger ink; touches are still rejected while
the pen is down, for palm-sized contacts and within 150 ms of pen activity.
A finger drag off the page always pans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL
Blank pages added next to an existing page (via the "+" tile, Alt+Arrow
or the page menu) now copy that page's width and height, including
imported PDF pages, instead of always falling back to A4. The "+" preview
tile uses the same size so it matches the page it will create.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsorPV3JeJRoZ1mD1pdvtL