Files
marcandClaude Opus 5 4013e959d6 The same page on every screen
The text is laid out on a page of one fixed size and the sheet scaled to
the screen, so a phone breaks it exactly where a desktop does — twenty-one
lines to the page on both. The type size is computed from the page and the
line count rather than chosen.

A PDF's full screen is the browser window, not the screen, and its button
sits beside the zoom. "Recent Notes" is stamped by every write, not only by
a rename. And a PDF that will not open says which of the four reasons it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 13:23:03 +02:00

27 KiB

Changelog

What changed, newest first — and the digests of every image that went out, so it stays possible to tell what is actually running on the server. Digests are read back from the registry (docker buildx imagetools inspect), never taken from the output of docker push.

Kept short on purpose. A day's work usually went out as several images, and a round that repaired what an earlier round the same day had broken says nothing worth keeping on its own — so each day is one entry, describing where it ended up, with every digest of that day listed. Where the server image is named as unchanged in substance, the digest still moved: build.sh builds both images and a rebuild is not bit-identical.


2026-08-26 — The same page on every screen

Images — web sha256:e475c080…, server sha256:49a98c66…

The server image carries no change of its own; build.sh builds both.

Changed

  • Twenty-one lines to the page, and the same twenty-one everywhere. The text is now laid out on a page of one fixed size and the whole sheet is scaled to the screen, instead of being set in screen pixels. Set that way, a phone and a desktop disagreed about everything that matters — the same note broke into different lines on different pages depending on what you read it on, and a phone page held eighteen lines where a desktop held far more. The type size is computed from the page and the line count rather than chosen, so the count survives a change to either; a desktop therefore shows larger writing on a smaller-looking sheet, the way a ruled notebook app does.
  • Full screen for a PDF means the browser window, not the screen. Asking the browser for its own full screen took the tabs and the address bar with it, which was more than was wanted. The overlay over the app — the part that removes the sidebar, the list and the title bar — is the whole feature now. The button moved next to the zoom, where the other question about how much room the document gets already lives.

Fixed

  • "Recent Notes" is now sorted by when you last wrote in a note, not by when you last renamed one. The store always stamped every write; the lists on screen read the app's own copy of the notes, and writing in a note left that copy untouched — so the dashboard and the "Last changed" sort showed the order from the last reload.
  • A PDF that will not open says why. One message covered every failure and guessed at the cause ("offline?"), which was usually wrong and left a report of "it will not open on my phone" with nothing in it. The status now travels with the failure: a missing file, a server that refused, a server that could not be reached and a file that will not parse are four different messages.

2026-08-25 — Colour, a page you can see the edge of, and a document on its own

Images — web sha256:3e1b47e1…, server sha256:89e474cc… (earlier that day: web 783ccf92…, d7c66235…; server 2141f145…, 308ed1b7…)

The server changed earlier in the day for the bar's runtime configuration; this last pair carries everything below and no backend change of its own. The two entries under it are the same day's earlier rounds, kept apart because they are about different things.

Added

  • A marker pen and a colour for the letters, both under one button in the toolbar — two buttons would be two panels for one question. A highlight is a <mark>, the element that means highlighted, and marked text is painted with dark ink whatever the theme, the way a real marker leaves the writing alone. Markdown has no colour, so both travel as HTML like underline does, and both are read back — including a bare <mark>, which is what most other writers emit.
  • The PDF fills the screen. The button asks the browser for its own full screen and lays the document out over the app; either alone is half the job, and a browser that refuses the first still gets the second. Escape leaves, and the button stays visible while full — a way out that is only known is not a way out.
  • Dragging in from the left edge shows the sidebar on a phone or tablet, and dragging it back hides it. It must start at the edge, travel far enough to be meant, and be more sideways than downwards; without the first rule every sideways drag inside a note opens it, without the third every scroll does.

Changed

  • The dashboard leads with what you were last writing, the shelf of notebooks second — and "Recent" is sorted by time whatever order the notebooks are in. It is a question about time, and a hand-made arrangement is the wrong answer.
  • Tapping the blank part of a page starts writing there. The text is only as tall as what has been written, so most of a fresh page is beside the editable area rather than in it, and a tap down there landed on nothing at all: no caret, and on a phone no keyboard. The caret goes to the position nearest the tap rather than to the end.
  • The page has an edge on a phone. The paper runs to the edges of the screen there, but it was drawn with no border and no shadow, so nothing said where one page ended and the next began.

