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.