A font size that could not be chosen with a mouse, a template drawing itself without an icon, and a test suite that stopped needing six machines.
Appearance
- fix — choosing a font size works with a mouse. Hovering a size previews it on the dashboard behind the panel, which is what it is for — but
applyFontSize()puts the class on<body>and the config panel reads the same six--font-size-*variables, so the panel resized too, including the button under the pointer. That is a loop rather than a cosmetic fault: the button grows, slides out from under the cursor, firespointerleave, which reverts, which slides it back under the cursor, which firespointerenter. Logged at the document with capture on: ten enter/leave pairs in a row and noclickat all.holdConfigTypeScale()copies those six values onto.config-viewbefore the class is swapped, so the panel stays put while<body>changes under it; the click and the revert both release it, so taking a size still resizes everything. The keyboard and touch never entered the loop, which is why it went unnoticed.
Config → Overview
- fix — the project's own posts keep their place in the six rows. The stream is in date order, which is right for the list and wrong for a six-row window: two hotfixes shipped in one afternoon carry two release rows and the settings they introduced, all stamped that day, and between them they fill it — so the posts, the one source that is not about a version number, fell off the page the day after they went up. Up to two of the newest keep a slot; the window is still six rows and still in date order, and a source filter reserves nothing, since the reader has already said which kind they want.
- fix — a release that brings no features no longer spends the window. Features from the two most recent releases join the stream; counted in releases, two hotfixes emptied it of everything a reader could act on. It counts the two most recent releases that actually introduced a setting instead.
- new — the font-size choice is in the stream, with a way through to Appearance.
Unraid
- fix — the Community Applications template draws its icon. It pointed at
favicon.pngin the repository root, which is not there, so the entry appeared blank. It isstatic/nextdash-logo.pngnow. Project links to nextdash.cc rather than to the source — Support and ReadMe still point at GitHub, where an issue and the readme actually live — and the fifth screenshot joins the four that were listed.
Tests
- new — the suite runs a server per worker.
workers: 1was not a property of the tests but of the harness: onego run .and one data directory meant a second worker would delete the first one's bookmarks mid-test. Each worker now gets its own port (18080 + workerIndex), its own data directory and its own server, started from a binary the global setup builds once — so nothing is shared and nothing can interfere. A full run took about an hour and a half and needed six CI machines to come in under twenty minutes. - fix — 22 specs lost their fixtures import in a merge and silently went back to sharing whatever the previous file left behind — invisible while everything ran against one server, and an immediate
ERR_CONNECTION_REFUSEDonce each worker had its own. - fix — three tests took a handle on a row in the bookmarks list and used it after the list had repainted itself, failing about one run in three with Element is not attached to the DOM. And
config-bookmarks-editornever marked what's-new as seen, so the promo card landed mid-test and shifted the Save button a few pixels — five tests in that file were passing only on retry. One of its assertions was also asking for something the app does not promise:openCountseeded into the browser's copy of a bookmark, expected back after a save the dashboard does not send it in.
v1.3.3.1 — 24 August 2026
A hotfix. Four things that only worked with a mouse or did not work at all, and the overview's news arriving while it is still news.
Keyboard
- fix — a command with an argument could not be typed.
searchActivewas set byshowSearch(), which runs from the debouncedupdateSearch(), so for the first characters of a query the app still believed nothing was open — and every guard that reads it let a key through that belonged to the palette. In:buttons cheatsheetthecopened add a category and thehwent into the name field it had just created, leaving:buttons eatsheet. The session is marked open at the first keystroke now, with the overlay still drawn at the debounce, so nothing on screen changes. - fix — a space ran the highlighted row instead of separating two words. The row takes focus and a focused row is a button, so
:buttons+ space applied the first entry. Space is text while a query is being typed; Enter still activates, which is the deliberate way in. - fix — a category's ⋯ menu opens with the keyboard. The button carries
aria-haspopup="menu"and a handler for ArrowDown, but a bare arrow starts grid navigation from anywhere and that handler runs first, in capture — so the key moved the cursor into the bookmarks and took focus off the button on the way. The grid leaves the sort controls alone now.
Fixed
- fix — a page no longer jumps to where you once were.
takeRememberedScrollis named for consuming the offset and its comment says so; it only ever read it, leaving the entry in the Map and insessionStorage. Every later visit to that page — from a tab, a shortcut, anywhere — landed halfway down with nothing on screen to explain it. It is taken from both stores now. - fix — a release that changes only wording reaches the browser.
/locales/is requested with the app-version fingerprint appended precisely so a deploy makes the URL new — andassetFingerprint()hashed CSS and JS only. A release that rewrote text and added a key therefore served the old file from cache until something else happened to change: the rewritten line stayed as it was, and the new key came back empty. The translations are part of the fingerprint now, read from disk-then-embed the way they are served, with a test that fails if a wording-only edit leaves the token where it was. - fix — the setting that marks rows which publish carries its ℹ. It sat in the field map with a default and no explainer, so the panel offered to restore a default it never named. Four languages.
The overview's news
- new — the stream is refreshed while it is still news. The server held its copy for six hours and the browser kept whatever it had for the whole session, so a post could go up and a dashboard that was open all day would not show it until a reload. The server's window is 90 minutes and the browser refetches after 30; the two are deliberately out of step, so a browser that asks halfway through still gets what the server has. Asking is nearly free — the fetch is conditional, so the common answer is a
304with no body, once per server rather than once per reader. - new — release notes open the moment a new version does. The prompt was scheduled 900 ms after boot, chosen for a prompt that might not appear at all; on an upgraded install that read as the dashboard settling and then interrupting. When the release is unread the modal's own code is fetched at once and the notes open on the next tick.
- new — four more of what v1.3.2 and v1.3.3 added are in the stream, each with a way straight to the setting: the rebuilt hover card and what it knows about a link, its three modes and the calm hover delay, the site's posts on the overview, and the two-column add form.
Tests
- fix — the suite was red on every shard, and most of it was the tests being behind. Six files described a trend chart that moved into the tile row, three a feature carousel that no longer exists, three a button bar that was given its own tab, plus an overflow filter menu that went, a
templates/config.htmlthat was deleted, a CSV that gained areasoncolumn, and an About that became a ninth section. Four specs depended on data another spec happened to leave behind, three raced the dashboard's own construction, and one seeded statistics onto a different row than it edited. All of them now pin what the product actually does — the five failures above are the ones that were pointing at something real.
v1.3.3 — 22 August 2026
The overview, rebuilt around one question: what is happening with nextDash, and what is happening in your install. It used to answer "what is new" in three places at once — a carousel showing one of forty-nine feature spotlights, a Latest update panel repeating the release named directly above it, and the project's own posts below both, starting below the fold on a 900-pixel screen. It is one dated stream now, and the figures about your own library sit beside it rather than on top of it.
Behind it, an audit of the whole tree: three paths that could lose bookmarks, a data directory that was readable without the write token, outage alerts that never left an install configured for Pushover or browser push, four keyboard paths that did not do what they promised, and a first paint carrying a preload the page never received.
Overview, rebuilt
The page answered "what is new" in three places at once — a carousel showing one of forty-nine feature spotlights, a Latest update panel repeating the release the bar above it had already named, and the site's own posts below both — while the figures about your own library took the top of the page. On a 1440×900 screen the carousel was 498 pixels of a 1451-pixel page for one item of forty-nine, and the site's posts started at 936, which is to say below the fold. It is two zones now, grouped by where things come from.
- new — one dated stream, three sources. Posts from nextdash.cc, releases, and the settings each release introduced, in one list, newest first, every row carrying a source label and its own way in: read the post, show what's new, or open the setting. Six rows fit where one sixty-word spotlight used to, which is the whole gain — not more space, six times the number of things you can act on. Source chips narrow it to one kind and back; that they can hide the site's posts is what keeps a news block in a self-hosted tool from reading as an advert.
dashboard-news-stream.js, fetched with config. - new — the stream leads the page. The act zone still comes first — nothing displaces "this wants you" — and then the stream, in the wide column directly under it. The side column beside it opens with About the developer — the project's site, its feed, GitHub and the author's own site as four addresses rather than two buttons — level with the top of the stream, since it belongs to the same zone. Under it a Your install line heads the two blocks that describe your own: At a glance, and what differs from the defaults. Figures about your own library are looked up, not met on the way in, and moving them is what lifts the stream above the fold: it now starts at 474 pixels rather than 936.
- new — a green dot and a count on Overview in the section rail, for anything published since you last read the stream. A first visit stamps the moment rather than counting everything ever published, or the badge would read 156 on day one and be ignored from then on.
- new — the drill-in: All news & features opens About → News & features, which carries the stream in full, the same chips, and the forty-two features from before the catalogue was dated. Under it, Follow it from your own dashboard saves nextdash.cc as a bookmark so Fresh counts its posts — the product following its own feed, which is also the shortest demonstration of what Fresh is for.
- new — the site feed carries a summary and ten items rather than five: the WordPress description, stripped of its markup and cut to 160 characters on a word boundary. Fetches are conditional now (
ETag/If-Modified-Since) and the result is mirrored tosite-news.jsonin the data directory, so a machine that reboots nightly does not go back out to the site every morning.DISABLE_NEWS_FEED=trueswitches it off server-wide, besideDISABLE_TELEMETRYandDISABLE_UPDATE_CHECK. - new — features carry the release they landed in (
sinceinoverview-features.json), which is what lets them take their place in a dated list. Seven are dated from the file's own history; the forty-two imported in one go stay undated on purpose rather than being guessed at, and sort to the drill-in.npm run validate:overview-featuresfails on a broken entry and reports the undated ones. - fix — the source labels sit in a column of one width. Every row is its own grid, so an
autofirst column sized itself per row —nextdash.ccis twice the width ofrelease— and the titles started at a different place on almost every line. One fixed width, the label filling it, and a longer source name added later is clipped rather than shifting the list. - fix — the carousel is gone, and with it
stepOverviewFeature,repaintOverviewNewFeaturesand its keyboard handlers: a control that needed forty-eight clicks to show what it had was never going to be used, and nothing measured whether anyone tried.
Analytics
- fix — every number an event carries is now rounded into a band. The privacy setting promises exactly that, and the two snapshots kept the promise while the action events did not: a bulk recheck reported
count: 37, a health exportrows: 1274— a figure more precise than the question needs, and on a small install a fingerprint. The rounding sits innextdashTrack()itself, the one function every event passes through, so a new event cannot reintroduce an exact figure by forgetting to bucket it. Bands are 0, 1, 2, 5, 10, 25, 50, 100,100+; a page position and a walkthrough step are deliberately exempt, being small fixed ranges that describe nothing about a collection. Pinned bynpm run test:analytics-buckets.
What's new
- new — a new version shows its release notes once, by itself. The machinery has been in place for a long time — a release token in the stub, a "seen" stamp written when the modal closes, a prompt scheduled after onboarding — but nothing pinned it, and the manual said the opposite: release notes never open by themselves. It is a contract now, with a test: an install that last read an older release gets the notes on its next visit, closing them records the release, and no later visit reopens them. A browser meeting nextDash for the first time is exempt on purpose — quick start is running, and notes for a version that reader never used would be noise.
Fixes to the rebuild
- fix — a failing feed request now says so.
/api/site-newsanswering 500 came back in the same shape as a cleared setting, so the stream reported Site news is switched off to someone who had switched nothing off. - fix — reading the stream marks it read. The marker was written to storage but not to the page, so the green dots came back on every repaint and the count on Overview kept its first number until a reload. It is now set where the stream is drawn rather than where it is fetched — loading it from the Privacy switch or from About no longer clears dots for a page nobody looked at — and it moves after the render, so the dots stay visible on the pass that draws them.
- fix — About → News & features is addressable. The tab was in the sub-tab state map but not in the list of tabs that can appear in the address, so
#config/about/newsopened the colophon and choosing the tab never reached the hash — the one sub-tab in config that could not be linked to or remembered. - fix — the drill-in fills itself when opened directly. The stream repaints the overview when it lands; About had no such repaint, so going straight there left "Loading…" on screen until an unrelated render happened along.
- fix — a post's summary was unescaped twice, so a post about
&lt;script&gt;showed<script>— the text it quoted rather than the text it wrote. The unusedencodedfield went with it. - fix — the carousel left 78 lines of CSS and seven locale strings behind, and the preview card a
wants()nobody called.
Locales
- fix — 765 unreachable strings removed from all four locale files, out of about 5,500: everything left behind by panels, buttons and whole views that had been rewritten. Found with a new
npm run validate:locale-unused, which is deliberately conservative — a key counts as reachable when its name appears anywhere in the source, when it fits a template a call site builds (t(\config.${field}Label`)), or when it belongs to a family looked up by a value, like a theme id or a layout preset. Two more checks keep it that way:validate:locale-parity` fails if the four files stop carrying the same keys, and a spec walks every config section and sub-tab, plus health and inbox, failing on any text that reads as an identifier rather than a sentence. - fix — the 199 strings the feature catalogue reaches came back. The scanner skipped every directory called
data— meant for the runtime one, which says nothing about the source — and tookstatic/datawith it, whereoverview-features.jsonnames four locale keys per feature and is the only place they appear. They were reported as unreachable and removed, which left About → News & features drawing all fifty features from their English fallbacks in Dutch, German and French. The skip is by path now, and the three checks that would have caught it — the catalogue, locale parity, duplicate keys — run in CI rather than only when someone remembers them.
Menus
- fix — the Pin entry in the context menus is drawn, not typed. Every other entry is a glyph tinted with the accent colour —
⧉,✎,→,◉— and Pin was 📌, painted by the system font in its own red and yellow on every theme, and deaf to the red a destructive row is given. It is a small SVG incurrentColornow, shared by the grid's menu and the config list's throughjs/shared/menu-icons.js, which is also the only place markup is ever assigned into a menu's icon slot. The pin on a config row and the paste-choice card still carry the emoji: they are labels rather than menu entries, and are left for a separate pass.
The bookmark form
- new — the add/edit form is two columns on a window wide enough for them. Eleven fields stacked at full width made it 735 pixels tall to add and 763 to edit; a 1366×768 laptop leaves about 660 once the browser has taken its share, so the form scrolled and the save row sat below the fold on the machine most people use. The fields fall into two groups of nearly equal height — what the bookmark is (name, address, icon, note) and where it goes and how it is watched (page, category, tags, shortcut, pinned, availability) — and side by side the dialog comes to 435 pixels, adding or editing alike, which fits a 640-pixel window with room over. The columns are real elements, so Tab still runs down one and then the other; below 860 pixels of window they dissolve and the single column returns unchanged.
- new — the form explains itself without printing the explanations. Pinned and the three availability pills say what they are, not what they do, and their descriptions lived in a hover title and behind an info button — no use at all on a touch screen. Each now opens a bubble on hover, on focus and on tap: Pinned says what pinning does, a mode pill describes that mode, and the i names the choice and describes the mode currently set. A screen reader gets the same sentence whether or not the bubble is open.
- fix — a warning no longer moves the form. Every message the form could show — a shortcut already in use, a letter the grid also claims, Name is required, Valid URL required — appeared under its field as you typed and pushed everything below it down. On an empty Add form one keystroke in the name was enough to make the address complain and shift the column by nineteen pixels. They are all the same bubble now, in the error colour, with the field keeping its red border, and they open only once a field has been used or a save has been refused — the form no longer greets you by pointing out that you have not filled it in yet. Each message is a live region, so a screen reader still hears it as it appears.
- new — a phone gets the short form. Icon and Note are hidden below a fine pointer: four buttons for a favicon that is fetched from the page anyway, and a textarea under a keyboard that covers half the screen. They are hidden rather than dropped, so editing a bookmark on a phone leaves the note and the icon it already had exactly as they were. The two columns are a fine-pointer layout too — a landscape phone is nearly 950 pixels wide and is still a phone, where one column you scroll beats two you pinch at.
- fix — adding a bookmark starts in the address field rather than the name. The address is the one thing only you can supply; the name usually arrives with the page title. Editing still starts on the name, which is what is nearly always being changed.
Your data
- fix — an import that carried no bookmark page emptied the library. Committing an import deletes every bookmark page the archive does not name, so a ZIP of unrelated-but-valid files — settings, themes, icons — cleared
data/and still answered200. An archive with nobookmarks-*.jsonin it is refused now, before anything is replaced. - fix — merging duplicates could delete a bookmark that was not part of the merge. Two identical source refs were both validated against the pre-merge snapshot and then removed one after the other from a slice that had already shifted, so the second delete took an innocent neighbour — and the keeper's open count was added twice.
- fix — a failed delete left the page holding every removed bookmark twice. The error path re-inserted bookmarks that the save had already rolled back; it restores the pre-delete copy instead. The inline editor had the same gap the comment above it already promised was closed: a write that failed still recorded a trash entry, still said deleted, and offered an undo that made a real duplicate. The tag filter's rollback repairs
allBookmarkstoo, having no snapshot of its own. - fix — a save from a tab that was open before a bookmark started failing no longer erases the down since history:
BrokenSinceis carried across the save besideLastError.
Access
- fix — the data directory is no longer served whole.
/data/was a plain file server over the data directory, directory listings included:settings.json, everybookmarks-N.json,inbox.json,trash.json,health-history.jsonand the automatic backup ZIPs could be read without the write token, while/api/backuphands out that same content only behind it. Onlydata/icons/and an uploaded favicon or font are served now. Icons are content-named and never reused, so they go outimmutableon the way past — they had no cache header at all, and there is one per bookmark. - fix — the trash validates what it takes in. A restore splices the stored bookmark straight back onto its page, so trash-then-restore was the one route that could keep a
javascript:URL, a private address with allow localhost bookmarks off, or a client-chosen icon path — the hole the inbox closed for itself a release ago. - fix — listing and downloading an automatic backup need the write token, the way running, restoring and deleting one already did. Both client paths already carried it.
- fix —
/api/health/check-urlapplies the SSRF rate limit every sibling that fetches a caller-supplied URL applies. On the default install there is no write token, so that limit was the only thing in front of it. - fix — an
X-Forwarded-Forvalue that is not an address can no longer become a rate-limiter key. - fix — Send test alert worked on an install with a write token. The button did not go through the token-carrying fetch, so its handler answered a flat
401— on exactly the installs most likely to be alerting on something.
Monitoring
- fix — outage alerts went silent on Pushover and browser push. Alerts were dropped whenever no webhook URL was set, before a single transition was looked at — but Pushover is configured with an application token and a user key against a fixed endpoint and never sets that URL, and browser push is an independent sink reached before the webhook target is read at all. So an operator who picked the preset for which the config UI does not even render a URL field, or who only switched push on, got no alert for an outage of any length. Send test alert does not pass through that gate, so the setup looked correct. It is gated on the sinks that actually resolve now.
Keyboard
- fix —
,,+and&can be typed again. The three shortcuts sat above the printable-character branches without the not while searching guard the key right below them already had, so they could never reach a search field, a filter, or a:new https://x/?a=1&b=2command — where&opened the quick-add omnibox on top of the command palette. - fix —
:columnsno longer strips a view of its layout. It assigned the grid's class list outright, and that element is shared with inbox, health and config: running the command over one of those took its layout class with it, and on the grid it dropped the density, layout and packed/masonry classes. It goes through the sync helper that guards exactly this. - fix — Tab no longer stops at the wrap-around. The focus trap tried to focus the search overlay's clear button, which carries
[hidden]while the query is empty — a silent no-op, after the trap had already swallowed the key. - fix —
g+ a digit lands on the first row that is really there. It jumped to the first row in document order, so with a category's first bookmark filtered out the jump aborted without a word.
Browser extension
- fix — a duplicate lookup that rejected — a proxy error page, an auth redirect — surfaced as an unhandled rejection in the service worker, and neither the keyboard command nor the context-menu entry awaits it. Both routes have the badge as their only feedback, so a save that failed said nothing at all.
- fix — one shared hide timer for the popup's message. Each message set its own without cancelling the last, so a saved shown five seconds after an error vanished a tenth of a second later.
- fix — an IPv6 host was bracketed twice —
[[::1]]:8080— so the extension's duplicate key never matched the server's, which is what the comment above it says it does. - fix — the shared bookmark form's preview module is back in step with the dashboard copy; the two are required to be identical and the extension's had stayed behind by a feed line.
Speed
- fix — the font preload never reached the page. It sat inside the block the CSS bundle replaces with a single
<link>, sorel="preload"appeared zero times in the served HTML and the font was discovered only after the stylesheet had downloaded and parsed. - fix — packed masonry reads every height, then writes every span. Interleaving the two forced a full grid re-layout per category on every render — and continuously while the window was being dragged. The measurements are unchanged; only their order is.
- fix — bookmark and category icons decode off the main thread. One synchronous favicon decode per row during paint is visible jank on a large page; the preview card already did this.
- fix — the generated theme stylesheet is built in a buffer. Around 150 blocks appended one at a time reallocated and copied the whole 76 KB string on every append, on every dashboard load.
Under the hood
- fix — the inbox's newest-first order is stable, with insertion order as the tiebreak. Timestamps have millisecond resolution, so items written in the same millisecond — a seeded inbox, an import, a batch the extension replays — sorted differently from one read to the next, and at capacity the trim evicted whichever tied item happened to fall last rather than the one added first.
- fix — two leaks: the rate limiter kept an entry for every client address seen since boot, and a host looked up once and never revisited kept its pinned IP for the life of the process. Both are swept opportunistically, under a lock that is already held.
- fix — a capture that pushed the inbox past its cap stranded one favicon in
data/icons/per capture. The evicted items are returned precisely so the caller can clean up their icons, and the caller was dropping the list. - fix — naming an automatic backup is bounded. A permission or I/O error made every candidate name look taken, and the loop only ended on file does not exist — spinning forever while holding the lock restore and delete also need.
- fix — the first update check runs inside its own goroutine, where every other scheduler in the tree already runs it. Called before the listener started, it held boot for the full ten-second timeout whenever egress to
api.github.comis blocked — long enough to fail a container health check. - fix —
Varyis added rather than set, so theAccept-Encodingthe compression middleware had just written survives. The ETag is computed over the uncompressed body, so a shared cache in front could otherwise hand gzipped bytes to a client that never asked for them. - fix — the asset-hash generator writes formatted Go, and the twelve assets these fixes touched carry their new hash. On a production build the map is read from the embed rather than recomputed, so a returning browser would have been handed the URL it already had cached and gone on running the old JavaScript.
Docs
- new — the audit is written down where someone would look for it. The protected-endpoint table names the automatic-backup routes and the health view's check-a-URL; the self-hosting section says what
/data/publishes and what it no longer does; Data & backups says an archive with no bookmark page in it is refused, and the trash section says what is checked on the way in. Config → Help carries the same three points, in all four languages. - new — README and MANUAL describe what is actually measured. The analytics section had been written before the content snapshot, the walkthrough and tip events, Fresh, category spreading, right-click menus, and two thirds of what Health reports; all of it is listed now, alongside the fact that a bookmark can also be opened from the health view. The claim that counts are bucketed is true again — see above. The inline-editor passages in both files described a full-page blur that no longer exists, the search filters left out
status:feed,Shift + Vwas still documented as a toggle rather than the pinned card it opens, and Config → Statistics was described as it stood before v1.3.1.
v1.3.2 — 21 August 2026
The hover card, rebuilt. It was seven stacked divs that printed the address twice, hid the one state worth acting on, cut itself off at 360 pixels without saying so, and chased the cursor while claiming to be reachable — on a dashboard where the row underneath already showed more than the card explaining it. It is three bands now, in a fixed order, each absent rather than blank; it knows what the rest of the app knows about the link; and how it is reached is a choice rather than a switch.
Link preview cards
- new — the card is rebuilt around three questions: what this is, what it says, and what you know about it — a header with the favicon, the title, one address and a status pill; then the picture, the fetched description, your note and the tags; then a facts strip. Every band is absent rather than blank when it has nothing to carry, so a bare link is a short card instead of eighty percent empty space. The address is stated once: the old card printed the full URL in mono and then its hostname on the next line, two of seven rows saying the same thing, neither clickable.
- new — the card knows about nextDash. Last check and ping, uptime over thirty days, a certificate about to expire, the Fresh count, opens and last opened, the shortcut and the page it lives on. All of it read from what the app already holds in memory — hovering a row still makes no request; only a card you asked for may fetch the health figures, once.
- new — peek and pinned are separate. Hovering gives you something to read: the card takes no pointer events and carries no buttons, so there is nothing to aim at and no reason for it to move.
Shift + Vpins it — focusable, with Copy, Refresh and Edit in a footer, closing onEscapeand handing focus back to the row. - new — three ways to reach it, not a switch. Off, On hover (the new default) and Keyboard only, for people who want what the card says without a panel appearing under the pointer — their only answer used to be off, which throws away the whole feature to avoid one behaviour of it. Beside it a checklist of the eight rows the card may draw, and the card itself, drawn from one of your own bookmarks and redrawn as you tick.
- new — uptime and an expiring certificate show on hover, not only on a card you pinned. The dashboard already fetches the whole health report on every load to put a number on the health icon — a row per bookmark, carrying monitor stats and certificate expiries — and read twelve counts out of it before dropping the rest. The badge keeps the four facts worth carrying now (uptime over thirty days, certificate expiry, how long something has been failing, and the error), keyed by canonical URL, and the card reads them. No new request: a hover still costs nothing, and the pinned-mode fetch is left as the fallback for a dashboard with the health icon switched off. The health view's own report wins when it is open, since it is the fresher of the two, and a refresh there updates what the cards quote.
- fix — the card no longer chases the cursor. It was repositioned on every pixel of
mousemovewhile claiming to be reachable, so moving toward the refresh button moved the card away, and on a dense grid it slid across the rows underneath. It is anchored to the row it describes, which is what the keyboard path always did. - fix — "never opened" is a row.
if (openCount > 0)hid the usage line, so the most interesting state a bookmark can be in — you saved it and never went back — rendered as nothing at all. - fix — a dead thumbnail leaves no gap. The image was hot-linked with no
onerrorand its wrapper shown before it loaded, so a rotted og:image drew the broken-image glyph in a 150-pixel band — in the one app that exists to notice rot. - fix — nothing is cut off silently. The card was
max-height: min(360px, …)withoverflow: hidden: a picture, a title, a description, a note, tags and three more rows passed that easily and the last rows simply were not there. The blocks clamp themselves now, so the card is only ever shorter for a reason you can see. - fix — the note is your note. It was rendered in the same tertiary grey as the site's marketing copy and truncated twice — cut at 140 characters in JavaScript, mid-word, then clamped to three lines in CSS. It is labelled, marked with a rule in the accent colour, and clamped once.
- new — the hover delay starts calm, at 250 ms rather than 150. A card that opens the moment the pointer crosses a row opens on every row you were only passing over on the way somewhere else — and with the card now carrying six kinds of information, the cost of opening one you did not ask for is higher than it was. Fast and Balanced are still there. An install that has already chosen a delay keeps it.
- fix — the hover delay offered delays it would not accept. The list read Instant, 200 ms, 400 ms, 700 ms, 1 s while the code took 100, 150 or 250 and quietly rewrote everything else to 150: four of the five options did nothing. It offers the three that work.
- fix — turning cards on no longer takes the description away from screen readers. The card removes the row's
titleso the browser tooltip does not sit on top of it, and hovering is mouse-only — so assistive tech was left with nothing. The text moves to a hidden element the row points at witharia-describedby, and a pinned card is a real dialog. - fix — the payload the card draws was assembled by hand in three places — hover, the keyboard toggle and the refresh button — so every new field meant three edits, which is why there were none. One
buildPreviewPayload()now, and the sample in the setting is drawn by the card's own code rather than a mock-up free to drift from it.
Fixes
- fix — an edit no longer resets a bookmark's statistics. The dashboard saves a page by POSTing the whole list back, built from what the browser had in memory — so anything the server had written since the page loaded was silently absent from it, and the save wrote its absence. Open a bookmark, then edit any bookmark on that page, and the open count went to zero; the last check, its error, the fetched preview text and the certificate host went the same way.
carryServerOwnedBookmarkFields()puts back every field the payload does not carry, matched on the canonical URL. A value the payload does carry still wins, so an import keeps its own counts and a check still clears an error it has just re-tested. - fix — "Show in Health" lands on the row you asked for. The health list selects the row under the pointer, and a row arriving under a cursor that never moved is not a hover — but the browser reports it as one. Opening a bookmark in Health left the pointer wherever the menu item had been, the list drew beneath it, and the row you asked for lost its selection to whichever row landed there. Hovering now has to be earned by an actual
pointermoveafter a row is focused by name; the same applies to a?hv_id=deep link. - fix — the inline editor takes the clicks aimed at it in Safari. Every non-editing row's children carried
filter: blur(), which gives each column its own compositing layer — and WebKit hit-tests those in paint order rather than by z-index, so a blurred neighbouring column was painted over the tall inline form and ate every click on its fields.pointer-events: nonedoes not help; WebKit hit-tests the layer regardless. At 18% opacity the blur was doing almost nothing visible, so it is gone — the same fix that settled this bug class in the check-mode popover. The rows stay dimmed. - fix — the health badge fetched the entire health report on every dashboard load — a row per bookmark with names, tags, scores and reasons, plus the duplicate groups, the daily trend and the fleet view — to read twelve counts out of it. It asks
?view=factsnow: the counts plus only the bookmarks that have something to report. Measured against a real library: 8,424 bytes down to 458. The health view still loads the full report, which is the one screen that draws it. - fix —
bookmarksLoadingandbookmarksKeysHintwere each written twice in the same locale object, in all four languages. A JSON parser keeps the last one and says nothing, so the earlier line was a string that looked live and could never render.npm run validate:locale-duplicatesnow fails on any key written twice, reported by path and line — the check that would have caught the three delay labels added and withdrawn during this release. - fix —
:preview offfollowed by:preview onno longer moves someone who chose keyboard only onto hover. The command is a switch, so it puts back the mode it took away. - fix — the twinkle that marked this release's new setting still pointed at Appearance → Layout, two releases after the setting it introduced. A mark that outlives its release teaches people to ignore the mark, so the trail is cleared until the next one earns it.
Config
- new — Appearance → Display → Link preview cards is a panel of its own: the mode as three labelled choices, the hover delay, the eight-row checklist, and a live card drawn from one of your own bookmarks — the setting you otherwise had to leave the screen and hover something to understand. It names Bookmarks → Link preview and Health, where the fetched text and the bookmarks without one are managed, so a reader landing on any of the three finds the other two.
- new — Config → Overview carries the card as this release's spotlight, with what it is, how to reach it and the way through to the setting.
Docs
- new — the Appearance help article gains a Link preview cards section: the three bands, the three modes,
Shift + V, and the checklist — in English, Dutch, German and French. The README's line about a card that "shows full URL, open count, and last-opened date" described a card that no longer exists, and the MANUAL's compatibility table said link previews were off by default. - fix —
linkPreviewDelayInstantandshowLinkPreviewCardsLabelwere locale strings for controls that no longer exist; both are gone from all four languages. Three delay labels were added and then dropped again when it turned out the file already carried them a few lines further down — a duplicate key in the same object, where the later one silently wins.
v1.3.1 — 20 August 2026
Fresh, made to work. It shipped in 1.3.0 depending on a setting most installs have switched off — link previews — so it could never find a feed, and a reader who turned it on watched a dashboard that never changed. This release makes it look for its own feeds, say what it found, and answer which bookmarks take part at all. Plus the modifier click every browser honours, given back.
What's new
Fresh
- new — which bookmarks take part in Fresh, three ways. A row with a feed and nothing new was identical to a row with no feed, and the count on the tab said how many there were without saying which. The bookmark editor now shows a Feed line with the address when there is one — the place to look when a bookmark never says anything.
status:feed(and-status:feed) narrows a search to the ones taking part. And Behavior → Fresh → mark rows that publish puts a quiet dot on those rows even when nothing is new; off by default, because a mark on twenty rows that are silent most of the time is the noise Fresh exists to avoid. A real count still wins over the dot. - new — Fresh is a tab of its own under Behavior, between Inbox and Status & health. It was the fourth panel down a tab about link checking, which answers a different question — is this still there against has this moved on — and is where a reader stops looking. Behavior is seven sub-tabs now.
- fix — Fresh looks for its own feeds. It learned where a feed lived only as a side effect of fetching a link preview, so an install with link previews switched off never fetched a page, never discovered anything, and answered “nothing new” forever — which is exactly what the reader who reported this saw, with the feature on and
feeds.jsonempty. Switching it on now reads the head of each saved page itself, records the pages that have no feed so they are not asked again for a month, and polls what it found. The panel says what came of it — 7 of 7 bookmarks asked · 0 publish a feed, and in that case a sentence saying so is not a fault — with Find feeds now beside it to repeat the round. - fix — Fresh polls on an hour of its own instead of borrowing the health recheck interval, which can be switched off entirely while Fresh is on — and was, on that same install — and runs as slowly as once a day. Every request is conditional, so an hour is unremarkable for the site on the other end.
- new — Fresh is offered rather than hidden. It is off by default and its switch sits four panels down a tab called Status & health, so the people it was built for never met it. A corner card now offers it once — skipped only on an all-but-empty page, and never shown to someone who already has it on — and hands over to a four-step walkthrough rather than switching anything on: what the count means, the collection it gathers into, why polling costs almost nothing (the feed was already found while a preview was fetched, and every request is conditional), and what it deliberately is not. The last step is the switch itself, because a walkthrough that ends in “now go and find the setting” wastes the one moment the reader is convinced. It is offered before those two, since it is the one thing here nobody has had the chance to meet. Same shape as the side-rail and spread-across-columns cards, and the same walkthrough is one button away in Config → Help → Monitoring, where Fresh now has a panel of its own with a state line saying whether it is on for you. No article list, no titles, no OPML — deliberately not a feed reader.
The dashboard
- fix —
Ctrl/Cmd+click on a bookmark opens it in a new tab, as it does on every other link on the web. It used to tick the row for a bulk action, withpreventDefault— so the one modifier every browser honours did the opposite of what it does everywhere else, and on a Mac it came with the row menu on top of that,Ctrl+click being the platform's secondary click. The mouse route into a selection isAlt+click now, on the grid and in the health view alike;Shift+click still extends a range,xstill ticks the row under the cursor, and Select is still in the right-click menu. The open is recorded by the row itself, since letting the browser have the click means the anchor's own handler never runs — without that, opening this way would not have counted, and Fresh reads the same timestamp.
Statistics
- new — the section says what its numbers mean. What this says opens Overview with the three things that follow from the figures — where your opening lands, what is going unread, what is not answering — each with the button that acts on it, and nothing at all when there is nothing to report. A count you cannot act on is trivia.
- new — the tiles carry a direction. The daily points the health report has been recording all along now also hold how many bookmarks were untagged and how many opens there were, and Statistics reads them through a new
/api/health/trend— a file read of a few dozen points rather than the full report they used to ride with. A tile says+7 this weekwhen it moved, and nothing when it did not:+0is noise dressed as information. Narrowed to one page, the comparison is dropped rather than invented. - new — the activity chart in a sentence, above the bars: how long the window is, where the busiest point sits, and how many bookmarks it accounts for. A screen reader had thirty numbers in a table and no shape; everyone else had to hover for the peak.
- new — most-opened and category rows hand off to the bookmark list the way the tag rows already did, and each tab carries a 🔗 that copies a link to it. Statistics has been deep-linkable all along and nothing on the page said so.
- fix —
computeStats()walked every bookmark five times over on every paint — a tab switch, a scope change, the Overview cards, the export — for figures that cannot change between two paints of the same data. It is computed once per paint now and shared by the six callers, and dropped at the start of the next one, since a bookmark's own fields are edited in place all over the app. - fix — the CSV carries every tab. Health and Inbox come from the server and load when their tab is opened, so an export from Overview silently left two of the five tabs out of a complete-looking file. It waits for them now, and writes
inbox_included,0if a fetch failed rather than dropping the rows without saying so. - fix — the note explaining that Inbox and Health cannot be narrowed to one page no longer appears on a single-page install, where there is no narrowing to explain.
Health
- fix — the row menu is as tall as its contents. It capped at
min(70vh, 24rem)— 384px on any window — so a row whose repair options ran long put a scrollbar inside a context menu, and a menu you have to scroll is a menu whose last item nobody finds. Sprawl was the reason for the cap, and placement answers that better: the menu flips above its trigger when there is no room below, and the cursor path clamps it inside the viewport. The window is the only cap left.
Bookmarks
- new — the filters are in the address bar. The list could be narrowed five ways and the hash knew only the page, so "the 41 untagged on Work" was something you could see and not hand to anyone, and a reload threw it away. Query, category, tag, cleanup filter and sort now ride in the hash, and a link opens the list it describes. An unfiltered list keeps the short hash it always had.
- new — an empty list says which filter emptied it. It blamed your search whatever was actually narrowing — a cleanup filter with nothing in it, a page with no rows, a tag nobody uses — which sent people looking in the search box for a query that was not there. Each filter that can empty a list now says so in its own words.
- new — a selection survives a filter change, so "tick the untagged, then also the duplicates" is possible at last. The bulk bar is honest about the consequence: 12 selected (3 not shown by this filter), because acting on rows you cannot see is exactly the surprise the old clearing was there to prevent.
- new — undo for bulk tags, pins and availability, on the eight-second toast the bulk delete already had. Forty rows retagged could not be picked apart by hand, and
replacecannot be reversed by reasoning about what it did — so the undo restores the pages as they were rather than inverting the edit. - new — the three orders people use are chips, not options five and seven of a menu: page order, recently added, most opened, with the rest still in the select. Beside them, Changed in the last week — the question after an import or an afternoon of tidying, which every bookmark has carried the timestamp for and nothing could ask.
- new — the keys the list answers to are written under the toolbar:
j/k,x, Enter, Esc. They worked; nothing said so. - new — the list draws a screenful, not the whole library. Infinite scroll answered how much is fetched and nothing about how much is painted: a row is thirty-four elements, so a thousand rows is thirty-three thousand nodes and four megabytes of markup, and five thousand is a third of a second of layout on every repaint — a tag added, a row ticked. The rows near the viewport are drawn and the rest are two spacers of the right height, so the scrollbar still describes the whole list and nothing jumps: two thousand rows go from sixty-seven thousand nodes to about a thousand, and the build from 88 ms to 3 ms. Off below a hundred and twenty rows, where the arithmetic would buy nothing, and off while a row is expanded into its editor, whose height the spacers cannot know.
j/kwalk the loaded rows rather than the drawn ones and drag the window with them, and focus is put back on the row it was on — otherwise closing a menu would drop the list's keyboard navigation. - new — the bookmark list is its own file,
dashboard-config-bookmarks.js, fetched when config opens rather than carried by every visit to any other section: twelve methods and four hundred lines that draw rows, the bulk bar, the tag cloud, the chips and the crumbs. Same prototype, same methods, moved verbatim — only the moment they arrive has changed. Opening the section waits for them rather than flashing a placeholder and repainting over it, which would tear down whatever the reader had already reached for; the placeholder is what a failed fetch leaves, and it says the list is on its way. Split one section at a time on purpose: lifting the whole of config out at once failed on forty-one tests, and the failures did not name the method that had moved. - fix — typing in the search box no longer throws away how far you had scrolled. Refining a query is a narrowing of what you are already looking at, and being thrown back to row fifty after four hundred rows — for one letter, sometimes a letter you then delete — is what makes a long list feel hostile.
- new — one answer to “which bookmarks”.
status:untaggedin the search bar and the cleanup filter Without tags here are the same question, and they were written twice, so they were free to disagree: a tag that is nothing but spaces counted as a tag in one place and not the other, and the statistics panel had a third opinion about a note of whitespace. Fourteen of those questions — untagged, noted, never opened, opened once, insecure, no icon, changed, stale and their negatives — now come from one registry,bookmark-predicates.js, loaded before the search bar, the config list and the statistics. Only Duplicates stays local: it is the one that has to know about every other bookmark's address. The search bar also learnedstatus:feedandstatus:unfedon the way in. - fix — the visible-rows memo is dropped at the start of each paint of the list. Bookmarks are edited in place all over the app, and none of that moves the array identity it keyed on, so the list could show a filter that no longer held.
Docs
- new — the Fresh walkthrough and its help article answer the question every reader actually arrives with. The last step is If nothing shows up — an empty dashboard is the usual answer and not a fault, because most saved pages publish nothing — and both now name the count on the tab (12 of 40 bookmarks asked · 2 publish a feed) as the way to tell that apart from a broken feature. The article also says plainly that a row carries no mark for publishes, but nothing new: a badge on twenty silent rows would be the noise Fresh exists to avoid.
- Help describes the config that exists. Behavior said five sub-tabs and has six — Search and Inbox are separate now, and its Status & health section had grown from three panels to seven without the prose noticing Fresh, downtime alerts, browser notifications or maintenance windows. Appearance sent readers to Behavior → Layout for the grid, a tab that no longer exists, and to Date & time, which is Date & weather. Data & backups described a single Maintenance panel where there are now Icons & previews, Server log and Reset. The section rail listed eight sections in the wrong order and omitted About; Bookmarks never mentioned its Settings tab. Four languages, plus the drawing caption that still counted five sub-tabs.
v1.3.0 — 19 August 2026
Twenty-four changes across 148 files, +14,585 / −3,343 lines, and one subject running through all of it — the life of a link. How it gets in when you are not at the desktop that has the extension; how you find out it has rotted; how the tidying becomes something you can finish rather than a number you learn to ignore; and how a page you saved tells you it has published something. The fourth strand is speed: the dashboard's first load is about five times lighter than it was.
What's new
Capture — from wherever you are
- new — PWA share target. Installed as an app, nextDash appears in the phone's share sheet; a shared link lands in the Inbox and the app opens on it. Android often puts the address inside the shared text rather than in the URL field, so the first
http(s)address in either is used, with trailing sentence punctuation left to the sentence. - new —
GET /add?url=…&title=…, and a bookmarklet built for you in Config → Help → Inbox carrying this install's origin. Both routes are plain GETs because neither a share sheet nor a bookmarklet can set a header; on an install with a write token,NEXTDASH_CAPTURE_TOKENopens these two routes and nothing else. - new —
integrations/: a shell script, two Raycast commands (one for a URL, one for the frontmost tab in five browsers), a Dropzone action, a Ulauncher extension, and Alfred and Apple Shortcuts recipes. No binary bundles — steps you can read instead. - new — manifest shortcuts for Inbox, Health and Config, so a long-press on the installed app opens the view rather than the dashboard.
- fix — duplicate detection looks everywhere, not only at the page being saved to, and answers with the conflicting bookmark's name, page and category rather than a bare 409. Same page is refused; another page asks, with the existing bookmark as a link and Save anyway to file a second copy. Moves send
allowDuplicate, since a move is an add followed by a delete.
Link rot
- new — broken since: the first failure of a run is recorded, so a row can say failing for three weeks rather than only broken. Carried into the filters, the row, the export and the report.
- new — soft 404 detection: a monitored check can spot a page that answers 200 while saying not found. One bounded body read per check, which is why it is a switch beside the check timeout rather than always on.
- new — group by site: one host down reads as one problem instead of forty.
- new — archive fallback: point a dead link at its last Web Archive capture, keeping the original address in the note.
- new — history days: the trend can be read over a longer window, and a window the samples cannot fill says so rather than pretending.
- new — Rot report in the toolbar: what has gone, moved or been rewritten, has been failing for over a month, is broken and never opened, and what broke this week. Everything in it was already in the report; what was missing was somewhere it added up.
- fix — the Rot report button was never styled, and rendered as a bare user-agent button between four styled ones.
Cleaning as a ritual
- new — a card in the corner of the dashboard names what is waiting — “10 links to review: 4 broken, 3 never opened, 3 not opened in a year” — and starts a bounded session over the worst ten in the health view's Work through. Below five waiting links it stays quiet.
- new — the session ends: a count of what was dealt with, Another ten while more are waiting, and Done for today which puts the offer away until the next local day. Skipping is not handling, so the count is honest.
Fresh
- new — a page's
<link rel="alternate">feed is noted while previews are fetched, polled on the background re-check interval with a conditional request, and a bookmark carries a count of what has been published since you last opened it. A Fresh smart collection lists them newest publication first. - new — opening the bookmark clears the count;
lastOpenedis the whole of the read state. No article list, no titles, no OPML — deliberately not a feed reader. - new — off by default, under Behavior → Status & health → Fresh; switching it on polls once immediately rather than waiting for the scheduler.
The dashboard, in place
- new —
Shift + Ffilters the page you are on: a slim bar narrows the rows, hides the categories left empty, and keeps the layout, the cursor and any selection. - new — the scroll offset is remembered per page, across a view change and a reload, for as long as the tab is open. Switch it off under Behavior → General.
- new — a selection can be pinned and switched to Periodic or Monitor in one action, and a bulk tag change can be undone for eight seconds.
- new —
Shift + Alt + ← / →moves the focused bookmark into the category beside it; smart collections are skipped, being a query rather than a place. - new — what typing a bookmark shortcut does is a setting: open the moment it matches (default), open after a short pause, or press Enter. Inbox settings moved to a tab of their own, and Search gained the three-card control with an interactive ℹ.
- fix — the default is open the moment it matches again, and v1.2.0's change to press Enter is recorded here as a mistake. The reasoning in v1.2.0 was that an instant shortcut can swallow an ordinary word, and which words survive depends on the shortcuts you own. That is true, and it is the rare case: what the change actually did was charge every shortcut, on every use, a second keystroke to prevent it — which takes away the reason to have a shortcut at all. The collision has two answers that cost nothing when it is not happening: the two other modes, and renaming the shortcut that clashes.
- fix — the new default reaches existing installs.
shortcutOpenModeis written into every settings file v1.2.0 touched, so a default change alone would have reached nobody.migrateShortcutOpenModeDefaultInstantmoves installs still carryingenter— the value nobody chose — toinstantonce, behindshortcutOpenModeInstantMigrated;delayis left alone, because it can only be there because someone picked it, and the marker means anyone who sets Enter back keeps it through the next restart. - fix — the category header's glyph follows the favicon harmonisation setting, instead of staying at full saturation beside recoloured icons.
- fix — the filter bar is placed above the grid rather than inside it. As the layout's first child it was fine while the layout was a CSS grid and wrong in every other shape: packed columns lay it out as a flex row that does not wrap, so the bar became a column of its own — squeezed to a fraction of its width and pushed off the left edge of the window (x = −21 on a three-column page). It also kept a search field inside
role="grid"and gave masonry a child that is not a category.
Config
- new — Config → Help → Tips filters itself. Fifty-three tips in nine groups, and the search in the header returns whole panels — the wrong grain when you are after the one key that does the thing. The field narrows to the line, counts what is left, says so when nothing matches, and hides a group whose rows have all gone. Rows are hidden rather than re-rendered, so the cursor stays where you are typing.
- new — a topic that continues on another tab says so. Health and Monitoring answer different questions on purpose, but the split left no thread: seven panels now carry a Continues in line — availability to uptime and alerts, working through the list to drift, the walkthrough to expected responses and maintenance windows, and back. The button writes the same
#config/help/<tab>/<panel>hash the panel's own link copies, so clicking it and following a shared link land in the same place. - new — the Server log panel says whether it is on for you, like the four panels that already did. It is off by default, which is exactly when a reader follows the prose and finds nothing where it says. Fresh deliberately has no such line: it is a paragraph inside the monitoring prose rather than a panel, and hanging the line on a panel about something else would say something untrue.
- new — the version panel reads the release index instead of carrying the number in four translated strings. It read nextDash 1.1.0 for two releases after 1.1.0, because the fourth file was the one nobody remembered. The ★ modal, Overview → Latest update and this heading now agree by construction.
- fix — on a narrow screen, a config section reached by hash or by a restored visit is scrolled into view properly. The strip carries
scroll-snap-type: x proximity, which re-snapped the nearest-fit scroll to the closest button boundary and left Help hanging 36px over the right edge with 112px of scroll still available. It scrolls the strip itself now, aligned to the start as the snap expects. - fix — Help opened with empty panels when its prose did not arrive. That text is a locale scope of its own — a third of the translation file, fetched beside the config module — and every panel falls back to nothing, so a page of headings with no text under them was the failure mode. A core locale load replaces the whole bundle, so one landing after the help scope had merged (a reload onto a
#config/helplink, a settings sync while config is open) wiped it, and switching help tabs repainted from the same empty bundle. The core load now keeps the help keys when the language has not changed, and every help repaint asks the bundle whether the prose is really there rather than trusting a flag. - new — Appearance → Button bar is one tab for one object: where the bar sits (the five positions, each drawn) and what it carries (main buttons and extras, with Show all / Hide all and a count). The position was a panel on Layout and the twelve toggles were two panels on Toolbar & tabs, two tabs further along, so moving the bar and hiding a button on it were separate errands. Toolbar & tabs keeps the Header group, which is a different strip. Everything that pointed at the old homes now points here: the settings search, the side-rail card's Open button bar settings, both Overview → New features entries, Help → Appearance, the manual and the README.
- new — the settings search matches a setting's current value as well as its name, and shows it after the location, with switches read as On/Off.
- new — settings that stay server-wide while Keep settings on this device only is on carry an all devices mark; the switch itself is marked this device.
- new — Duplicate a page (with its categories, and optionally its bookmarks) or a category (with its width, icon and sort). Copies start with empty check and open history, and without the original's shortcut.
- new — edit a name or shortcut in the row: double-click the title or click the shortcut pill — including the faint + on a bookmark that has none. A shortcut already in use is named as you type it and refused on Enter, checked across every page.
- new — a tag in Most used tags opens Config → Bookmarks filtered to it.
Speed
- new — 99 scripts and 42 stylesheets are served as two bundles, the generated theme is inlined, Help's translations load with Help, and each view's stylesheet arrives when the view is opened. First load: 169 requests and 915 KB → 30 requests and 685 KB.
NEXTDASH_BUNDLE=offrestores the individual tags. - new — config's cold open is 74 ms → 55 ms, the bookmark list draws 2757 → 1762 nodes, and the module is 1000 KB → 884 KB with the statistics renderers loading beside their own section.
Docs
static/data/whats-new/v1.3.0.jsonand the index entry ahead of v1.2.1.DASHBOARD_RELEASEandNEXTDASH_WHATS_NEW_DATA_VERSIONare bumped, which is what reopens the modal for everyone — right for a feature release, where v1.2.1 deliberately left both alone.tests/whats-new-hidden-release.spec.js: the shipped-index case now asserts v1.3.0 leads the index and the modal, that v1.2.1 is still recorded and still hidden behind it, and that the two constants name v1.3.0. The four fixture-driven cases that prove the mechanism are untouched.- Config → Help gained the release's subjects in every tab that owns one — the shortcut modes, the cross-page duplicate question, coming back where you were, Fresh, the review session and the Rot report, finding a setting by value, and the capture routes — plus eleven new Tips, in four languages.
- The cheat sheet gained
Shift + FandShift + Alt + ← / →, and the printable sheet now carries every key the modal does — 12 sections, 164 rows, three A4 pages in two columns, with 32 one-off palette commands left off and counted. It was a curated subset policed by a 70-row one-page budget, which made the paper a smaller product than the app: a key added to the modal was not on the sheet, and which keys made the cut was decided by whoever ran out of page first. The shortprintFallbackwording stays, because a printed row that reads in one line is still the point; the registry check is coverage now rather than a ceiling, and it compares row by row within a section — the same chord means different things in different views, and a key-only index answered for the wrong row. - Every Help article now opens with a drawing of its own subject rather than only the panels whose topic was a shape: the three search prefixes in the field they are typed into, the health tiles in the colours the rows wear, a certificate meter with the day the warning starts marked on it, a maintenance window shaded into a day, the boundary the statistics never cross. Thirteen new shapes joined the
SettingArtvocabulary — steps, keycaps, state legends, a typed query, bars, a trend line, a meter, a day strip, switches, swatches, colour plates, a bookmark row and config's own rail — so config, help and the tours keep drawing the same things the same way. Labels inside a drawing go throught();200 OK,Shift + Handdata/*.jsondo not. Seventy-one caption strings, in four languages. tests/config-setting-art.spec.jschecks coverage rather than a hand-counted number: every prose panel on every Help tab must open with a drawing. Its paste-route case was stale — the setting moved to Behavior → Quick add & inbox — and had been failing since.- Config → Overview → New features carries the six headline items of this release, newest first — Capture, the cross-page duplicate question, the Rot report, the review session, Fresh, and the Button bar tab.
MANUAL.mdandREADME.mdcover every feature above; the Help version panel names 1.3.0.- 240 dead help strings are gone — 60 per language, 408 KB, describing the config that was replaced: tab groups called System/Dashboard/Extras, archiving pages, an Essentials/Advanced toggle. Nothing in the app had read them since that config went; the only things that named them were
scripts/apply-help-prose.jsandscripts/help-prose-content.js, generators for a help system that no longer exists, which go with them.en.jsonis 591 KB → 494 KB, and the 23 of those strings that were never translated into German or French stop being a translation debt. npm run validate:help-i18nchecks Help the way the cheat sheet has been checked for a while: a key English has and another language does not is fatal, since the fallback is the English wording and renders as a translation nobody got round to. Strings identical to English and strings the other locales still carry are reported, not failed — failing on those would make an accurate translation impossible to ship. It foundhelpIntrountranslated in German and French on its first run; 272 help strings, complete in four languages, plusinlineEditHintMac, which existed only in English.- The server re-narrows a locale file when it changes on disk. The
core/helpsplit is cached per language and scope, and it was cached for the life of the process — so an edit to a translation was invisible until someone restarted the server, which is the one feedback loop that matters while writing them. The cached entry now carries the file's modification time and size. Served off disk it also revalidates (no-cacheplus an ETag and a 304) rather than being held for a day; a binary serving its embedded copy keeps the day, because there nothing can change under a running process. tests/config-help-navigation.spec.jspins the tips filter, the cross-tab jump and the version heading;locale_scope_cache_test.gopins the cache following the file and the revalidation.tests/config-help-prose-loads.spec.jscovers the empty-help failure from both ends: prose that goes missing while the view is open comes back on the next repaint, and a core locale load no longer drops the help scope out from under it.tests/help-current-features.spec.jspins the help prose, the tips and the two cheat-sheet keys against the rendered UI, so the next release cannot quietly leave them behind.tests/config-help-health.spec.jswas stale from the Health/Monitoring tab split — nine of its fifteen cases failed before this release. Expectations follow the tabs now, and its translation case waits for Help's own scoped strings rather than racing them.
v1.2.1 — 17 August 2026
One fix, in the Health view. Flagged hideFromModal in the index: it counts toward the version number and shows in Config → Overview → Latest update, but does not reopen the What's new modal, which still leads with v1.2.0. DASHBOARD_RELEASE and NEXTDASH_WHATS_NEW_DATA_VERSION are deliberately unchanged — those two tokens are what re-show the modal, and a one-fix release should not push a feature release off the front of it for everyone who has already read it.
What's new
- fix — usage no longer decides where a row sits.
never_openedandnot_opened_30_dayscost 10 points each, so opening a bookmark — the action the Broken filter's own note asks for — raised its score, and under the default worst-first sort carried the row away: measured on a 200-bookmark install, position 10 of 200 became position 188 while the reader was still working through the list. Both penalties are 0 now; the flags, the Unused and Stale tiles and filters, and the reason text on the row are unchanged. - fix — the score sort's tiebreak reads the flags with the two usage ones removed (
stableStatusRank) rather than the status. Status alone was not enough: opening a never-opened row that also has no preview turnsunusedintomissing-preview, a different rank, and the row travels on that instead. - fix — a row you have acted on keeps its position. Opening one under Unused, or re-checking one under Broken, takes it out of what the filter selects, and the list used to close the gap under the cursor. It stays at the position it held, dimmed and marked handled, until the list is asked a different question (filter, sort, search) or reloaded on purpose with
Ror Retest all — and when the view is left. - new — the score breakdown separates deductions from context: reasons that cost nothing are listed under worth knowing, at no cost to the score, because a full score with a deduction printed beneath it reads as broken arithmetic.
Docs
static/data/whats-new/v1.2.1.json, and the index entry ahead of v1.2.0 carryinghideFromModal: true.tests/health-list-stability.spec.jspins both halves against the real UI path — the row's own Open — and compares positions against the rows whose own score did not move, so a startup check landing mid-test cannot pass for a re-ranking. Both halves were falsified: restoring the penalties fails the score assertion, removing the anchoring fails the position one.tests/whats-new-hidden-release.spec.js: the real-index case flips to asserting v1.2.1 is hidden and that v1.2.0 still leads the modal; the constants case now asserts the two tokens are unchanged, and says why that is right here where bumping was right for v1.2.0. The four fixture-driven cases are untouched.MANUAL.mdandREADME.md: the score breakdown no longer lists the two usage penalties, and both describe the handled row and the sort key nothing can move.
v1.2.0 — 16 August 2026
The keyboard release, and the one where three views stop lying about what they know. Typing on the dashboard used to open bookmarks mid-word; the inbox counted links it was not showing; a failed health check worked out its cause and threw it away. All three are now what they appear to be.
What's new
Typing and keys
- fix — typing on the grid no longer opens anything by itself. A bookmark shortcut fired the instant the typed query matched it exactly, so which of your words survived depended on which other bookmarks you owned: on a 200-shortcut install, eight of thirteen ordinary words were swallowed mid-word. Typing now narrows the list and Enter opens the top result, with the exact shortcut match leading it — one keystroke more, and stable against every bookmark you add later.
- fix —
g,jandkno longer eat the first letter of a word. Letters reach the grid only while a row is selected (_letterMayActOnGrid); the arrows always pass,j/kstop propagation once they act, and theg/Gchord paintsdata-grid-keyson the body instead of swallowing the keystroke.cis the documented exception: it still creates a category unconditionally, because four specs pin that behaviour deliberately. - new — the mode prefix is drawn in front of the query line (
>search,:commands,?finders) with an × to clear it, andcloseSearch()now clears#search-queryinstead of leaving the old text behind. - new — an optional key legend under the bookmark grid, shown after the first keystroke and hidden again on Enter. Behavior → General → Show grid key legend, on for fresh installs (
Settings.ShowGridKeyLegend). - new — Show shortcut hints on toolbar icons is off for everyone, via a one-time migration (
migrateShortcutTooltipsDefaultOff, markerShortcutTooltipsOffMigrated). A default change alone would not have reached existing installs, which all carry the field in their stored settings. - new — the shortcut field warns about the letter you are assigning:
ShortcutKeys.gridKeyNote()says what the grid does with it and when, andusedShortcutsNote()lists the shortcuts the page has already spoken for.
Inbox
- fix — every count follows the active filter.
filterBaseItems()/narrowItems()feed the tiles, the pills and the badge, Mark all read becomes Mark shown read whileisNarrowed(), and the first tile is Active — asleep is deliberately excluded. - fix — snoozed links have a footer saying how many are asleep and when the first wakes, and
domainOptions()no longer offers a host whose only link is asleep. - new — Import beside the CSV and JSON exports (
openImportPicker,parseImportPayload,importFromFile), skipping links already present and reporting added / already there / rejected. - new —
markUnread(id)and a row-menu entry for it, and the fetched summary (previewDesc) is rendered on the row, searched, and carried into both exports.
Health
- fix — a failure records its class (
failureClass,HealthSample.Fail) so DNS, timeout, refused, TLS, redirect and content failures reach the incident list, the timeline and the CSV with a reason. Anything that was not an HTTP error storedUp:false, Code:0and showed nothing. - fix — a failed check is re-probed after
monitorConfirmDelayand only recorded if it fails again, so one dropped check no longer dents a month of uptime. - fix —
buildMonitorStatsreportsCoveredMs, andrenderUptimeTilesprints how much history is behind a window rather than labelling a week "30 days". - new — certificate expiry comes from every check:
liveHostsnow includesCheckStatusbookmarks, not only monitored ones. - new — a recovery names its duration,
TREND_SERIESgives the trend chart six series with a per-mode axis,/api/health/expectations-bulkmutes or unmutes a whole selection, andSettings.HealthCheckTimeoutSecondsreplaces the fixed three-second dial timeout.
Search
- new — negation.
parseSearchFiltersproducesfilters.not, andmatchesAdvancedFiltersruns the positive keys then the negative ones, sotag:dev -status:pinnedis expressible in the bar the way it always was in a collection rule. - new —
status:untagged,status:tagged,status:notedandstatus:unnoted;:trashas a command onto#config/data-backups/trash; andCtrl/Cmd+Enteron a result opens in a new tab regardless of the standing preference.
Dashboard and config
- new — undo for moves: single (
undoBookmarkCategoryMove) and cross-page bulk (undoBulkMoveToPage), each restoring from a snapshot so every bookmark returns to its own category.showGroupedNotificationtranslatedundoCallbackinto nothing — the bulk-delete undo it also gates has been dead since it shipped. - new — category icons (
openIconEditor, live preview into.category-title-icon) andAlt+←/→to move a category, announced to screen readers. - new —
setting-art.jsdraws the settings whose subject is a shape (grid, spacing, margins, density, dots, type size, layout version, bar position, flow), in config and in help. - fix — a settings change made elsewhere now reaches an open dashboard:
fetchDataRevisiontracks the settings revision (GetSettingsRevision) and routes a change throughrefreshAfterConfigSettingsUpdate. - fix — a failed automatic backup is visible:
autoBackupRunStaterecordsLastRunAt/LastRunErrorand the tile reports it. - new — a bookmark row in config carries a page › category breadcrumb pill, each half filtering the list.
- new — fresh installs start on Retro CRT (
defaultThemeID), with fold-all and shortcut hints off. Existing installs are untouched. - new — the extension can send a link straight to the inbox: two context-menu entries, a
quick-save-inboxcommand onCtrl/Cmd+Shift+U, and tags carried throughpostInboxLink.
Docs
static/data/whats-new/v1.2.0.jsonand the index entry ahead of v1.1.2.DASHBOARD_RELEASE(2026.08-dashboard-release-v1.2.0) andNEXTDASH_WHATS_NEW_DATA_VERSION(whats-new-v245) are both bumped: a feature release that left them alone would be announced to nobody who had already dismissed v1.1.2.tests/whats-new-hidden-release.spec.js: the real-index and constants cases move to v1.2.0. The four fixture-drivenhideFromModalcases are untouched.- Config → Overview gains three feature spotlights — typing filters/Enter opens, the health failure reason, and the inbox counts — with their five locale keys each in en/nl/de/fr.
MANUAL.mdandREADME.mdcarry the keyboard model, the inbox round, the health engine and the new settings; Config → Help and the printable cheat sheet were updated alongside them.
v1.1.2 — 15 August 2026
A release that exists to announce the one before it. v1.1.1 shipped hideFromModal so it would not displace v1.1.0 on the day both landed; that call is reversed here.
What's new
- fix — the
hideFromModalflag is dropped from the v1.1.1 index entry, so the modal lists it again. The flag only ever gated the modal: the release tag and Config → Overview → Latest update readindex[0]and have shown v1.1.1 since it shipped. - new —
DASHBOARD_RELEASE(2026.08-dashboard-release-v1.1.2) andNEXTDASH_WHATS_NEW_DATA_VERSION(whats-new-v244) are bumped, so the modal reopens once for every user. Unbumped they would have left the un-hiding invisible to anyone who had already dismissed it — the two tokens are the whole re-show mechanism, which is why v1.1.1 deliberately left them alone.
Docs
static/data/whats-new/v1.1.2.json, and the index entry ahead of v1.1.1.tests/whats-new-hidden-release.spec.js: the real-index case flips from asserting v1.1.1 is hidden to asserting nothing in the top three is, and that the modal lists both v1.1.2 and v1.1.1; the constants case moves to the v1.1.2 literals and says why bumping is right here where it was wrong there. The four fixture-driven cases are untouched — the flag still works, it is simply no longer in use.MANUAL.md: the What's new section now says a release can be recorded without being announced, and names v1.1.1 as the one that was.
v1.1.1 — 15 August 2026
Keyboard consistency, and the parts of the app that teach it. Flagged hideFromModal in the index: it counts toward the version number and shows in Config → Overview → Latest update, but does not reopen the What's new modal, which still leads with v1.1.0. DASHBOARD_RELEASE and NEXTDASH_WHATS_NEW_DATA_VERSION are deliberately unchanged — those tokens are what re-show the modal.
Keyboard
- new — the Shift+letter family is closed:
Shift+Einline edit andShift+Vpreview join move, tag, delete, checking, pin, share and reveal.;and[still work, undocumented, the way0still opens the inbox. Brackets mean previous / next sub-tab in config, and one pair of keys meaning two things was the reason to move preview off[. - new — share moves to
Shift+L, soShift+Salways opens config. It was the only key whose meaning depended on whether a row was selected, which took two cheat-sheet rows and a parenthesis to explain. The capture-phase handler still declines rather than swallows a key it has no row for — that is what keepsShift+SreachingsetupPageShortcutsover a selected row, andtests/shift-key-row-actions.spec.js(renamed fromshift-s-two-meanings.spec.js) pins it. - new — bare letters act on keydown.
C_HOLD_MSand the g-chord hold are gone, and with them the wholekeyuphandler inkeyboard-navigation.jsandSearchComponent.addShortcutLetter, which had no other caller._activateGChordModenow runs on the firstg;G_CHORD_MS(3s) is the only timer left. - new —
j/kaliasArrowDown/ArrowUpin the grid, matching config's section rail and bookmark list. - new —
Shift+Homefocuses the category header viafocusCategoryHeader(), which reusesresolveFocusedCategoryEl.Deleteon a focused header callscategoryMenu.runAction('delete', …)instead of opening the menu;Shift+F10opens it. - fix —
handleContextMenutookclientX/clientYat face value, which a keyboard-raisedcontextmenureports as 0/0.menuPointFor()falls back to the row's rectangle; the category menu's own handler does the same. - fix —
resolveFocusedCategoryEltook an options argument::widthkeeps the first-category fallback because the palette names what it acts on,Shift+WpassesfallbackToFirst: falseand falls through. - fix —
search.jskept a code-name list (KeyM/KeyD/KeyT/KeyB) that had fallen behind the keys added since; it tests/^Key[A-Z]$/now. - fix —
setupPageShortcutswas the only typing guard in the app without anisContentEditablecheck.
Discoverability
- new —
DashboardContextMenurenders akbd.move-popover-item-keyper entry with a matchingaria-keyshortcuts, for all eleven actions that have a key.ShortcutFormat.ariaKeys()and.modifierLabel()are shared with the category menu, which had the chips but no aria. - new —
syncShortcutAriaHints()stampsaria-keyshortcutson the toolbar, the header buttons and the first nine page tabs. It runs independently ofshowShortcutTooltips: the tooltips are a desktop hover affordance that can be switched off, this is the only route the keys have to assistive technology.shortcutButtonDefs()replaces the three parallel lists that let the header row end up with tooltips and no aria. - fix — the side-rail legend printed the what's-new button's
★glyph in the same chip as real keys.
Cheat sheet
- fix —
buildPrintSectionspassed each row'sprintFallbackto the caller's label resolver as a fallback, andgenerate-cheatsheet.cjsresolves fromlocales/en.json, where the i18n check guarantees everycheatKeyexists. The short wording therefore never reached paper for any of the 23 rows that carry one.printLabel()uses it directly.validate-cheatsheet-registry.cjsgains a check built with the generator's resolver — with the identity resolver the other checks use, aprintFallbackwins either way and the check would pass against the bug. - new — printed rows for
k/j,Shift+EandShift+Home;nextDash-cheatsheet.html,nextDash-cheatsheet.pdfand thestatic/copy regenerated.
Config
- new —
logFilterActivityis a fourth value for the log viewer's Show control, matched onSource == "activity"bylogEntryMatchesFilter()rather than on severity. Activity lines already flowed intoserverLogSinkthroughlog.Printf("activity: …")andparseServerLogLinealready split the subsystem out; nothing about capture orNEXTDASH_ACTIVITY_LOGchanges. - new — Help → About carries the wordmark and three addresses:
nextdash.cc, GitHub, andjordibrw.nl. Thehelp-signaturesign-off is dropped, so each address appears once.
Dashboard
- fix —
.category-titlecentres its flex items instead of aligning them on the baseline. The+button hangs padding and a border below its own baseline, which made every header carrying it 1.44px taller; centring costs nothing, where aline-heighttall enough to swallow the button added 3px to every header..category-title--multilinekeepsflex-start. - fix —
*carries the same guards as!and., with an exception for its own modal so it still closes the recent list.
Analytics
- new —
settings-snapshotwidens from 21 to 45 fields and a newcontent-snapshotreports install size (bookmarks, pages, categories, tags, finders, collections, monitored, inbox totals). Every value is bucketed client-side;MAX_EVENT_PROPS = 50is Umami's per-event limit, which is why it is two events.analytics_content.gocounts server-side and returns""when telemetry is off —analytics_content_test.goproves it with a nil store, where any counting would panic. - fix — a custom theme reported its own id, which is
theme-<base36>-<4 chars>and random per install: not a name leak, but distinctive enough to follow one install across releases. It reportscustom.
Docs
MANUAL.mdandREADME.mdfollow the moved keys, the G chord, the category-header keys, the right-click menu chips, the log viewer's Activity only and the About tab.locales/{en,nl,de,fr}.json: the cheat-sheet rows for the moved keys, two new rows (navCategoryHeader,bmCategoryDeleteKey), a rewrittenconfig.helpKeyboardBody,keyboardFixedNoteGridNavwithout the hold,logLevelActivity,logActivityHint,helpAboutBody,helpSiteProject,helpSiteAuthor.static/nextdash-wordmark.pngis keyed out oflogo-ascii-on-black-large.pngby greenness rather than brightness; the existinglogo-ascii-transparent.pngclears only pixels darker than 40, which leaves the glow behind the middle of the word as a dark ellipse.- New:
tests/dashboard-keyboard-uniformity.spec.js,tests/config-help-about.spec.js,TestServerLogSinkActivityFilter. Updated: the c-hold and G-chord cases intests/create-page-category-from-dashboard.spec.jsandtests/dashboard-grid-shortcuts.spec.js, the category-menu chip case intests/dashboard-nice-to-haves.spec.js. go generate ./...for the changed CSS/JS.
v1.1.0 — 15 August 2026
The first minor since 1.0: a category can be wider than one column. DASHBOARD_RELEASE and NEXTDASH_WHATS_NEW_DATA_VERSION are bumped, so the modal reopens once.
Categories across columns
- new —
Category.Spread(spreadinbookmarks-{page}.json) marks a category as allowed to run across grid columns; uncategorized and the smart collections keep theirs insettings.categorySpreads[pageId][categoryId], the shapecategorySortModesalready uses.dashboard-category-span.jsowns both, so no caller has to know which applies. - new — the width itself is derived, never stored:
spanForCountismin(ceil(count / categoryItemLimit), effectiveColumns, 12). It was a number the user picked in the first draft; it became a switch because the number is implied by two settings that already exist, and a stored number goes stale the moment a bookmark is added. - new —
applyCategoryItemLimitmultiplies the limit by the span, so a spread category shows its limit once per column and stays the height of its neighbours.refreshAllCategorySpansruns fromfinishIncrementalRefresh, which is what makes an added bookmark bring the next column with it rather than waiting for a reload;settleSpanChangefollows that through to the "+ N more" cut and, in packed mode, to a full re-render. - new — the two settings rule out each other's extreme:
spreadUnavailableReasonreturnsunlimited-itemswhencategoryItemLimitis 0, and the config select disables Unlimited whileanySpreadCategoryholds. Both say why where they are asked for. - new — routes:
DashboardCategoryMenu(entry flips to Back to one column, noaria-checkedbeside a label that already flips),Shift+Winkeyboard-navigation.js,:width on|off|all, adata-cat-spreadbutton per row in the categories editor, andAppearance → Layout → Categories across columnsfor the defaults and the reset. - new —
.category--widespans withgrid-column: span var(--category-span)and repeats the row's track pattern once per column, sosubgridalignment survives. The inner column gap is the grid gap plus2 × --category-inline-pad: a run of categories pads every column on both sides, a single wide box only its outer edges, and without that the inner columns sat 16px left of the grid.syncWideColumnTrackspins the shortcut and lead tracks, which are intrinsic and were sized per repeat by whichever rows happened to land in them. - new — a rule under the header spanning the block, and a
↔Nbadge in the header, both dropped while the category is collapsed or one column wide.
Packed columns
- new — packed keeps its round-robin flex columns while nothing spreads, and switches to a grid (
packed-masonry) the moment something does:--masonry-spanrows per category fromdashboard-packed-masonry.js,grid-auto-flow: row dense, aResizeObserverper category. Bands were tried first and left a hole the height of the tallest column beside the wide block. - fix —
syncCategoriesFromDomread the columns in document order while the render filled them round-robin. Those are not each other's inverse, so every category drag rewrote the order into one that redistributed differently and scrambled the page.readCategoryElementsInOrderis the single inverse now, keyed on what is in the DOM, andgetExistingCategoriesdelegates to it. - fix —
syncDashboardGridLayoutrebuildsclassNamewholesale and droppedpacked-masonry, so any settings refresh — including the one behind a window resize — left the categories as bare flex children of a row with no columns in it.
Config
- fix — the location memory was written only on the exits config knows about; the header's health, inbox and page buttons switch view around it.
restoreConfigHashnow saves on every move inside config andsetActiveViewstamps it on the way out. The TTL is 5 minutes (DashboardConfigandDashboardConfigLoaderboth), counted from leaving rather than from the last click inside. - new —
type: 'action'panel controls withbindPanelActions, used for Turn spreading off everywhere;NEW_THIS_RELEASEis the one place naming where the twinkle points, drawn on the section, the sub-tab and the panel. - new —
#category-context-menusizes to its content (width: max-content, capped at 24rem). The shared.move-popovercap is for the move/tag/delete pickers; at 16rem the widest row here needed 274px and got 237, so theShift+Wchip was trimmed away — and the French label is half again as long.
Config → Bookmarks
- new — the section gets the sub-tab strip five others already have: List and Settings. The nine settings sat after the list — fifty rows down by default and up to five hundred as the infinite scroll loads more, which also rules out jumping to the bottom of a list whose bottom moves as you approach it. Registered in
SUB_TABS,SUB_TAB_STATE,SUB_TAB_ATTRandSUB_TAB_SECTION, so the deep link, the remembered location and the arrow-key walk come for free;handleOverviewGoacceptsbmTab.
Views
- new — Health, Inbox and Config → Bookmarks draw one card from
feed-row.cssinstead of three copies. The rule was written out inhealth-view.css,dashboard-inbox.cssandconfig-view.cssand the copies had drifted into being byte-identical — one design, three places to change it, and no way to tell whether a difference was meant. What genuinely differs is a modifier:feed-row--with-selectfor Health's checkbox column, and the coloured left edge each view uses for its own state, including modern layout's inset redraw of it. Classic layout also gains the row focus ring only modern had. - new — the shape the three views disagreed on is settled: Inbox rounded its filter pills and buttons where Health squared them off; rounded won. Config → Bookmarks reads as a view rather than a settings panel — a header with a count, the search box sized like Health's, and tiles matching Health's without the stripe.
Discoverability
- new —
spread-notice.js(aNoticeCard) andspread-tutorial.js, a four-stepAppModalwalkthrough in the shape of the inbox and health tours, reachable afterwards fromHelp → Pages & categories. - fix —
NoticeCardbound an action withquerySelector, and a card naming its × throughdismissNameputs that attribute on the × as well. The × comes first, so it took the handler and the button sharing its name got none: the side rail's No thanks had been dead since the cards were unified.
Docs
MANUAL.md— the spreading section with the columns-per-bookmark-count table, and the config-memory paragraphs at five minutes. Config → Bookmarks is described with its two sub-tabs.README.md— the feature bullet,Shift+W,:width on|off, and the five-minute memory.CHANGELOG.md,static/data/whats-new/v1.1.0.json,index.json,whats-new-stub.js,tests/whats-new-hidden-release.spec.js, the four locale files, andgo generate ./...forasset_hashes_gen.go.