2026-08-25 — The bar that ties the apps together, and Ctrl over a PDF

Images — web sha256:783ccf92…, server sha256:2141f145… (earlier that day: web d7c66235…, server 308ed1b7…)

The server image changed for the bar's runtime configuration; the second pair of digests carries the PDF work as well, and no backend change of its own.

The bar

Added

  • A strip along the top that leads to the other apps. It is served by HoneyOS, so what it looks like and which apps it lists are settled there — InkNote only says where to find it. HONEYBAR_ORIGIN in the .env switches it on, empty leaves everything exactly as before.
  • GET /api/public/config hands out that origin, and the key that goes with it when HoneyOS lives on a different site. Runtime, not build time: an image is pulled ready-made, so a value baked in at build time could never be changed from the .env — and a service worker keeps whole responses, headers included, so anything frozen into index.html outlives the setting in the browser. Aether learned that one the hard way first.

Changed

  • The app now measures its height against the bar, in #root and in the four places that asked for the full window. With overflow: hidden on the body the surplus would not have scrolled — it would have been cut off at the bottom, silently. And #root padded itself around the phone's notch, which the bar now covers, so that gap would have been counted twice. Without a bar --honeybar-h is 0px and every one of these is what it was.

Reading a PDF

Added

  • Ctrl (or ⌘) and the wheel zoom the document, as in every other viewer, and a trackpad pinch with it — it arrives as the same event. The listener is registered by hand rather than through onWheel: React attaches wheel listeners passively, a passive listener may not call preventDefault, and without that the browser zooms the whole application instead, toolbar and all. The point under the pointer stays under the pointer: you are pointing at a word and asking for that to grow, not to be sent to the top of the page.

Fixed

  • Zooming with the buttons drifted. A zoom changes how tall the document is, so the scroll position means something else afterwards, and nothing was holding anything still — three presses of + and you were elsewhere in the file. They hold the page being read now, which is what "show me this page larger" asks for.

2026-08-22 — A bigger face, and the theme where the settings are

Images — web sha256:fe35b0e7…, server sha256:a557fb4d…

The server image changed: theme is now one of the settings an account keeps.

Changed

  • The profile picture fills the account button, 34px instead of 20 — a face at twenty pixels is a smudge — and the same size in the account panel.
  • Light or dark moved into Account → Profile, beside the typeface and how a chapter opens, which is the kind of thing it is. It used to be a sun in the corner of the sidebar: reached for by accident, and gone again on the next device. That corner now holds one button, the way into the account.
  • And it stays. The theme was pure screen state before, so every reload landed on dark whatever had been chosen. It belongs to the account now and follows you to the phone, with a copy in the browser so a reload does not flash the other one while the account is being fetched. The server takes dark or light and drops anything else, like every other setting written by a browser.

2026-08-21 — Asking for the picture properly

Images — web sha256:4310304d…, server sha256:7a486929…

The server image changed; the web image only because build.sh builds both.

Fixed

  • The profile picture never arrived, and two assumptions of mine were why. The picture was only looked for in the ID token — a provider may keep its token lean and hold the rest at its userinfo endpoint, which is what PocketID does, and that endpoint was never asked. And the image was then fetched with no credentials at all, while providers commonly keep avatars behind their own login. The endpoint is now asked when something is missing (and its answer trusted only if the sub matches the verified token), and the access token goes with the picture request — but only when the picture lives at the provider, because a token sent to a third party is a credential given away.
  • And it failed in silence, which is the part that actually cost the time: there was no way to see which of those two it was. Every outcome is now one line in the server log — stored, no claim, refused, not an image, too large, unreachable.

The test provider now behaves like a lean one: it leaves the picture out of the token and hands it over only against the access token, so the check passing is evidence for both new paths.


2026-08-21 — The face from the provider, and whose business a password is

Images — web sha256:e525b11d…, server sha256:95d56c46…

The server image changed: a table for profile pictures and the route that serves them.

  • The profile picture comes along. When the provider sends one — PocketID does — the server fetches it at each sign-in and keeps it, encrypted, in a table of its own rather than in users, where a blob would ride along with every SELECT * for the sake of one corner of the screen. It is served back through the API: nothing on the page talks to the provider, no third-party cookie has to survive for a face to appear, and the picture keeps working when the provider does not. It shows in the sidebar, in the account panel and beside the name in the administration list, and arrives with the next sign-in — the one moment this app speaks to the provider at all.
  • An account that signs in through the provider has no password tab, and that now includes one that had a password before it was linked — the usual case for whoever set this up, and the reason the tab was still there. The password it kept is the emergency door rather than a second thing for its owner to maintain: it is set by an administrator, and the account panel says so.

