What’s new · version 1.11.1
What’s new in Skysa Notes
Every release of the app, newest first, in the words of the people who made it. The app is open source, so this is the project’s own release history; the hosted app at notes.skysa.com runs the version at the top. For what the app does today, read the features tour.
1.11.1
Fixes and small changes
Text copied from the rich editor no longer has
 at the end of its lines. A space at the end of a line, such as the one a phone's keyboard leaves after the last word before Enter, is left out of what is copied; the note keeps it.The fill window times its guard against a stray press from the moment its logins are drawn, rather than after the browser has painted them, so a press that lands on logins just drawn is not taken as one meant for them.
1.11.0
A password has a Site: the web address it fills in on, and the sites under it, encrypted with the username and password. It is a field after the label, which says when what is in it is not a web address. An item whose label is a web address is offered "Use github.com as the site" while its Site is empty. Opened, an item shows its Site's address with Open, in place of its label's.
The fill window, the app's own page at
/fill, and the bookmarklet that opens it over a site's sign-in page: what they say to each other, and what the window says while it waits for the page, cannot reach one, or is opened by a bookmarklet of another version. The window takes the page to be the origin the browser stamped on its message, never what the page says, and answers that origin alone.Fill on sites: the fill window unlocks each source's vault as the app does, lists the passwords whose Site fills in on the page, and fills one in (or copies it, where the page has no fields). It closes once it has filled, and when its lock locks.
Passwords: Fill on sites, a button in the list's header that sets up the bookmarklet. Drag its link to your bookmarks bar, or copy it to make a bookmark on a phone. The dialog also says where it does not work, and that the fill window never asks for your recovery key.
Fill on sites remembers a password typed on the site. The fill window offers to save it, with the page as its Site, or, where it is the new password of the one login saved there for that username, to update that login. An Update keeps the password it replaces, and Passwords shows it as a Previous password, with Show and Copy.
Core knows which site a password is for, and whether it fills a page:
siteOfreads an item's Site,pageSitethe origin a page's message came from, andfillsOnsays whether the one fills the other — the same host or one under it, on its port, neverhttps:tohttp:. An item's payload keeps its sites inurlsand the passwords an Update replaced inpreviousPasswords(vaultUrls,withVaultUrls,vaultPrevious,withReplacedPassword). Nothing uses them yet.
1.10.3
Fixes and small changes
A file's chip has a little room round it, in a note and on a scratchpad card. Chips put in together stand as far apart as chips with a space between, the rows of a run of chips that wraps no longer touch, and a chip that starts its line has no room before it, so it lines up with the words above.
1.10.2
Fixes and small changes
The vault's menu items on the gear and a source's
⋯say "Vault" and "Set up vault", without an ellipsis, as every other menu item does.
1.10.1
Fixes and small changes
Search and the command palette: the row under the pointer is lit, as in the notes list. It is the same highlight the arrow keys move, so only one row is ever lit and Enter opens it.
1.10.0
Search: a press on the empty search field lists the notes opened lately on this device, newest first, from every source, leaving out the note on screen. Scratch cards and password items are not listed.
Fixes and small changes
Passwords: the filter row is as tall as the column headings and the Scratchpad row, so the rule under it lines up with the one under Notebooks.
1.9.0
Passwords: search finds a password by its label while Passwords is shown, Generate makes a password in Edit (length and symbols remembered on the device), and a label that is a web address opens.
Ground work for passwords in the vault. A source's
.vault/folder, where the vault's passwords will be kept one note each, now syncs as the scratchpad's folder does, while staying out of the notebooks and out of search. Each device reads its storage once more after updating, as "Re-scan" does, so that items another device made are picked up; it re-reads nothing it already holds.@skysa/coreadds the item format (a label in plain text, and the username, password and notes as JSON in one age block) and an unbiased password generator.Passwords in the vault. Turn on "Passwords" in a source's Vault dialog, and a Passwords row appears under the notebooks on that device. It lists the source's passwords by label, with a filter and
+for a new one. A password has a label, which is not encrypted, and a username, a password and notes, which are encrypted with the vault's key as they are typed. Copy puts the username or the password on the clipboard, Show reveals the password, and Edit and Delete work as they do for a note, with Undo after a delete.The Secrets feature is now called Vault. The menu item on the gear and a source's
⋯says "Vault…", or "Set up vault…" until the source has one, the dialog it opens is called "Vault", and a note's warning opens it with "Set up vault" or "Open Vault". Setting up, unlocking, the recovery key's printed page ("Vault recovery key for …") and a new device-lock passkey ("Unlocks the vault on this device: …") say the vault too; a passkey made before keeps its old name and still works. A secret in a note is still a secret, and nothing in anyone's notes or keys changes.
1.8.0
A device that unlocks secrets with its own lock (fingerprint, face or screen lock) asks for it straight away. Opening a secret while the device is locked shows the device's prompt with no Unlock dialog to tap through first, and the Secrets dialog's Unlock does the same. If the prompt is cancelled, the Unlock dialog appears with Try again as its answer, Cancel beside it, and "Use the recovery key" on a line of its own above them.
Fixes and small changes
While a new recovery key is shown, the page is titled "Recovery key for secrets in {source}", so saving its print as a PDF names the file after the key rather than after the note open behind the dialog.
1.7.0
Every dialog is drawn one way, redrawn after trying them:
- A dialog you can leave has a ✕ in its corner, and a press on the page around it leaves it too, as Escape does. Once nothing is being asked, the ✕ is the only way out: there is no Close button in the row.
- The answer sits alone on the right of the button row; Cancel and anything else are on the left.
- A dialog is never taller than the window. Its title and buttons stay put while what is between them scrolls. On a phone, the field being typed in stays in sight above the keyboard.
- On a phone, every dialog takes the window's width, so a row of three buttons fits on one line.
- A notebook's Attached files scrolls as a whole rather than in a box of its own.
- A printed recovery key has a margin on every side again, rather than starting at the paper's left edge.
The secrets dialog's steps are laid out so the button row holds only the way out and the answer:
- On a device that is set up, "Change how this device unlocks" and "Replace this source's key" are a list in the dialog rather than buttons crowding its row. The row has Remove from this device (and Lock now, while unlocked) on the left, and Unlock on the right.
- Checking a new recovery key asks for its two groups in numbered cells side by side, drawn like the cells the whole key is typed into. Typing goes on from the first to the second as it fills, and Backspace in an empty cell goes back.
- Print and Copy are under the new key, and Scan QR code is under the cells a key is typed into.
- When secrets in the source were made with another key, "Use that key instead" and "Add it as well" are a list in the dialog, with Continue in the row.
- The recovery key's QR code lines up with its groups.
1.6.0
The recovery key now comes with a QR code, on screen and on the printed page. Wherever the key is asked for, Scan QR code reads it with the camera instead of typing it. The frames are read on the device, and the camera is let go of as soon as a key is read or the scan is cancelled. On a phone, the key's groups now sit three to a row, so none is cut off at the dialog's edge.
A secret can be given a label: a Label field over its words in the panel. The closed secret shows the label in place of "Secret", as do scratchpad cards, and search finds the note by it. The label is plain text on the secret's first line in the file (
```age Wi-Fi password), not encrypted, and the field says so. With no label, it still says "Secret". A version before this one opens a note with a labelled secret in raw mode, losing nothing.
Fixes and small changes
Search no longer finds a note by a secret's encrypted text. A short search could match a run of a secret's base64 by chance, or the
ageits fence opens with; the index now holds each secret as its label alone, so a note is still found by its secrets' labels.withoutArmor(core) gives a note's body that way.A secret selected in the rich editor shows Delete secret at the end of its line, as a selected picture shows Remove from note, instead of the formatting bar, which had nothing to format in it.
Where a secret cannot be made or opened because this device or the source still needs setting up, the warning now has a button that opens the source's Secrets dialog over the note ("Set up secrets" or "Open Secrets"), instead of saying where in the source's menu to find it. "Use the recovery key" in the unlock over a note opens the dialog directly.
Choosing how secrets unlock on a phone: the four kinds (Pattern, PIN, Password, This device's lock) now sit two by two where one row would squeeze them, instead of crushing into a row.
1.5.0
Every dialog now works the same way. Its buttons are one row at the end: Cancel (or Close) first, and the answer last, on the right. The first field has the focus when there is one, and the answer when there is not. Escape is Cancel everywhere, including the secret panel in a note, where it used to be Done; Mod+Enter is Done there. The storage panel's questions, and Delete on a file in a notebook's attached files, follow the same rules. Disconnect and "Discard for good" are now drawn as the warnings they are.
Fixes and small changes
Secrets: a recovery key is copied in its groups, as it is shown, and is typed into twelve numbered fields, one for each group. Each field goes on to the next as it fills, and a key pasted whole fills them all. The printed key has a page margin of its own.
The rich editor's toolbar has "Insert secret", a lock after the paperclip, which writes a new secret as "Secret" in the slash menu does. On a narrow toolbar it is in More tools.
1.4.0
Secrets: a secret made with a key this device does not have offers "Add its key". The key is checked against that secret, then kept on this device beside the source's own, and the secret opens.
Secrets: a device whose source key was replaced on another device asks for the new key when it meets a secret made with it, and keeps it as the source's key. A device that still holds the key from before encrypts what that key opens to the new one as it is unlocked, so a replacement that stopped short, or a secret made offline meanwhile, catches up.
Secrets: a device can unlock a source's secrets with its own lock — fingerprint, face or screen lock — where the browser supports it. It uses a passkey that only ever opens this device's copy of the keys, and one the app stops using is let go of where the browser allows it.
Secrets in the raw editor. A secret's fence shows its encrypted text as it is written, with Reveal at the end of its first line (or Alt+Enter inside it), which opens the same panel as in the rich editor, under the fence. What you type there is encrypted into the fence as you type it, and one undo takes back what was typed. "Insert secret" in the palette works in raw mode too, and writes a new fence after the paragraph, heading or code block you are in, inside its list or quote; Cancel leaves the note exactly as it was. Find in raw mode passes over a secret's fence, so a search never lands in it and Replace never changes it. A scratchpad card shows "Secret" where its note has one, and a scratch note holding only a secret or only a picture is no longer deleted when the "Take a note" box closes. A sync that changes the note elsewhere no longer moves the cursor or takes the focus from an open secret. In
core,previewBlocksgives a secret a line of its own that carries a secret's mark, including one cut short by a preview's length.Secrets: "Replace key…" in a source's Secrets dialog makes a new key, or takes one you have, and encrypts the secrets made with the old key to it.
Secrets in the rich editor. A secret shows as a closed block that says "Secret" and offers Reveal, which unlocks the device if it is locked and opens the words in a panel under the block, with Copy, Cancel and Done. What you type there is encrypted into the note as you type it, so closing the note, switching mode, the app locking or a sync never finds words that are not saved; Done closes the panel, and Cancel puts the secret back as it was. "Secret" in the slash menu, and "Insert secret" in the palette, write a new one. When the app locks, every open secret closes. A secret copied and pasted, or pasted as markdown, is a secret still. In
core, sealing and opening an age file no longer wait:encrypt,decrypt,opens,sealSecret,openSecretandopensSecretanswer at once, with HKDF and HMAC from@noble/hashes."Set up secrets…", in a source's gear and its
⋯, sets the source up for secrets: a new recovery key, shown once with Print and Copy and checked by typing two of its groups back, or a key you already have. Each device then unlocks with a pattern, a PIN or a password of its own, which can be changed later without the recovery key. A source already set up asks for its recovery key instead, and a key that would leave existing secrets locked says so first.
Fixes and small changes
A secret in a note, an
ageblock holding age's armor, is left out of the note list's preview, the scratchpad's cards and search, which would otherwise show its base64. The age format itself (X25519 keys, the header, the payload and the armor) is now in core, checked against age's published test vectors.
1.3.0
A notebook can be pinned to the bottom of the notebooks beside it, from its menu, as it can be pinned to the top. A thin line sets the notebooks pinned to the bottom apart from the rest. It is kept on this device, and a notebook is pinned to one end at a time.
1.2.7
Fixes and small changes
The sync status at the foot of the sidebar stays one line and one height: its words are shorter ("Checking…", "40 changes found", "120 of 1,000 sent"), and where the sidebar is too narrow for them they end in "…" instead of wrapping. The progress bar of a long sync no longer pushes the status up; it sits in the space under it.
1.2.6
Fixes and small changes
A file added to the clipboard keeps its name: "Q3 Report.pdf" is shown and saved as "Q3 Report.pdf", not "q3-report.pdf". Only characters a storage provider or a disk refuses are replaced, and the extension is lowercased. Items already on the clipboard keep the names they have.
With the clipboard shown, a file dropped anywhere in the window is added to it, not only on the clipboard itself, and in a compact window too, where the clipboard is in the source menu. A file dropped on a note's text still goes into the note. While files are dragged where a drop would add them, the window says so.
A note with html in it shows characters such as ’ and “ where the file has
’and“, in the editor and in the note list. Notes exported from Google Keep showed them as written. The file itself is unchanged.The sync status at the foot of the sidebar no longer names the source, so it stays on one line as it changes: "Synced 12:04" instead of "Dropbox · Synced 12:04". The tab above still says which source it is. The status can now be clicked whatever it says, and opens a dialog naming the source and its account.
The notice after deleting a note now goes away as soon as you click or tap anywhere else, and closes with an × in its corner, so Undo is the one button on it. A long name with no spaces in it, such as a scratch note that is only a link, now wraps inside the notice instead of pushing its buttons out of the box. How an import went is now said in a notice over the page, like the others, instead of under the storage panel or in the sources menu.
A note edited in Google Docs keeps its title, its dates and its place in the app. Docs turns the details at the top of a note into a heading, and the app showed that heading as the note's title, with the details in the note itself. It now reads them back as they were, and tidies the file the next time you edit the note.
1.2.5
Fixes and small changes
Google Drive: "Checking for changes…" no longer takes a long time after a device has been away while the account was busy. When the check would take longer than a full re-scan, it re-scans instead, which is what pausing and re-scanning by hand did. Each page of the check also takes about half as long as before.
Every provider: a re-scan can no longer delete a note another device made. Before, this could happen when the other device had put its note at the name of one you had just deleted here.
1.2.4
Fixes and small changes
"A new version is available" → Reload works while the app is syncing. It used to seem to do nothing, sometimes for as long as five minutes. It now pauses syncing in every open window of the app for a moment, then switches to the new version and reloads, and says "Updating…" while it does. Updating to this version may still need a quiet moment: if Reload seems stuck, close every window of the app and open it again.
1.2.3
Fixes and small changes
Google Drive: a notebook moved, renamed or deleted on one device no longer stays behind as an empty notebook on your other devices, or comes back after a re-scan. Its old folder on Drive is marked as left instead of being kept as a notebook, and nothing in it is touched, so anything you put there yourself through Drive is safe. Making a notebook with that name again brings it back. Folders left behind before this update still show; remove them once from the app, or from drive.google.com.
1.2.2
Fixes and small changes
A menu item that can't be chosen right now looks greyed out. Sync now and Re-scan from scratch were already turned off while a sync is running, but they looked like every other item and were highlighted under the pointer, so a tap on one seemed to do nothing. They now show as unavailable, as a toolbar's buttons do.
A sync waiting for another tab or window of the app to finish syncing the same source now says "Waiting for another tab" after a few seconds, as it already did while waiting on that tab for the notes on this device, instead of a plain "Syncing…".
Syncing after notes or notebooks were moved on another device is much faster on a large library: deciding where each moved note goes no longer reads every note on the device, once per note.
You can see what a sync is doing, and pause it. While the storage panel's status says it is syncing, tap the status to open a window showing what it is doing, how far it has got and the file it is on, with Pause syncing. Paused, nothing syncs on this device: your edits are kept here and sent once you choose Resume syncing or Sync now, re-scan, switch to another source and back, or open the app again. The status says "Paused" meanwhile.
While a sync reads what has changed on another device, the status says so. It used to say just "Syncing…" until it started counting notes, which after a lot of notebooks were moved on another device could take minutes. Now the line says "Checking for changes…" from the start, and once it has found twenty or more changed notes, how many so far, with a bar. A re-scan says "Looking for notes…" before it has found any. For other code using
@skysa/core: thereceivingprogress now carrieslisting, which is true while the pages of changes are still being read.Google Drive: a sync after notebooks were moved or made on another device reads what is in the new folders four at a time, a level at a time, instead of one folder after another, so it gets to "Checking for changes" counts and downloads sooner.
1.2.1
Fixes and small changes
On a phone, the bar at the top no longer names what the open dropdown is there to choose. With the sources open, the notebook and the note are hidden; with the notebooks open, the note is (or a scratchpad card's name). They fade out as the dropdown opens and come back as it shuts, and the bar keeps their room, so nothing moves along it.
1.2.0
Importing is now one item in the storage menu, "Import notes". It asks whether to choose a folder or files (notes and ZIP files), and replaces the separate "Import a folder" and "Import files" items. While the scratchpad is shown, "Import to scratchpad" sits beside it. That brings in the notes at the top of the folder or files you choose as scratchpad cards, along with the pictures and files those notes link to. Notes inside folders are left out, and the question before importing names them, so you can bring them in with "Import notes" instead, where their folders become notebooks. A file a note links to inside a folder comes too, into the same folder in the scratchpad, so the link keeps working. A ZIP of a single folder counts as that folder.
1.1.2
Fixes and small changes
A note with no name now goes by its first words wherever one line has to say which note it is. On a phone, the note's dropdown in the bar at the top, and a scratchpad card's name there, show the start of the note's first line, cut to about 40 characters, instead of "Untitled"; the bar catches up with what is typed when the note is saved. So does the notice after a note is deleted ("Deleted “Pick up the parcel”."), and the one after Undo brings it back to another storage account. A note with no text is still "Untitled", and a scratchpad card with no text still shows no name in the bar.
1.1.1
Fixes and small changes
The chip under a link no longer turns see-through when the pointer is over it. Before, the words of the note behind it showed through the chip on hover.
1.1.0
Links in a note can now be opened. When the cursor is in a link, a chip under it shows where the link goes, and pressing the chip opens it. With a mouse or trackpad, the chip also says you can ⌘-click a link (Ctrl-click on Windows and Linux) to open it straight away. Alt+Enter opens the link the cursor is in. This works in both rich text and Markdown mode, and a bare web address,
www.address or e-mail address in Markdown mode counts as a link, as it already did in rich text. On a scratchpad card, pressing a link's words opens the link instead of the card. Links open in a new browser window, not inside the installed app. Only web, e-mail and phone links open. A link to another note does not open yet.
1.0.11
Fixes and small changes
A notebook whose notes are all in notebooks inside it now opens like any other notebook. On a phone, tapping one shows its notes to choose from. Before, it opened straight onto a note from one of the notebooks inside it, with no list. When it opens on a note by itself, it picks the first one its list shows, so pinned notebooks and pinned notes come first. Before, it started a new blank note instead. A note you had open last in that notebook is still the one it opens on.
1.0.10
Fixes and small changes
The brand's documentation now says what colours the installed app's navigation bar on Android, which 1.0.9 got wrong. On Android 15 and later it is
colors.backgroundinbrand.json, the splash screen's colour, in both light and dark mode; the light theme'sbrandcolours it only on Android 14 and older. To change the bar on current phones, changebackground: a light one is a pale strip under the app in dark mode, and the lightbrand's value gives one bar on every phone. Nothing changes in the app itself.
1.0.9
Fixes and small changes
On Android, the installed app's navigation bar is now the brand's colour (the light theme's
brandinbrand.json), in both light and dark mode. Since 1.0.6 it was the splash background, which was a pale bar under the app in dark mode, as the light title-bar colour had been before. A phone picks this up once Android updates the installed app, which can take a day or more;chrome://webapks→ "Update" makes it sooner.
1.0.8
Fixes and small changes
On a phone, a scratch card left open under the notebooks dropdown no longer flashes over the dropdowns when Back or Forward comes back to it or leaves it, or when a notebook is chosen there. It comes and goes under the dropdown, as a note does.
On a phone, the notebook and the note open are no longer lit in their dropdowns, where what they mark is out of sight under the panel. And a scratch card closed by Back, then opened again from a search answer, now closes to the scratchpad from its Close, rather than going back to wherever the search was made.
1.0.7
Fixes and small changes
The app open in a background browser tab no longer stops the installed app, or another tab, from opening notes. A tab the browser had frozen held the device's notes database each time another tab saved, so notes would not open and sync said "Syncing…" until that tab was closed. A frozen tab now lets go of the database and picks it up again when it is next used.
A note that is slow to open now says "Opening the note…" instead of "Select a note". A scratchpad card pressed says the same on the card until its note is ready, then opens. Before, a note held up by another tab of the app looked as if nothing had been chosen, and a card pressed did nothing at all.
When syncing is held up because another tab or window of the app is still using the notes on the device, the status line now says "Waiting for another tab", and its tooltip says to close the app's other tabs if it does not go away. Before, it said "Syncing…" for as long as it lasted, which looked like a slow network.
1.0.6
Fixes and small changes
On Android, the installed app no longer shows a pale bar along the bottom of the screen in dark mode. The bar under the app now follows the phone's light or dark theme. Installed copies pick this up when Chrome next refreshes the app, without reinstalling.
1.0.5
Fixes and small changes
On a phone, tapping low in a note no longer leaves the cursor under the keyboard that the tap brings up: the note scrolls so the line being typed shows just above it, in rich text and Markdown alike.
Selecting several paragraphs in rich text and pressing a list button now makes a list with one item per paragraph, rather than one item holding them all. One Undo takes it back.
1.0.4
Fixes and small changes
On a phone, Back no longer jumps from a note to the notebook open before it, which on Android looked as if the end of the swipe had tapped a notebook. Back now goes back through the dropdowns: from a note to the notes it was chosen from, then to the notebooks, then to the note before. With a dropdown open, Back shuts it.
1.0.3
Fixes and small changes
On a phone, the compact bar no longer shortens a notebook's or a note's name that has room to show whole —
Bugswas showing asB…s— and a long name chosen after another is shortened to the room it has, not cut as though it were as long as the one before.On an iPhone, the toolbar under a scratch note is no longer stretched tall: opened over the whole screen, its buttons sit in one row clear of the home indicator, and in the box on the scratchpad and in the dialog on a wide screen it no longer leaves room for one.
1.0.2
Fixes and small changes
On a phone or tablet, a note's title no longer has a ring drawn round it while it is being typed in, in the scratchpad or in a notebook.
1.0.1
Fixes and small changes
Scratch notes made on another device before this one had the scratchpad now arrive: each source reads its storage once more after the update, downloading only what this device is missing.
1.0.0
Version 1.0. An instance on 0.21 moves to it with nothing to do: no setting, secret, binding or database migration is new. From here on the number keeps semver's promise to the person running an instance — a major version is one they have to act on, and a minor or patch version never is.
A scratchpad for quick notes, for every source, the device's own notes included. It is shown for every source until it is hidden from the storage menu ("Hide scratchpad"), and a source connected after one was hidden starts hidden. It sits above the notebooks: a "Take a note…" box, and the notes taken as cards, the pinned first. A note there has the basic tools — bold, italic, lists, a link — and can have files attached. A card can be pinned and given a colour, which are kept in the note's file and so reach every device. A card shows its note as the note is set — bold, lists, boxes, headings, its pictures and files — and opens from a press anywhere on it but its tools. It opens in a dialog, or the whole window on a phone, growing out of the card and going back into it as it closes, and the "Take a note…" box eases open and shut. "Move to notebook" makes it a full note, named first if it has no name. Scratch notes are ordinary notes in a hidden
.scratchpadfolder, synced as any note is, and search finds them while the scratchpad is shown.Labels are spelled the American way where they were not: "Canceling…" while an import is called off, and "Connecting storage was canceled."
On a phone, or in any window too narrow for three columns, the source dropdown now ends with the storage gear, as the sidebar does on a wide screen. It has what the source's ⋯ has, and is there before the device has a note to give it a row.
When the formatting toolbar is short of room, its More tools button now sits straight after the last tool it shows, instead of out at the bar's far end with a gap between them.
Fixes and small changes
The storage panel and the question before connecting now write their counts with a thousands separator, so "1,028 files" rather than "1028 files".
Scratch cards draw their pictures from small copies, and hold each picture's room before it arrives, so the wall stops rearranging as pictures come in.
The clipboard panel draws its pictures from small thumbnails made on the device, rather than decoding each full picture.
A source let go of on this device no longer has all its notes read each time another source syncs.
The editors' words now come from the app's catalog. A size or a count of a thousand or more in what the editor says about a picture or a file is now written with a thousands separator: "1,024 MB", "1,028 files".
The words the app writes into the user's files where a name gives none, the alt text of a pasted picture, the link text of a file with no name and the folder of a notebook given no usable name, now come from the app's catalog.
@skysa/coretakes them from its caller (attachmentLabel'swords,sanitizeFolderName'sunnamed). A note with no name staysuntitled.mdandUNTITLED_TITLEin every language, and is shown in the catalog's words (notes.untitled).ClipName.labelis nowundefinedfor a pasted text or a picture with no name of its own, andfileKindLabel, which nothing used, is gone. The English is unchanged.A scratchpad of hundreds of cards no longer reads every card's frontmatter again each time it is drawn.
The find bar's match count and the number of files in something shared to the app are now written with a thousands separator, as in "1,028 of 2,048".
A sync run that brings nothing no longer reads every note again for the notebooks, the note list and the loose-notes count.
Clicking back to a notebook soon after leaving it opens the note left open there, rather than its newest, on a busy device. Before, the newest note could open and was then remembered in its place.
The words of importing, downloading, the clipboard and the scratchpad come from the app's catalog, and a count of a thousand or more in them is now written with a separator ("and 1,028 more", "without 1,028 files") where it was not.
A notebook of more than 150 notes draws only the rows near the screen, so a long list opens and scrolls faster on a phone.
The note list draws a row again only when what that row shows changes, so saving one note in a notebook of a thousand no longer redraws the other 999 rows.
The number of notes and files in the question asked before a notebook is deleted, and a file's size in a notebook's attached files and the clipboard, are now written with a thousands separator: "1,028 notes", "1,000 KB".
A note's pictures are drawn from smaller copies made on the device, as wide as the editor draws them, one at a time; a picture's box keeps its size while it loads, and a copy stands in for a picture that cannot be downloaded now.
A picture beside a note, selected, has Open full size and Download on its bar, as a file's chip has; Enter opens it, and Tab reaches its bar.
A notebook or scratchpad with more than 400 notes no longer parses every note's preview again each time its list is drawn, which it was doing on every autosave of the note beside it. The cache of previews now grows with the lists on screen, and a scratchpad card's text is parsed once instead of twice.
Importing a library, or pulling one into a device for the first time, reads its notes a little at a time before writing them, so a phone no longer stops answering until a few thousand notes are in, and the rest of the app can read the notes while they are being read.
The scratchpad places its cards in one pass and draws a card again only when what that card shows changes, so opening a card on a large scratchpad takes a fifth less work.
Opening a scratchpad of hundreds of cards no longer holds the page still while every card's height is guessed: the cards are guessed a few at a time from the top, and the wall grows as they are.
A scratchpad of hundreds of cards shows its first cards sooner: to find the pinned ones, only the frontmatter that says
pinnedis read.A scratchpad of more than 100 cards draws only the cards near the screen, so it opens and scrolls faster on a phone.
Search builds its index as the field takes the cursor, a little at a time, so typing into it on a phone no longer stalls: the first letter is answered without a second's wait, and each letter goes into the field before its matches are drawn.
A picture shown in a note and on its scratch card at once is held in memory once instead of twice, and a scratchpad of picture cards watches its source's files with one query instead of one per card.
The sidebar builds its tree in one pass and draws a notebook's row again only when that row changes, so an autosave, a sync run or a drag over the notebooks no longer redraws every row of a large library.
Counts in the source tabs, the disconnect and move questions, a disconnected source's panel and the first import's progress are now written with thousands separators in English digits ("1,240 found so far"), where they had no separator or followed the browser's language.
Two tabs open on different notebooks of a source no longer write where the user is back and forth for as long as both are open.
0.21.2
Fixes and small changes
On a phone, or in any window too narrow for three columns, a long note name in the note dropdown is now shortened in the middle, as a notebook's path is, rather than at its end:
Forecas…ond half, so two notes that start alike can still be told apart.
0.21.1
Fixes and small changes
On a phone, or in any window too narrow for three columns, the notebook dropdown now shows the notebook's full path, as the heading over the notes does, instead of its name alone. When the bar is short of room the path is shortened in the middle: the notebooks it is in give way first (
Work/Proj…/Q3), and the notebook's own name last.
0.21.0
A notebook with notebooks inside it now lists their notes too. Its own notes come first, then each inner notebook's notes under that notebook's name, in the order the sidebar shows them. Choosing one of those notes opens the notebook it is in, with the note. A notebook's count in the sidebar is every note inside it, at every depth, whether it is open or shut.
0.20.3
Fixes and small changes
Resizing the window no longer crashes the note at some widths, around 1080px on a desktop. At those widths the formatting toolbar could not settle on which buttons fit, moved one into its "More tools" menu and back for ever, and React gave up on the page. The toolbar now settles at once at every width.
0.20.2
Fixes and small changes
While the clipboard holds anything it is highlighted, with a tint of the brand's colour, an edge in that colour and a soft glow around it, in place of the shading inside its edges. It stays the full width of the sidebar. The edge thickens inward, so the items never move, and the glow grows while files are dragged over the window, and most with them over the clipboard.
The app moves more smoothly, and all of a piece. Hover, selection and anything switched on fade in rather than blink. Menus open out of the button that opened them, upward where they rise from the foot of the sidebar or of a phone's note. Dialogs and the command palette rise in as the page behind them dims, and notices slide up from the bottom. A notebook's notebooks, new clipboard items and the find bar fade in. With reduced motion turned on in the system settings, nothing moves and only colours fade.
0.20.1
Fixes and small changes
The clipboard is easier to read and to drop things on. It has a section of its own above the storage status, ruled off from it across the sidebar. It glows a little in the brand's colour while it holds anything. Dragging files anywhere over the window shows "Drop here to add to clipboard", and the glow brightens with the files over it. An item still being sent is greyed out under a small spinner until it is up. "Copied" and "Saved" now show over the item you pressed, and items show a hover state. While the clipboard is empty, the text that explains it now sits in a dotted box and says to click or tap an item to use it.
0.20.0
The browser's Back and Forward buttons now move between the notes and notebooks you opened, including back into another source after switching to it. The address bar names the open note by its notebooks and its name, as lowercase words joined by hyphens, such as
/#/work-stuff/projects/q3-plan. A link or bookmark to a note opens it, including one typed with the names as they are, and the address follows the note when it is renamed. The page title says where you are:Work > Projects > Q3 plan. Notebook and note names stay in the part of the address after#, which the browser never sends to the server. Links from earlier versions, which named a note by?note=, now open wherever you last were.
Fixes and small changes
With instant updates between devices turned on, a change made on another device no longer sometimes waits a minute to arrive. Google Drive takes a few seconds to report a change, and a device used to ask it once and then wait for the next minute's sync. It now looks again a few seconds later. This was most noticeable with the clipboard: an item added, or several removed one after another, could take a minute to show on your other devices. An item removed from the clipboard on another device also no longer stays on this one until the next change to the clipboard.
0.19.0
While the app runs in a browser tab, a banner under the top bar offers to install it. In Chrome and Edge, on a desktop or Android, its Install button opens the browser's install prompt. On an iPhone or iPad it says to tap Share, then Add to Home Screen, and in Safari on a Mac it says to choose File › Add to Dock. Browsers that cannot install the app are not offered it. The banner goes once the app is installed or opened as an installed app, and dismissing it hides it for good on that device.
The installed app is a share target on Android and ChromeOS. Text, links and files shared to it from the system's share sheet go on the clipboard of the source showing, once the user says so. The app always asks, naming what came, and offers to show the clipboard where it is hidden. Files over 25 MB are left out and named. Notes kept on this device only, and a source no longer connected, have no clipboard, and the app says so. The service worker answers the share itself and keeps it on the device until the page has asked; nothing shared is sent to the server.
A clipboard a source's devices share. "Show clipboard" in a connected source's storage menu (the gear, or the source's
⋯on a phone) puts a Clipboard region above the status line, on this device. Paste reads text or a picture from the system clipboard. A keyboard paste or a drop on the region, or "Add a file", adds files of up to 25 MB. Each item shows as a text preview, a thumbnail or a file card. Pressing one copies text or a picture back to the clipboard and saves a file. It keeps the last 10 items, newest first, and pasting something already there moves it to the top. Items are files in a hidden.clipboardfolder in the app folder. A paste is kept on the device at once and sent when online, and where the instance runs the change relay, the source's other devices show it within a second or two. Not offered for notes kept on this device only. Turning it off only hides it, and disconnecting a source drops its clipboard from the device.@skysa/coreexports the clipboard's naming (clipName,readClipName,clipStamp,clipPath,isClipPath) and its folder and cap (CLIPBOARD_FOLDER,CLIPBOARD_ITEMS). A sync's outcome now says when a pull met that folder (SyncOutcome.clipboard).
0.18.2
Fixes and small changes
Menu items no longer end in "…". "Disconnect…", "Import a folder…", "Import files…", "About Google Drive…", "Move to notebook…" and the format toolbar's "Link…" now read like every other menu item: "Disconnect", "Import a folder", and so on.
0.18.1
Fixes and small changes
On a phone, the source dropdown's storage panel now ends in the same line as the foot of the sidebar: the source and a few words on how syncing is going ("Google Drive · Synced 9:41"), with the bar under it during a long sync. The count of other devices sits at the end of that line and opens the same list as on desktop. "About Google Drive…" is now in the source's
⋯menu instead of folding out under the source's name. The "Syncing with … · account" heading and the full-sentence status above it are gone; problems that need the user are still spelled out above the line.
0.18.0
On an instance that runs the change relay, the app holds a socket to it for the source on screen, while the app is in front of the user and online. A device that has just pushed says so, and the connection's other devices sync within a second or two instead of at their next poll. Polling goes on as before, and an instance without a relay is never asked for a ticket: the app asks
/api/configfirst. The socket closes when the tab is hidden, goes offline, switches source or is disconnected. It reconnects with backoff after a failure, at once when its hour is up, and never after the device has been signed out.@skysa/corenow exports the relay's wire protocol, so the two ends cannot drift: its two messages, its keep-alive, its close codes and its throttle.
0.17.1
Fixes and small changes
A long sync on a phone no longer starts over when the screen goes off. A pull that fails part-way, as one does when a phone's screen times out and takes the network with it, keeps the notes it had already read, and the next try reads only the rest; before, a batch of up to a thousand notes on Google Drive was downloaded again from the first. While a sync long enough to show a count is running and the app is on screen, the app also asks the browser to keep the screen on, and lets go when the sync ends.
0.17.0
The storage panel at the foot of a wide window's sidebar is now one line — the source and how syncing is going, with the account in its tooltip — and a gear whose menu holds Sync now, Re-scan, the download, the imports, Stop syncing and Disconnect, the same items as a source's
⋯on a phone. How many other devices are signed in stays in the line, beside the gear, and opens their list over everything. What Google Drive keeps from the app is in the gear as "About Google Drive…".Notebooks and notes can be pinned to the top of their list from the
⋯on their row, or a right-click: "Pin to top", and "Unpin" once pinned. A pinned notebook goes to the top of the notebooks under its own parent, a pinned note to the top of its notebook's list, and a pinned row is tinted. Pins are kept on this device, per source, and follow a notebook that is renamed or moved.A long sync now says how far it has got. The engine counts a round from a stored cursor as it receives it (
SyncProgressgains areceivingstage), so a device picking up another's import of a thousand notes is no longer silent until all of it lands. The storage panel shows a run of twenty or more as a count in its status line — "Sending 120 of 1,000", "Receiving 5 of 30", "Looking for notes: 40 found" — with a bar under it and the file it is on in the line's tooltip; in a compact window the panel says it in a sentence over the bar.
Fixes and small changes
An imported library, or the notes a device brings to the first account it connects, is now sent notebook by notebook: each notebook goes up with its notes and the files beside them before the next one starts. Another device fills in a notebook at a time, rather than showing every notebook empty until the notes arrive.
A sync to Google Drive sends up to four notes at once rather than one at a time, so a large import reaches it several times faster. A provider says how many it takes (
StorageProvider.writesAtOnce); Dropbox and OneDrive still take one, since Dropbox refuses writes that meet one another as a rate limit. Notebooks, moves, deletions, files beside notes, and the write of a note with a rename queued still go one at a time, in order, and a notebook missing under notes sent together is made once between them. Requests that find the access token expired at the same moment now share one new token.
0.16.0
A notebook with notebooks inside it can be opened and shut in the sidebar, by the chevron before its name or with the Right and Left arrow keys on its row. Notebooks start shut, so a large library opens as the short list of its top level, and a shut notebook's count includes every note inside it. Which notebooks are open is remembered on this device, for each source, and follows a notebook that is renamed or moved, here or on another device. The notebook open in the note list is always shown: the notebooks it is in are opened for it. While a note or notebook is being moved, resting it on a shut notebook opens it, so a destination inside one can be reached.
A note's dates come from its frontmatter in every browser. A
createdwritten as2014-02-20 14:00:10 UTC, as OneNote's exporters write it, is now read in Safari and on iPhones too, where it was taken for the day the note was imported. The date a note shows as edited is read fromupdated, or else frommodified,date modifiedorlastmod, as other tools write it, or else from when the note was made, rather than being the day it was imported. Notes already imported keep the dates they were given; importing them again dates them by their frontmatter.
Fixes and small changes
The chevron before a notebook that opens now starts where the text of the other lists does, and the notebooks' names move over by less to make room for it. On a touch screen its tap area reaches back to the pane's edge, so it is wider than before while the names sit closer to it.
A note's row no longer spends its preview on a line that only says when the note was made, such as the
Thursday, February 20, 2014 2:00 PMOneNote puts under every page's title. The preview starts with the note's own words instead. A line that says anything more than the date is kept.
0.15.0
The storage panel of a Google Drive source says, behind a line that opens, that Drive lets the app see only the files it made, so notes copied into its folder on the Drive website, with Drive for desktop or by another app do not appear. The README says the same before anyone chooses a provider.
Notes can be imported from a folder or from files (a
.zip, such as the one "Download all notes" makes, or loose.mdfiles), from the storage panel of the source showing. Notebooks and the files beside notes come too. Before anything is written, a question says what will come in and what stays out and why: notes that are not UTF-8 text, notes the app keeps hidden, and files over 25 MB. Nothing already in the source is changed: a taken name gets a number, and a file already there is not sent again. This is the way to bring notes into a Google Drive source, where files copied into the folder on the Drive website are invisible to the app; the Drive notice now points to it.
0.14.0
An operator's
checkCodemay answer an accepted code withhold, a value the app keeps and sends in place of what was typed, for up to a year: a pass for the device, say, so a code good for minutes is typed once per device rather than once per sitting. The app never shows a held value (the gate says the code was accepted on this device, and Change opens an empty field), and asks about a held value again as it loads, at most once an hour and at once after a refused connect, keeping the answer and letting go of a value the policy refuses. A refused connect no longer drops a held value, only a typed code. An app from before this release refuses an answer that keeps a code longer than a day, so an operator should give long holds only once its app has been updated. A code, or a held value, may now be up to 256 characters. The self-hosting guide's code gate example gains thecheckCodeit needs.
0.13.0
Each source shows which storage it is: the provider's logo in front of its name on the tabs, in the source dropdown, and in the menu that connects another account. On a phone, the source dropdown shows the logo alone; its name is in the tooltip and is still read by a screen reader.
0.12.0
A notebook's menu has Attached files where it holds any: each file with the notes that link it, and Delete for one no note links.
A picture or file taken out of a note is deleted a few minutes after the note is closed, once a sync has run since, if no note in the same storage names it then. A provider puts it in its trash. Nothing is deleted while the device may not hold every note in that storage, such as during its first import, and deleting a note still deletes none of its files.
Fixes and small changes
An empty line in the rich editor is written to the note as a blank line, not as
<br />. Two blank lines between paragraphs are one empty line in rich text, and three are two, so a note another app wrote opens with the spacing its text shows. Pressing Enter in an empty note, or at the top or end of one, no longer sends the note to markdown mode with "The rich editor has no way to show the HTML<br />on line 1": an empty line at the top or end of a note is written as nothing. A<br />already in a note is kept as written and shows in rich text, where it can be deleted. An empty task item is still written- [ ] <br />, since- [ ]alone is not a task in markdown.A picture tapped on a phone can be taken out of the note again. Backspace on a picture selected whole did nothing on Android, where the keyboard sends an input event rather than a key: a delete it asks for now deletes the selected picture or file chip. A selected picture also has a Remove from note button over its corner, as a chip has, for a keyboard that sends nothing at all.
0.11.1
Fixes and small changes
When the rich editor keeps a note in markdown mode, the banner now always says what it found. If the editor would add something the note doesn't have, the banner says so and shows what it is, including its text, with invisible characters written out. A kind of markdown the banner has no friendly name for is named as it is, rather than as "markdown the rich editor has no way to show".
A note with a picture in it opens in the rich editor again. A picture with no title broke the rich editor with prosemirror-model 1.25.12, so a note was sent to markdown mode with "This note uses markdown the rich editor has no way to show" once it was opened again, or switched to markdown and back. A picture just added was shown fine until then. The workspace now resolves prosemirror-model 1.25.12 and prosemirror-view 1.42.6, so the tests run against the versions a fresh install gets.
The text cursor in the markdown editor is visible in dark mode again. It was drawn black on the dark background.
0.11.0
Downloads take the files beside the notes (#187). "Download all notes" puts in every file whose current bytes this device holds, at its own path beside the notes that link it, and says how many it left out because the device holds no current copy of them; the disconnect question's download and a detached source's take the files not uploaded yet and the files the unsent notes link, and a file not uploaded yet can be downloaded on its own. The archive is written as parts straight into the blob, so a large file is never copied into one buffer, and one too big for a ZIP is refused before its bytes are read. The archive limits are told in words that name files too. "Download all notes" is offered, in the panel and the palette alike, only where the archive would hold something. A download of what was never sent that fails now says why, in the disconnect question and in a detached source's panel.
Files beside notes follow them between sources (#187). Connecting a source copies the device's own files into it with the notes, each owed an upload ahead of the note that links it, and a cancelled import leaves the device's files as they were. A source's own account coming back resumes its files as they were, uploads and all; one whose remote turns out to be someone else's sends again the files whose bytes this device holds. A detach keeps the files not uploaded yet, the files its kept notes link, and the files a notebook renamed here took with it. Moving unsent work to another source takes the files not uploaded yet and the files the moving notes link, where this device holds their bytes, and the confirmation says that the rest stay behind; a file not uploaded yet whose bytes are here counts as something to move on its own, and a copy owed from the remote does not. A detach that finds nothing unsent leaves no file rows behind it, and a file is never queued two uploads. While a resumed source is unchecked, a file the remote may have lost whose bytes are here counts as unsent.
A file beside a note that is not a picture shows in the rich editor as a chip with its kind and its name. Selected, it can be opened, downloaded, or removed from the note. A PDF, a picture, a recording or a film opens in a tab, and anything else is downloaded, never shown. On a phone, Download offers the share sheet.
Code no longer loses a picture or a file: Code over one leaves it what it is, and a paragraph made into a code block keeps each as the markdown it is.
A file pasted or dropped into a note goes beside it in the note's folder, and the note links it: a picture shows as a picture, and any other file as its chip. Raw mode puts in the markdown that links it. A new note is stored when a file is added to it. A paste that carries words of its own, as one from Excel or Word does, is still pasted as words. A file over 25 MB, or a
.mdfile, is refused and the reason is shown. If the note or the editor is left before a file goes in, you are told to add it again. A file dropped anywhere else in the app no longer replaces the app with that file.You can choose files to put in a note in three places: the paperclip on the toolbar, Image or File in the slash menu, and "Attach files" in the command palette. The palette command works in both editors. What you choose goes beside the note and takes the place of the selection, just as a pasted file does. On a phone, Image asks for photos. Toolbar buttons that are not toggles, such as indentation and clearing formatting, are no longer announced to screen readers as "not pressed".
Pictures beside a note show in the rich editor. One that cannot be shown says why, and one larger than 8 MiB waits to be asked for. A picture pasted from Word or Outlook, which no page can load, keeps its words.
The library learns files beside notes (#187).
addAttachmentadds a file to a note's folder under a content-stamped name, refusing anything over 25 MB and any.md, and queues its upload ahead of the note. Moving a note takes the files it links with it, copying one another note in the old notebook still links, and putting one beside a different file of the same name under a conflict name; moving a notebook moves its files ahead of its notes, and deleting one deletes them, with the count in the confirmation. A file not uploaded yet counts as unsent work when a source is disconnected, keeps its upload while the source is detached, and is forgotten with a source the user discards. A blocked sync names the op that stopped the queue rather than an upload stepped over. In the sync store, an upload that lands for a file moved or deleted meanwhile sends thermdirs over it behind what it now owes, so a notebook let go during an upload does not stay on the remote. Nothing in the editor adds a file yet.
Fixes and small changes
The app can read a file beside a note for showing it (#187), though nothing shows one yet. A file this device holds is answered from the device; any other is downloaded through the source being synced, at most two at once and once however many ask, and kept in a cache of 250 MB that lets go of the least recently used first. It never lets go of a file not uploaded yet, nor of the files of a disconnected source or of one not yet checked against its storage. A file the storage no longer has is reported as gone and left for the next sync to remove. Object URLs for showing files are shared between views and revoked a few seconds after the last one goes.
The sync engine mirrors files that are not notes on pull (#187). Each listed file becomes a row, matched by id, or adopted from a pending row at its path of the same size; nothing is downloaded. The remote keeps the path: a pending row, or one the user moved there, steps aside under a conflict name that keeps its extension. A file the user is deleting is passed over, and one the user is moving stays where they put it. A file deleted there goes, unless the user moved it here, when it is pending again and sent from the bytes held here, or let go of at the push where none are; a file moved to a hidden path counts as deleted. Folder deletions take bound rows and keep pending ones, a note renamed into a file lets the note go and the other way round, and a scan drops the bound rows it did not find. In the web store, a file sent again is pending whether or not its bytes are held here, as the engine expects.
The sync engine pushes files that are not notes (#187). An
uploadsends a pending file's bytes, or copies them from the remote file it copies, making the folders it needs. The same file already at its name is taken for its own, unless another row holds it or a queued delete is to take it. Anything else there keeps the name, and the upload goes beside it under a conflict name that keeps its extension, chosen from one listing of the folder. Amove-filemoves a file by id, and adelete-filedeletes one. Uploads do not hold up the queue: one that fails is counted and stepped over, then sent after everything else on later pushes; one out of attempts is kept and reported; and the outcome carrieswaitingUploads. A delete that a waiting copy still needs is held back, and so are anrmdirover it and an upload or move to its name. Anrmdirleaves a notebook that still holds a file row. The web store no longer carries cached bytes to a file a move took for its own. New exports:contentTypeOfandconflictFilePath.A picture pasted, dropped or picked into a paragraph with words in it now goes in a paragraph of its own, rather than on the line beside them. A note's preview no longer runs a picture's or a file's name into the word before it.
A request to a storage provider is no longer given up on after a minute regardless of size: it gets 1 ms more for every 50 bytes it sends or receives — 50 KB/s, the slowest connection a file is still expected to cross — counted from the answer's
Content-Length, or as its bytes arrive where it gives none. A note or a page of changes keeps the minute. This makes room for attachments of up to 25 MB (#187).The sync store learns files that are not notes (#187): a row per file, bound to the remote or pending upload, with its bytes kept apart and handed back only while they are still the file's.
SyncStoregainsfileById,fileByPath,fileByRemoteId,allFiles,filesUnderandfileBytes; a pull can put, move aside, delete and re-upload a file; a folder move and delete take files with them, leaving a pending one; and an upload, a file move and a lost file each have an outcome. The web app's database gainsfilesandfileBytestables.The formatting toolbar now works from the keyboard. Enter or Space presses the focused button, opens one of its menus, or picks from an open menu; the toolbar over a selection works the same way. Focus stays on the toolbar after a key press, so the next key does not type over your selection. A button that can't be used right now is announced as unavailable but stays reachable. The arrow keys in the link field now move the cursor within the address instead of jumping to the toolbar. A toolbar menu closes when you tab past it. Screen readers now announce the toolbar's menus as expandable panels rather than menus. A right-click on a toolbar button no longer applies it.
0.10.0
In a narrow window, the storage, notebook and note dropdowns at the top sit together as a path — each as wide as its name, with its chevron midway to the next — instead of in three equal thirds of the bar. A short name keeps all of its width; only the long ones are cut short, sharing what room is left. The chevron of the dropdown that is open is lit.
In a narrow window, choosing in one dropdown opens the next: a source chosen opens its notebooks, and a notebook its notes, so a phone goes from an account to a note in one pass. Going from one dropdown straight to another slides them along together — the one left goes off to the side and the next comes in from the other — in the order their names sit in the bar. A notebook with nothing in it still goes straight to the new note begun in it, and a user who has asked for reduced motion gets each panel at once.
Formatting on a phone. The formatting bar shown with
Formatstays shown on this device after a reload, until it is turned off. Its menus — the text style,More tools, the link field — now open over everything, including the note's title and the top bar, and are never taller than the room the keyboard leaves, scrolling inside themselves past that; with the keyboard up, the first items ofMore toolsused to be cut off out of reach. And on a touch screen the floating formatting bar no longer comes up over selected text, where the system's own selection menu already does.
Fixes and small changes
An empty list item is written as its marker alone —
-,2.— instead of- <br />. Pressing Enter after a list item and switching to the markdown showed a<br />nobody had typed; and a note with an empty item in it (-on a line of its own) opened in the markdown editor, with the banner saying the rich editor could not show it. An empty task item still needs- [ ] <br />, since- [ ]alone is not a task.
0.9.0
Connecting another account covers the app with the same progress dialog as the first, until its notes have arrived. It used to open the new source straight away, empty, offering to make notebooks while the import ran. Cancel goes back to the account that was showing.
0.8.0
Disconnect signs out only the device it is pressed on. Other devices connected to the same account keep syncing, and the last device out disconnects the account and withdraws its access at the provider, as before. The disconnect question says which of the two it will be. Cancelling a first import signs out the same way. The API client's
disconnect()is replaced bysignOut().The connect gate's link ("Get a connect code") opens in the same window, as connecting storage does, instead of a new tab. In an installed app on a phone the new tab was a second window with no way back. The app opens at the code field when it is loaded with
?enter=code, which is where an operator's page should link back to.
Fixes and small changes
Connecting an account again replaces the source this device still had for it under an old connection, rather than adding a second one ("Google Drive 2") beside it. "Connect again" is now a button under its message, spaced like the panel's other buttons, rather than an unstyled one wrapped against the text on a phone.
0.7.0
The storage panel's device list names each device by its browser and system, "Safari on iPhone", and lists only the other devices, folded behind a count: "2 other devices signed in on this account". The server keeps the label, worked out from the User-Agent when a device connects, and never the header itself.
Migration
0006_grant_deviceadds a nullablegrants.devicecolumn. Apply it before deploying (wrangler d1 migrations apply). Devices that connected before it show as "A device" until they next connect.
Fixes and small changes
A notebook's new name shows everywhere it is named as it is typed — the note list's heading, the bar's dropdown in a compact window and the open note's path — instead of after Enter. On a phone, each source's
⋯begins with Rename, which turns its row into the name to type, and the dropdown above says the new name as it is typed.Two fixes on a phone: the menu the Sources dropdown's
+opens is drawn over the list again, rather than under the rows, andUse codesits in the middle of its button.
0.6.3
Fixes and small changes
Every row in the Notebooks, Notes and (on a phone) Sources lists ends in a
⋯of its own, with what can be done to that notebook, note or source, so it is clear what the menu is about. A notebook's comes after its note count. The⋯buttons in the pane headers are gone; each header keeps its+.On a phone, a source that is not showing offers the same actions as the one that is: choosing one shows that source first, then syncs it, re-scans it, downloads its notes, or asks about disconnecting it in its own panel.
0.6.2
Fixes and small changes
The Sources, Notebooks and Notes headers offer their choices the same way: a
⋯and a+beside the name.- On a phone, the Sources dropdown has that header. Its
⋯holds Sync now, Re-scan from scratch, Download all notes, Stop syncing on this device and Disconnect…, which were buttons in the panel; its+opens the list of storage providers, which was at the panel's foot. The storage panel there now says how syncing is going and asks what a choice needs asked. - The note's
Note optionsmenu (Move to notebook…, Delete) moves from the end of the note's header to beside the note list's+, in every window.
- On a phone, the Sources dropdown has that header. Its
0.6.1
Fixes and small changes
On a phone, the source, notebook and note dropdowns are their words and a chevron, without a box around them. The chevron points right while a dropdown is shut and turns down as it opens, and its panel opens out of the dropdown that was pressed and shuts back into it. With reduced motion asked for, the panel simply appears.
A menu or a question opened from inside a dropdown — the notebook's
⋯menu, its Delete confirmation — no longer shuts the dropdown when it is pressed, so Rename puts the name field where it can be typed into, rather than in a dropdown that has to be opened again to find it.The note list says what is being typed as it is typed: a row's title and preview follow an edit to its note at the keystroke, not when autosave stores it two seconds later, and so does a name being typed into the name field. A heading typed into a note not named yet names its row as it is typed, as the save will name it.
A new note opens with its name selected, ready to be typed over, and Enter in the name moves to the text. It is stored only once something is written in it — a name given, or a keystroke in its text — so a note opened and left blank leaves no file behind. A notebook with no notes in it begins one when it opens, the same as
+; the loose notes and a source still being imported do not. On a phone the note is what shows, not the notebooks or notes dropdown it was begun from.The note list is ordered by when each note was made, newest first, rather than by when it was last edited, so editing a note no longer moves it to the top.
The app remembers, on this device and per source, which notebook was open and which note was open in each notebook, and goes back there: when it opens at its start URL, when a source is shown again, and when a notebook is clicked. Where nothing is remembered, or what was has gone, it opens the first notebook and its newest note — including for a notebook the app opened by itself, which used to show an empty pane beside its list.
Renaming a notebook changes only its name into a field: the row keeps its height, its highlight and its note count while the new name is typed.
A note's preview in the list, and its excerpt in search answers, are the text the rich editor shows —
**bold**reads "bold", a link reads as its words without its URL — rather than the markdown with only its line markers removed. In the list, a pipe stands where one line of the note ends and the next begins, so two lines no longer read as one sentence.
0.6.0
The app's name, colours, icons and fonts come from a brand file read when it is built. Point
NOTES_BRANDat abrand.jsonof your own to set them (docs/self-hosting.md, "Your own brand"); nothing in the repo needs editing.Without one, the app now builds as "Notes", in gray, with the curled-page icons on that gray, where it was "Skysa Notes" in blue. An instance that wants its old look back sets
NOTES_BRAND. Nothing about data changes: the folder in storage and the local database keep their names, so connected libraries are found as before. The page also gets a title-bar colour for each of the light and dark themes, and the brand colour a dark-theme value of its own.
0.5.2
Fixes and small changes
The storage panel's list of devices signed in to a source is a row per device, with what the device is on the left and Remove at the end, rather than a bulleted list whose Remove button sat inline after a label that wraps in a narrow sidebar.
0.5.1
Fixes and small changes
The OAuth callback no longer strands a browser that asks for it twice. Its answer is kept for five minutes in a signed cookie,
skysa_flow_answer, so the same callback asked for again, as after going on past a browser's warning page, a reload or the back button, is sent where the first was,?connect=outcome and all, and exchanges nothing. A callback with no flow of this browser's behind it, which was answeredflow_expiredin raw JSON, is sent back to the app as?connect=expired, and the app says that connecting did not finish and to connect again if the storage is not connected.
0.5.0
A connect gate's code is checked as it is used. Breaking for a gate with
connectCode:createAppnow refuses one whoseEntitlementProviderhas nocheckCode(code), which answers{ accepted: true, expiresIn }in seconds (at most a day) or{ accepted: false, reason? }. The app asks it through the newPOST /api/connect-code, which is same-origin and rate-limited asconnect-code:<ip>, and the callback still decides withcheck.connectCodetakesrequired, which leaves no way to the buttons without an accepted code. The gate is two steps, one open at a time. An accepted code is held inlocalStoragefor as long as the policy said, and the gate folds to a line naming it until it runs out. A refusal is shown under the field, in the policy's words where it gave some.
0.4.1
Fixes and small changes
The service worker answers a navigation with the app shell only for the app's own route,
/with or without a search. Every other path on the origin goes to the network, so a page served beside the app, such as the one a gate's action links to, opens as itself rather than as the app's "Not found" in a browser that has run the app before.
0.4.0
An operator's gate can ask for a code.
ConnectGatetakes an optionalconnectCode: { label }, and the app shows a field under that label where the gate is, in front of the provider buttons and beside them. What is typed is held for the tab, sent with/startasconnectCode(trimmed, at mostMAX_CONNECT_CODEcharacters), carried to the callback in the signed flow cookie, and handed to the policy there ascheck(subject, { connectCode })./tokennever passes it. A plain refusal of a connect that carried a code reads "The code you entered was not accepted", and the code is dropped.
0.3.0
An operator can gate connecting.
EntitlementProvidertakes an optionalgate— a message and onehttps:link — whichcreateAppchecks when it is built and/api/configserves asconnectGate. The app shows it where the provider buttons are: a device with an account syncing on the instance sees the buttons with the gate beside them, and any other sees the gate with an "Already have access? Connect storage" control that shows them.A refusal can say which kind it was:
EntitlementDecision.codeis one ofENTITLEMENT_CODES(not_allowed,lapsed,limit_reached), carried by the callback as?connect=refused&code=…and by/tokenbesidereason; any other value is dropped. The refused toast and the storage panel word the refusal by it, the panel shows the operator's reason, and both offer the gate's link."Download all notes", in the command palette and the storage panel, saves the source showing as one zip of markdown: every note as the file a push would send, at the path it would have, and every empty notebook as a folder. On a device with nothing connected it is the one way to get the notes out of the browser. An archive past the format's limits (65,534 entries, 4 GiB) is refused in words rather than handed over broken.
The app asks the browser to keep this device's notes (
navigator.storage.persist()): once, when the first note is made in the device's own library, and again when the app is installed. While the browser has not agreed and the notes here are the only copy, the storage panel says the browser may clear them without warning, and that connecting storage or downloading them keeps them.
0.2.0
Disconnecting a source now asks what should become of anything it never sent, before anything happens — and can move it to another connected source.
Pressing Disconnect saves whatever the editor is still holding, sends one last push where that has any chance of working, and then asks. That push is the app finishing what you had already asked it to do, and it is the only thing that happens before you answer: nothing is said to our server until then, so cancelling leaves the account connected exactly as it was. If the account has everything, it is the plain confirm it always was. If it does not, the question says how much has not reached it and cannot once it is disconnected, names the notes (five, with the rest behind "and N more") and counts the renames, deletes and notebooks, and says why they cannot be sent right now when you are offline or a change has been refused too many times. Cancel is the button that has the focus in every step, Escape closes, and a note whose text could not be saved yet stops the question being answered at all until you have copied it out.
The answers are to move it, download it, discard it by name, or cancel. Move takes the notes the account never had in full, and the notebooks they are in, into another source you have connected — named on the button, chosen from a list where you have more than one — and uploads them there as new notes. A second step says what will be in each account afterwards: a note the old account already had in an older version stays there as well, and a rename or a delete you made but never sent is not carried across, because each is about a file only the old account has. That same Move is now on a disconnected source's own panel, beside Reconnect, Download and Discard.
Move is offered only where it means something: there has to be another source connected, something of the kind it carries on the list, and the source's files have to have been checked against its account. A source you reconnected and have not been online with since lists everything it holds, because until those files have been looked for, "already sent" is a memory rather than a fact — so the question says that is why the list is full, and offers Download, Discard and Cancel but not Move.
As with Discard, a move reaches only what you were shown: a note written into after the list appeared is kept where it is, and the source stays on the device around it. So is a note whose save is still failing when you answer, even if it only began failing while you were reading the question. Undoing a delete after a move puts the note back in the source its notes were moved to — under a new id where that account already had one of its own by that name, so nothing of its is written over — and an editor left open on a moved note follows it there.
Disconnecting a source no longer leaves its notes in a pile on the device that nothing shows while another source is connected.
A disconnect now removes from the device the notes the storage account already has — nothing is deleted from the account, and connecting it again brings them back. Whatever the account was never sent (an unsent note, a rename, a delete, a new notebook) stays exactly where it was, under that source, which is marked disconnected in the list with a count of what it holds. This is the same whether you pressed Disconnect, stopped syncing on this device, or the account was disconnected from another device while nobody was there to ask.
Text you were typing when the disconnect happened is kept too: what an editor still held is saved first, and a save that arrives after its note went with the source puts the note back under that source as an unsent change.
A disconnected source can still be opened and edited, with a notice saying it is not syncing. Its panel offers three things: Reconnect, which takes the kept changes up again when it is the same account (a note the account has changed meanwhile is kept beside it as a conflict copy); Download, which saves the unsent notes as a ZIP of markdown files; and Discard, which lists the notes by title, says plainly when a delete would be carried out on reconnecting, asks twice, and can only discard what it listed as it stood — a note written after the list was shown, or written into since, is kept, and so is everything if the source was connected again from another tab meanwhile.
Connecting a different account never takes another source's notes, and the "these notes belong to another account" prompt is gone with the pile it was about. Undoing a delete after its source was disconnected puts the note back under that source and says so.
Empty panes now offer to do what they suggest. "Select a note, or create one" makes a note in the open notebook when you press "create one". An empty notebook says "No notes here yet. Create one." And with no notebooks yet, the sidebar, the note list and the note pane each have a "Create a notebook" (or "Create one") that opens the new-notebook field. On a phone this opens the Notebooks panel as well, since the note pane is the only thing on screen there.
With nothing connected, the
+in the top bar now reads "+ Connect storage provider", and the storage panel's hint names it: "Use “Connect storage provider” above to sync them." Once an account is connected it goes back to a plain+.If you already have notebooks or notes on this device, choosing a provider now asks first: "Your 2 notebooks and 5 notes on this device will move into Dropbox and sync there." Connect and move carries on as before. Cancel (which has the focus, and is what Escape does) leaves everything on this device only, with nothing written and nothing sent to the server.
Connect and move is filled in the accent colour, as the answer the question expects. Cancel still has the focus.
Deleting a notebook now asks in the same kind of dialog, over the page, instead of in a strip under the Notebooks header: "Delete notebook? “Work” and the 3 notes in it will be deleted." Cancel has the focus, Escape cancels, and keyboard shortcuts wait until you've answered.
Right-click a notebook in the sidebar, or a note in the list, for the same menu its
⋮button opens: New notebook inside, Rename, Move and Delete for a notebook; Move to notebook and Delete for a note. It acts on the one you clicked, which does not have to be the one open, and deleting a note this way offers Undo just as the note's own menu does. The menu key and Shift+F10 open it too, under the focused row.The
⋮menus now open over everything and stay inside the window. At widths just above the phone layout the notebook menu used to run off the left edge of the window, and then was cut off at the edge of the sidebar.The note and notebook options menus are now drawn like the "Connect storage provider" menu and the editor toolbar's menus: a card with a full-width row per choice that lights up under the pointer. They used to be a list of underlined links. Their buttons show three solid vertical dots, like Material's "more" icon: the same icon in both, centred in the button, and the same one the formatting toolbar's overflow button uses. The dots are twice the size they were, which was hard to see. The notebook's button used to be a
⋯character sitting low and to one side, in a boxed button; it is now drawn like the note's, with no box, and lights up under the pointer.The Markdown tab in the note's header now shows a code icon (
<>), which is easier to tell apart from the rich text tab next to it.A source's first import says how it is going — notes found, then downloaded and uploaded against the whole count, with the file it is on — and can be cancelled, which puts the device back as it was before connecting. For the first source connected, the app is held behind the progress dialog until the import is through. The sync engine takes an
onProgressoption, and a full scan now lists every page before it downloads a note, so it has the whole count first. The web app cancels a sync session's requests when the session ends.The page and the installed app are called Skysa Notes. The storage panel's Re-scan and Disconnect are outlined buttons, Disconnect in red. The outline's toggle sits beside the note's menu, and on a phone the outline flies out over the note and shuts once a heading is chosen.
Notebooks can be managed. The
+in the sidebar header now makes a top-level notebook whatever is open — before, it always nested under the open notebook, so once there was one there was no way to make another at the top level. Beside it is a menu for the open notebook: a new notebook inside it, Rename, Move, Delete. Renaming happens in the row itself; deleting asks first and says how many notes would go with it, counting those in notebooks inside it.Notes are keyed by connection and id, so two connected sources can each hold a note of the same id — which they do whenever one folder has been copied into two accounts, since the id travels in the file.
The local database moves to schema version 5 on first open. Every note is carried over as it was; the upgrade is a single transaction, so a failure leaves the database as it found it. A tab still running the previous build is stopped and asks to be reloaded.
A file whose id another source already holds now syncs under the id it names. It used to be given a made-up id, and the two sources then took turns writing their own id into the file.
SyncStore.idHeldElsewhereis removed from the core port: a store's ids are its connection's own, and the engine no longer asks. When two sources that held a note of one id are both disconnected, the second to arrive in the local pile is given a fresh id and keeps its queue.Re-arrange notebooks and notes by dragging them. A notebook can be dropped into another or taken out to the top level, and a note can be dragged from the list into any notebook; both are real moves on the provider, through
moveFolderandmoveNote. A notebook cannot be dropped inside itself, and a note cannot be dropped at the top level, since the app never makes a loose note. Picking up is also a command ("Move notebook", "Move note to notebook") so the same moves work from the keyboard and from a pointer that never drags, and Escape puts down whatever is being held.The layout follows the width of the window, and each part of it the room it has. Below 1250px the notebook and note columns narrow with the window. In a window as narrow as a phone, the source tabs become a dropdown, the notebooks and the notes become dropdowns beside it, the search becomes an icon — or a field, on a bar with the room — and the source dropdown holds the storage panel — what is syncing and the way to disconnect — above the way to connect another account. The formatting toolbar is hidden there until the Format button shows it at the bottom of the note. The outline starts open beside a wide note and collapsed beside a narrower one, with an Outline button in the note's header to open it, going by the note's own width rather than the window's. The note's header keeps to one line, the path giving way before the title, and its controls are icons: rich text and markdown as a pair of tabs, Outline and Format toggles that show when they are on, and a Note options menu to move or delete the note. Spacing is consistent across the screen — the note's title lines up with its first line and the toolbar's first label, pane headings with their rows — and tightens in a compact window. A toggle that is on shows it in the accent colour, and no longer looks on after being tapped off on a touch screen. The formatting toolbar keeps to one line too: what does not fit moves into a More tools menu at its end, least used first. While a notebook or note is being moved, the hint above the notebooks has a Cancel link, and Escape still works. Pressing Escape on the
+beside the tabs now closes its menu, and a second press on the+closes it rather than reopening it.Search results now drop from the search field — a card under it on a wide screen, everything under the bar on a phone — instead of taking over the notes column. Arrow keys move through them and Enter opens one. Choosing a result opens the note, empties the field and, on a phone, closes the search.
Fixes from a whole-codebase review. Nothing here changes the file format or the database schema.
Sync. A note no longer ends up at a path its file has left when a round renames its folder away and moves the file back to the old name — the pull said
okand the next edit blocked the queue. A second connected source whose files name note ids another source on the device already holds now syncs; it used to fail identically on every retry, for ever. A cycle in the id tree an OneDrive or Drive cursor carries — a legitimate state between two pages — no longer overflows the stack on a large library.Editor. A sync pull into a note open in rich mode no longer saves a conflict copy nobody typed: changes Milkdown's own plugins make in answer to a loaded body (heading ids, table repair) were being counted as the user's. Undo after a pull no longer puts the pre-pull text back and pushes it over someone else's edit; an adopted body empties the undo history in both editors.
Deleting a note can be undone for about ten seconds, from a notice or the palette, whether or not the delete has already synced. The note goes back to the source it was deleted from, keeps its path, and anything that took the name meanwhile moves aside. Text the editor still held comes back with it — as the body, or beside the note where something newer had been stored since.
Saving. A save that fails is said, in the note, and retried — newest text first, never an older body over a newer one — where it used to be dropped silently. A tab left open across a schema upgrade made by a newer build in another tab now finishes its writes, closes, and asks to be reloaded, rather than carrying on writing with old code into the migrated database. A tab that opens after the upgrade was made closes at once, writing nothing, and asks the same. The notice can be put aside to copy text out.
Files. An
id:the user wrote that the app cannot use (id: 202409141302) is left as written instead of being replaced by a UUID on first save, in the note and in a conflict copy of it, and is not respelled (0123stayed0123) when the title or tags are written. A frontmatter block closed by...is read as one, the same way before and after the app's own save (which fences the block with---where the body's first line would otherwise be taken for its closer), and an unclosed block no longer swallows prose up to the next---. File names are cut between grapheme clusters and capped at 216 bytes as well as 120 code points, and a conflict copy's name is fitted to 255 bytes, so an emoji at the boundary cannot produce a name OneDrive's adapter throws on; existing files are not renamed.Shell. A crafted
?note=link can no longer crash the app — search params a route refused were reaching components anyway — and a render error now shows a screen saying the notes are safe, with Reload and Try again. In the storage panel, "Stop syncing on this device" after a failed disconnect can no longer be pressed under a different source than the one that failed.Search asks every connected source at once. The field has moved out of the notes column and into the bar across the top, at its right-hand end, and its answers say which source each match is in when there is more than one, and which notebook. Opening a match switches to that source, opens that notebook and opens the note, so the tabs, the sidebar and the list all agree about where you are. The way to connect another account now sits with the tabs, right after the last one, rather than at the far end of the bar.
When a note has something the rich editor can't show, the banner now says what it is and which line it's on — "The rich editor has no way to show a link reference definition on line 5" — rather than only that there is something.
And the note is no longer stuck in markdown mode for as long as it stays open. Once you have changed it, the rich text tab (and Ctrl/Cmd+E) is offered again; pressing it saves what you typed, then opens the rich editor, which checks the note again before you can type. If it still can't show the note, you are back in markdown mode with the banner saying what it found this time, and nothing in the note has been changed.
Fixes and small changes
The app has its own icon, a white page with a gold curled corner on blue, in the browser tab, on the home screen and when installed, in place of the placeholder. The blue is the primary button's colour and the colour of links too.
Code blocks now say what language they are written in, and both editors colour what is inside them. A fence had a language in the file and nowhere on screen: the rich editor drew a plain
<pre>and the only way to set or change the word after the backticks was to switch to raw mode and type it.A bar of tools floats under the code block the cursor is in, and under no other: nothing sits above the code, where a row of controls on every block would be a row of controls in the way of every block. On it are the language picker, wrapping, line numbers, copy and delete.
Choosing a language rewrites the fence's info string, which is an edit like any other — it changes the file. A fence that says
jsshows as JavaScript without being rewritten tojavascript, and a language this app has never heard of —mermaid, something from another tool — keeps its word, keeps its place in the picker, and is simply left uncoloured.Wrapping and line numbers are the reader's settings rather than the note's: there is nowhere in markdown to write either one, so they apply to every code block in every note and are remembered on this device until they are changed again. Nothing either of them does touches a file.
Thirty languages, each fetched only when a note actually holds one, and all of them available offline. The list is curated rather than the whole of
@codemirror/language-data: every language is a chunk the service worker precaches, so the list is what the app downloads to be able to work offline — thirty is about 550 KiB of grammars and nothing added to the first load, where a hundred and sixty would be several megabytes of languages nobody opens.Raw mode colours the inside of a fence too, with the same grammars and the same palette, so a code block looks the same whichever mode the note is open in. Markdown's own syntax stays in black and white there: raw mode is the mode where the characters are the formatting.
Inline code is now drawn as a chip — monospace, a tint, a thin edge and its own ink — so a
--flagin the middle of a sentence is visibly not the sentence.A block that names no language says "Detect language", and means it. Turning text you have already written into a code block guesses from that text: select a snippet, press the button, and the fence comes with
pythonorsqlordiffalready on it. A block started empty works it out as it is typed or pasted into, and stops asking the moment it has an answer — a language you picked yourself, or one the file came with, is never second-guessed. The guess rides along with the edit that prompted it, so a single undo takes back both.It only answers when the text is distinctive: prose, a path, a list of names or a snippet that could as easily be Java as C# stays blank, because a blank picker costs a click and a wrong word in the fence has to be noticed before it can be undone. Text the app itself puts in the editor — a sync pull, a switch back from raw mode — is never labelled: a note nobody typed in comes back byte for byte.
A note that ends in a code block has a way out of it again. Clicking below the last block used to put the cursor inside the code, because that was the nearest place a cursor could go, and typing added a line to the code sample. There is now a strip under such a block — and a button on it for the keyboard — that puts a paragraph after it. Clicking it and then thinking better of it costs nothing: the note is not marked as changed, and the empty paragraph is taken back when the cursor leaves it.
Selecting text inside a code block no longer brings up the floating formatting toolbar. A code block holds no marks, so every button on it was a button that did nothing.
The toolbar has a code block button, the one insert it offers; the rest — a table, a quote, a divider — still comes from the
/menu. It lights up inside a code block and pressing it again gives the block back as plain text, and the mark buttons are grey in there, since a code block holds no marks.The menu behind the source bar's
+is now the same kind of menu the editor toolbar has, a card with a row per provider, and the+itself sits on the tabs' baseline without a rounded corner up against the top of the window. The tabs line up along the bar's bottom edge and no longer change width when one is lit. The note's header is the same height as the two pane headers beside it, so the three bottom rules meet. Clicking the title of a note that has none selects the "Untitled" placeholder, so typing replaces it rather than landing in the middle of it.Groundwork for disconnecting a source without leaving notes where nobody can see them. Nothing a user can see has moved: the app can now say what a source holds that its remote has not been sent, have the editors write what they are holding and report what would not save, and build a ZIP of notes for download — none of which is called from the interface yet. The one change to stored data is that each connected source's row now remembers what the server last called the account, so a source can still be named once the server stops answering for it.
The rich editor now fills the pane it appears to fill. The editable surface was only as tall as the words in it: everything below the last paragraph looked like the note and was not, so a click there put the cursor nowhere. Milkdown puts a container of its own between the element it is handed and ProseMirror's, and with no height on it the surface's
min-height: 100%had nothing to resolve against. Every element in that chain now grows.Above it there is a formatting toolbar, grouped the way Atlassian's editor groups one: text style, then bold and italic with the rest of the marks behind a "More formatting" button, then the three kinds of list, then indentation, then the link.
What is missing from it is missing because markdown cannot hold it — there is no text colour, no highlight and no alignment in a
.mdfile. Insert is not duplicated either: a table, a code block, a quote or a divider still comes from the/menu. The style menu offers headings down to six, two deeper than/does.Indentation is list nesting, which is all indentation means in markdown, and both buttons are grey wherever nesting would not happen — including the first item of a list, where there is nothing above to nest under. A lit mark button means the next press takes that mark off.
The bar is one stop in the tab order with the arrows moving inside it, so it is not fourteen presses of Tab between a note's title and its text, and it appears only in rich mode: raw mode is markdown, where the characters are the formatting.
Lists work properly as well, which they did not before. Switching a bulleted list to a numbered or task list — or back — now changes the list, where the preset's commands would only wrap a second list around it and so appeared to do nothing. Pressing the button for the list you are already in takes the list off.
A new item beside task items is now a task item, and an unticked one. Pressing Enter at the end of a finished task used to hand back a second task already ticked, and a double Enter two levels into a checklist left a plain bullet behind.
Task checkboxes can be ticked. The box used to be a character drawn in the stylesheet, which nothing can press; it is a real checkbox now, and
Mod+Enterdoes the same from the keyboard. Ticking an item ticks everything under it, and unticking a child unticks the item above it, as far up as that goes — nothing above an unfinished task is finished. Unticking an item leaves its children alone: that says the item is not done, which says nothing about work already finished. Adding a new sub-item unticks the item above it for the same reason: adding to what something consists of is saying there is more to do. A note that arrives from elsewhere with a finished parent over an unfinished child is left as it is — only a parent your own edit left that way is corrected.Ticking the last of a parent's children does not tick the parent: that is a task in its own right, and may have a step nobody wrote down.
A table with an empty cell no longer sends its note to markdown mode. The rich editor wrote every empty cell back as
<br />, a break the note never had, so the check that keeps it from rewriting a note refused the whole note. An empty cell is now written empty, including in a table made in the rich editor, and a<br />written into a cell is still kept as it was.An operator's
EntitlementProvideris asked at the OAuth callback as well as at/api/token, before anything is stored. An account it refuses no longer leaves a refresh token sealed in the database: nothing is stored, the consent just given is withdrawn where the provider has a call for it, and the app says the account cannot sync on this server. For operators:EntitlementSubject'sconnectionIdis now optional, and is absent when the account is connecting for the first time.A note that opens with a heading or a paragraph no longer starts lower in the rich editor than it does in markdown: whatever block comes first adds no space above itself, so its first line sits under the note's title.
The shell's
Strict-Transport-Securityheader no longer saysincludeSubDomains. Served from an apex domain it committed every subdomain the operator owns to HTTPS for two years; that is theirs to decide, anddocs/self-hosting.mdsays how to add it back. The Node floor is 22.13, the first 22.x wherenode:sqlite, which the API tests use, needs no flag.A
<br />inside a sentence no longer sends the note to markdown mode. The rich editor was deleting it —first<br />secondbecamefirstsecond— and the check that protects notes from the editor caught that and locked the note in markdown mode with a banner. Now only the<br />the editor itself writes for an empty paragraph is read back as one; a break the author wrote, in any spelling and anywhere in the note, stays exactly as written.A
<br />on a line of its own inside a paragraph works too. When the note is next edited in the rich editor, the line ending just before it becomes a space — the markdown writer does that to any inline HTML at the start of a line, so it cannot be mistaken for an HTML block — which reads and renders the same.The storage panel no longer tells you to check a connection that was never used, or that an account is still connected when it is not.
Disconnecting, connecting and removing a device are each a few writes on this device around one call to our server, and until now any failure of any of them was reported as the server's: "the server cannot be reached". That was plainly wrong for "Stop syncing on this device", which asks the server nothing at all — a store that refused a write there sent you to look at your connection, and offered you a server to try again.
Each of the three now says which half failed, because the code that made the call says so rather than guessing from the error, and each tells a server that answered with a failure — worth trying again — from one that never answered, which is worth looking at the connection for.
No message claims an outcome it does not know. Everything after the server disconnects an account is this device's own work, so a failure there can leave the account gone at the provider and this device still syncing it; the message says so and offers a retry, which is safe, rather than telling you the account was not disconnected when it was. A failure the app cannot place at all — after a call has already done whatever it did — says neither where it happened nor whether it worked.
Connect failures are also announced now, as the panel's own already were.
"Connect again" now signs out the credential the device held before, so it no longer lingers in the device list for 180 days as a device that is not one and a live key to the account.
On the server, revoking the last live device of an account disconnects it: the row is deleted and the grant withdrawn at the provider where there is a call for it, rather than being left as a live refresh token nothing can reach or revoke.
DELETE /api/connection/grants/:idanswersdisconnected(andrevokedwhen it is) alongsideok. The web app's device list only removes other devices, so this is reached through the API; an account whose only device clears its site data is still not cleaned up.Sign-in separate from storage is dropped rather than deferred: identity and storage are coupled, so a person is their storage account.
Two things a user or an operator can see. The refusal for
AUTH_MODE=account-firstnow says the mode is not implemented and will not be, rather than "not implemented yet" — a decision rather than a schedule. And the client stops carrying three connect outcomes the server retired in Phase 7:conflict,occupied, andsignin, which rendered "Sign in before connecting storage" for a product that does not exist. A stale link carrying one of them used to render an empty red banner; now it renders nothing.The notice after connecting a storage account, and the one saying why a note or a notebook could not be made, are now cards floated over the page rather than a thin grey bar across the top of the frame.
The bar was missable, which was found the way these things are: an account was connected with Google's files permission unticked, the app said so correctly, and the line was read only after being looked for.
Each card is coloured by what it is — success, warning or error — as a bar down its leading edge and a wash mixed into the surface behind it. The words stay
--fgon what is within a few percent of the colour they were designed against, so the message's contrast does not depend on its tone, and a warning or an error is bold as well as coloured: the colour is the one thing a reader may not be able to see.rolefollows the tone —statusfor a success,alertfor the other two — so what a screen reader does is unchanged.Giving them colour meant deciding what each message is, which turned up one that had been wrong all along: a note brought back into the source it was deleted from was announced as an
alertalongside the genuine failures, and is a success. Restored into a source that is now disconnected is a warning, and an unticked permission is a warning too — everything worked exactly as asked and a tickbox fixes it — where a server that will not have the account is an error, because trying again cannot help.Three ways out and no timer: Dismiss, touching anything else on the page, or navigating. Pointers only, so an error can be read with the cursor still in the note it is about. The undo-delete notice deliberately does none of this — a click anywhere taking away the only route back from a delete would lose work — but it does join the same stack instead of placing itself 5rem up to dodge the new-version prompt, so two notices at once sit above one another rather than on one another. The card the four of them share is one rule; only the placing and the tone differ.
Clicking a notebook no longer clears the open note when the note is in it, or in a notebook inside it. Clicking the parent of the notebook a note was in used to leave an empty editor beside the list. When the open note is somewhere else, or nothing is open, the notebook's most recent note opens instead, so a notebook with notes in it never opens as a blank pane. Deleting the open note does the same: the most recent of what is left in the notebook opens, and the pane is empty only when the notebook is.
Renaming a source tab no longer changes the shape of the tab. The field wears the tab's own box — the same padding, type and lit edge — and carries none of its own: no padding, no background, no border, no focus ring. Before this it drew a second, smaller, bordered box inside the tab and shifted the name sideways as it opened.
Nor does the tab change width. An
inputis as wide as itssizeattribute rather than as wide as its text, so swapping one in resized the tab and shoved every tab after it along the bar. The tab measures itself as it is pressed and the field is pinned to that; a name longer than the room scrolls inside it, which is what a tab of fixed width owes a long name in any case.The caret and the tab's own lit edge are what say the name is being typed. A focus ring would draw exactly the border this is removing.
Fix a notebook moved to another notebook — or out to the top level — leaving a duplicate of itself and everything under it behind, on the remote as well as in the sidebar. Moving or deleting a notebook queued one directory removal, for the outermost directory only; every directory below it had nothing naming it, so the sync that ran next adopted them as new notebooks before the push could remove them, and the removal was then refused because the device held a notebook under that path again. Each directory now gets its own op. The whole-app sync suite also runs over Dropbox now, which it did not before.
The
/menu now closes when what you've typed after the slash matches no command./nothingused to leave an empty box under the cursor. Delete back to a query that matches something and the menu opens again.The storage accounts on a device are tabs across the top of the app, named "Dropbox", "OneDrive" and "Google Drive" — with a number on the second of a provider, "Dropbox 2" — and renamable to whatever the user likes.
A device can hold several accounts at once, each its own notes, notebooks, queue and cursor, and the only way between them was a list at the foot of the storage panel. That answered "switch me" and never "which of these am I looking at", which is the question the panes below the bar depend on. Every source is up there: the live ones, the detached ones — they hold work their remote was never sent and can still be written in, so leaving them out would hide notes — and the device's own pile when it holds anything. Disconnected says so in words, not only in colour.
Pressing the tab that is already showing turns it into its own name. Enter takes it, Escape abandons it, clicking away takes it; clearing it puts the derived name back. The name is this device's, kept apart from the one the server gives: that one is overwritten on every reconcile and a rename stored there would not survive the next one. A
+at the end of the bar offers the providers the deployment has.Numbering is derived rather than stamped on at connect time, so sources already on a device get names with no backfill and letting the first Dropbox go leaves the other one called "Dropbox" instead of a "Dropbox 2" with no 1 above it. A name the user chose is never moved by that.
The sidebar gives connection management up in return. The source list and the "Connect another…" buttons are gone from the storage panel, which now holds only what is true of the source in front — how syncing is going, the devices holding its credential, and Disconnect. A detached source keeps Reconnect, which is how work that was never sent gets home rather than a way to add an account. There is one list of sources now instead of two that named them differently.
The notebooks scroll inside the sidebar rather than the sidebar scrolling as one, so the storage panel sits at the foot of the pane instead of at the end of the content, where enough notebooks pushed it below the fold. At phone width, where the pane is 14rem and there is nothing to divide, it scrolls as one as before.
On a touch screen every control is at least 44px both ways — the editor's tabs, the header and pane icons, menus, list rows, the formatting toolbar, the code-block tools, dialogs and toasts — and a task's checkbox is 24px. A mouse keeps the denser layout. Fields are 16px on touch, so iOS no longer zooms into them.
A file in the notes folder that is not UTF-8 text — saved as Latin-1 or UTF-16 by another tool, or a binary that happens to be named
.md— is now left alone. It used to be decoded anyway, arriving as a note with�wherever a byte would not read, and the next push (even the app adding anid) wrote that damage over the original. Now nothing is imported and no note stays bound to such a file, so once a sync has seen it nothing the app sends can land on it. A note with no unsent edits whose file became unreadable goes from the device; one with edits keeps them, under a conflict name, and they go up as a new file beside the one that could not be read. A sync no longer stops on such a file, and reconnecting a folder whose notes have all been re-saved that way still recognises it. Save the file as UTF-8 and it is picked up on the next sync. Listing these files in the storage panel follows separately.Text holding a NUL character counts as unreadable too, since that is what UTF-16 without a byte-order mark and most binaries look like. So the app never writes one: a NUL pasted into a note is dropped by the editor as it arrives, and again as the note is saved or a file is imported.
The storage panel now says which files it is not showing. A file in the notes folder that is not UTF-8 text is left alone, and until now nothing said so: a note whose file another tool re-saved as Latin-1 simply went from the device. Each connected source now lists those files by path under its sync status, with what to do about it — save the file as UTF-8, or delete it — and the line goes once the file reads, is deleted, is renamed to something that is not a note, or a re-scan no longer finds it. A note of yours that had to move aside because such a file has its name is reported like any other conflict copy.
The list is kept with each source's sync cursor and written in the same step, so it never names a file from a sync that did not finish, and "Re-scan from scratch" keeps it until the scan has looked. It is a notice only: nothing the app decides depends on it, and a file on it is still read again every time the provider mentions it.
For anyone implementing
SyncStore: there is one new read,unreadable(), and two newPullChangekinds,unreadableandforget-unreadable;move-foldercarries the listed paths under a folder along anddelete-folderdrops them. The store contract suite covers all of it.