2026-08-18 — Which accounts belong to the provider

Images — web sha256:5fb51ed4…, server sha256:ce1f4649…

  • The administration list marks every account the provider can sign in, not only the ones it created. Those are two different things and the badge had been hung on the wrong one: an account that already existed and was linked — which is what happens to the account you were using when you first sign in this way — showed nothing at all. The row says which it is: an account with no password here says so, and the button offers to give it one rather than to reset something that does not exist, with a line explaining that this adds a second way in and takes nothing away.
  • The account panel says how you get in. The password tab is gone for an account that has none — it was already, and the server refuses that route too — but nothing said why. It now names the provider, and tells an account that has both that the password still works.

2026-08-18 — A second way in

Images — web sha256:232982cb…, server sha256:5ed577b1…

The server image changed: OpenID Connect, and an oidc_sub column on users.

  • Signing in through an identity provider, when one is configured — issuer, client id and secret, and the address this app is reached at; without them nothing about it appears anywhere. It is a second way to the same token the password login mints, which is why it fits in one file and needs no new dependency: Node imports a JWK itself, so checking an ID token is a signature and five claims.
  • The browser is handed a one-time ticket, never the token. The code is exchanged on the server, so the client secret stays there, and a token in a redirect URL would otherwise sit in the history, in the proxy's log and in the referrer of the next request. The ticket is good for a minute and for one use.
  • An account is made for somebody the app has not seen, and an account that already exists under that name is linked to the identity rather than duplicated — by subject from then on, so either side can be renamed. Never an administrator: those rights are granted in the app, and a group at the provider must not be a way around it.
  • Both doors stay open. An account made this way has no password here at all — the interface stops offering to change one and the login route refuses it as it refuses a wrong one, at the same speed — but an administrator can still set one, which is the way back in the day the provider is unreachable.
  • A suite that signs in at a provider of its own (e2e/oidc.mjs), which signs real RS256 tokens with a key InkNote fetches and checks. It holds down the four ways the journey must fail as well as the way it must work: a refusal, a replayed ticket, a token signed with an unpublished key, and one addressed to another client.

2026-08-17 — Quotes that hold more than a sentence, and one list of marks

Images — web sha256:30f67832…, server sha256:6ffe4b58…

No backend source changed this round.

  • A quote may hold whole blocks — and now has room for them. A heading, a list and a closing line inside a quote are ordinary blocks, but the rule that gives the writing its rhythm reaches only the editor's own children, so inside a quote everything stood jammed against its neighbour, with the items of the list further apart than the blocks around them.
  • The same divide when it leaves as Markdown. Blocks inside a quote were written on consecutive lines, which every other reader folds into one: the sentence after a quoted list became more of the last bullet. They are separated by a quoted blank line now — > on its own, since an empty line would end the quote — which is the same rule the top level has had since 11 August, and reading one back gives the blocks it was written with, quotes inside quotes included.
  • Marks are one list again. There were two copies, in the note dialog and the chapter dialog, and only one of them had ever been extended — so which marks existed depended on which dialog you happened to open. One grid, and with it twice as many marks, divided under headings now that there are too many for one wall of pictures.
  • A mark nobody thought of can simply be typed. The search box is a text field, which on a phone is exactly where the emoji keyboard lives, so anything pasted or typed into it is offered as a tile — one glyph with its modifiers, the same eight code points the server keeps.
  • The changelog is a third of its length. A day usually went out as several images, and a round that repaired what an earlier round the same day had broken reads as noise afterwards. One entry per day now, saying where it ended up, with every digest of that day listed.

2026-08-17 — A notebook arranged by hand

Images — web sha256:c5e460cf…, server sha256:ac3b0527… (earlier that day: web b7c9f8b7…, c1bcdcc4…, b8bc284a…, 18a68a2d…; server a0fd140a…, 6c9af404…, a66cc8eb…, febc4b93…)

The server gained one column: sort_mode on chapters.

Arrangement

  • A drop lands the row under the one it was released on, and only there. Dropping above a row and below its predecessor are the same move described twice, so every gap could be aimed at from two sides. A strip above the first row covers the one place a drop under something cannot reach.
  • A new note appears at the very top and nothing else moves. A row arriving without a position left every separator counting rows that were no longer above it, and a little more of the order came undone with each note. Every way in and every way out — new, restored, moved, binned, or a chapter removed — now renumbers the list.
  • A chapter is a place and a neighbour: its header takes a row dropped on it, its bottom edge is the gap under it. It is always in the user's own order, and its notes are dragged under the same switch as the list beside them.
  • A chapter can be given an order of its own — in the same dialog as its name, mark and colour. In any order but the hand-made one it loses its lines and handles, as the notebook does.
  • Two lines could not be put in one gap. They carry the same position, and the comparator claimed both directions at once for that case; the write that fixed it was then discarded by the "did anything change?" test, which sorted before comparing and so could not see a sequence.

Elsewhere

  • Move to… is divided under headings — the notebook itself, Chapters here, Other notebooks — each row wearing its chapter's mark or its notebook's cover. The headings are deliberately not shaped like entries.
  • A separator and a chapter each have one Edit instead of an entry per property, and the separator dialog draws the line as it will look.
  • A link wears the mark of the note it points at, in the text and in the @ menu, instead of one sheet-of-paper glyph for everything.
  • The mark, the title and the task-list box sit on the line they belong to. Boxes had been centred, which is not the same as letters being aligned; the mark and title share a baseline, the checkbox is centred on the capitals and sized in em, and the mark stands in the same column as the tools below it.
  • Grey letters ran down the bar while dragging a column edge. An absolutely positioned child in a cell of a border-collapse table sends Chromium down another painting path, and the cell's own text came out a second time, squeezed under the bar. The marker is never drawn now; the line is an inset shadow on the cells the column is made of.
  • Opening a note flashed a long narrow page, because the drawing stage was measured a frame after it was drawn. Measured before the first paint now.

2026-08-16 — Marks, lines, and a table that breaks

Images — web sha256:1f9f5141…, server sha256:3b2f8b44…

The server gained icon on notes and chapters, icon_color on notes, and colour and style on separators.

  • A note and a chapter can wear a mark, chosen from a short list searched by what a thing is for — "invoice", "house", "travel" — rather than from the system emoji keyboard, which is a different thing on every machine.
  • A separator takes its own colour and one of five shapes. Three faint grey lines through one notebook read as the same line repeated.
  • A long table lay across the gap between two sheets with a row cut in half. Tables are broken between their rows now, which is where a table breaks on paper; the spacer is a tr, since a div inside a tbody is not something the browser keeps.
  • A dialog closed in the middle of an edit when a selection was dragged past its edge: the click lands on the nearest common ancestor of press and release. All ten of them remember where the press began.
  • A chapter's colour could not be cleared — "None" sent null, and the update rode on COALESCE, which reads null as "leave it alone".
  • A table's header row is legible on the dark theme, and opening a notebook opens its first note on a wide screen.

2026-08-12 — Chapters, and a phone that works

Images — web sha256:c077f0e0…, server sha256:c13af5d5… (earlier that day: web 65799ecb…, 2041f103…, 95657aaf…, 3700247c…, 790ff73a…, 1217b81b…, 5ea846f3…, c1222665…; server a51b96ad…, ed0b9970…, b4db0725…, 181d77be…, 3303d3dc…, 06f94f43…)

The server gained a chapters table, chapter_id on notes, a color column and a reorder route for chapters, named positions on the note reorder, a cover_pattern column, and the per-account setting for how a chapter opens.

  • Chapters — folders inside a notebook. A note is in one or in none. Removing a chapter never removes what it held, and the dialog says so with the number affected. They sit at the same level as notes and separators: one counter, one grip, one kind of drag.
  • Two ways for a chapter to open, chosen per account: a further column beside the list, or unfolded under its own row. A phone fills the list instead.
  • A chapter is made complete in one dialog — name and colour together — and a chapter with no notes in it is visible at last; "nothing here" had been asked of the notes alone.
  • A notebook starts in the hand-made order, which is where its arrangement lives. The views across all notebooks keep "last changed": they have nowhere to keep an arrangement. Rearranging is a mode, switched on beside the sort menu.
  • A filed note came back out of its chapter by itself. The server had it right the whole time; the sync that follows rebuilds every note from the server's answer, and the function doing that never carried chapter_id.
  • Removing a chapter left you stuck in the confirmation — that one path set the dialog directly instead of going through the helper that takes it down. The suites had read the list straight through the open overlay and passed; they now assert the question is gone.
  • The order of notes could not be changed on a phone: the list used the HTML5 drag API, which mobile browsers never fire from touch. Pointer events and a grip per row. "Move to…" opened off the side of the screen and reacted only to hover; it opens on a tap and flips side when there is no room.
  • A note made offline lost everything written into it — its create reached the server, the server named it something else, and every queued edit went on addressing the name only that device knew. The queue is repointed with the database now.
  • The install button never appeared, even over HTTPS. beforeinstallprompt fires before React mounts, so it is caught in index.html; and the manifest is fetched without credentials unless the link says use-credentials, so behind an authenticating proxy the browser read the login page as the manifest.
  • The service worker could cache a proxy's login page as the application and then "work offline" by showing a login screen it could never get past. Redirected and unsuccessful responses are no longer cached.
  • A cover is a colour and a pattern, chosen in turn — every combination, drawn in CSS, with existing notebooks keeping exactly the cover they had.
  • The search box in a notebook is simply there, the margin-note column is half again as wide, and a submenu is no longer clipped away on a wide screen.

2026-08-11 — Writing on paper

Images — web sha256:3a181ff3…, server sha256:0832ee2b… (earlier that day: web b764a9d8…, dc804f7b…, ed11f034…, 3b040d99…; server 1648498f…)

The server gained a column and two routes for extracted PDF text.

  • You write on paper. Each page is a sheet of its own, centred, with a gap after it, and the text is broken against those sheets so no paragraph is cut in half. The breaks are decorations: nothing enters the document, reaches the server, or appears in an export. The toolbar says Page 2 of 4.
  • A drawing runs over as many pages as the note has, numbered the way the PDF reader numbers them, with one blank page past the end to start on. A drawing that lives on page one alone is still stored in the old shape.
  • A PDF is searchable on every device, not only the one that imported it: the importing device extracts the text and stores it beside the file, encrypted. The server still has no PDF library.
  • The typeface is a per-account setting — serif, sans or mono — so it follows you to the phone. Text alignment likewise, with its own extension so the schema stays in files this project owns; non-left blocks travel as <div align="…"> in Markdown.
  • A note with a drawing could not be typed in at all: the box wrapping each page's ink layer lay over the text and swallowed every click.
  • A drawing exported in a ZIP was lost on re-import — the exporter had written drawings/… since the interface went English, the importer still looked for zeichnungen/….
  • "Start writing..." was printed at the foot of every page with a break after it: the placeholder styles [contenteditable]:empty::before, and the spacer holding a gap open is exactly that.
  • The Markdown exporter began putting a blank line between top-level blocks, and the margins narrowed from 73 to about 80 characters.

2026-08-10 — Notes on passages

  • Pinned notes in a PDF. A remark is attached to a passage and shown in the margin beside the page, never written on top of it; on a phone it opens as a bottom sheet. Such a remark can be linked from the notes panel and written about at length.
  • The server stored an overlay holding only pinned notes as NULL, throwing every one of them away, because it counted strokes and nothing else.

2026-08-09 — Arranging a notebook

  • Sorting per notebook, remembered on the notebook, including an arrangement made by hand — which comes back intact after filtering.
  • Separators, Markdown import (single files or a ZIP this app exported), and moving notes between notebooks.
  • Switching to Draw shrank the page from 733px to 460px: the editor was a flex item at flex: 0 1 auto, so it was only as wide as its widest row — the toolbar, which loses a dozen buttons in Draw.

2026-08-08 — PDFs

  • Import a PDF, read it, select its text, draw on each page, and export it with the drawings written in. Notes written beside a PDF.
  • Uploaded files are encrypted at rest, like note bodies already were, and stored as deduplicated, content-addressed attachments; unused ones are freed, and versions are kept by count rather than by age.
  • Export as Markdown or PDF; the browser suites moved into the repository.
  • Vector clocks were never read — replaced with a conflict check that works.
  • Public sign-up removed: an administrator creates the accounts.

Earlier

The interface became English throughout, and search inside a notebook replaced the sidebar's global search (2026-08-11). Before that: the TipTap editor, version history, full-text search and attachments (2026-08-07); drawing sync with WebP/JPEG support (2026-06-21); the first syncing alpha and PWA (2026-02-18).