github jordibrouwer/nextdash v1.2.0

latest release: v1.2.1
one day ago

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.
  • fixg, j and k no longer eat the first letter of a word. Letters reach the grid only while a row is selected (_letterMayActOnGrid); the arrows always pass, j/k stop propagation once they act, and the g/G chord paints data-grid-keys on the body instead of swallowing the keystroke. c is 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, and closeSearch() now clears #search-query instead 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).
  • newShow shortcut hints on toolbar icons is off for everyone, via a one-time migration (migrateShortcutTooltipsDefaultOff, marker ShortcutTooltipsOffMigrated). 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, and usedShortcutsNote() 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 while isNarrowed(), 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.
  • newImport beside the CSV and JSON exports (openImportPicker, parseImportPayload, importFromFile), skipping links already present and reporting added / already there / rejected.
  • newmarkUnread(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 stored Up:false, Code:0 and showed nothing.
  • fix — a failed check is re-probed after monitorConfirmDelay and only recorded if it fails again, so one dropped check no longer dents a month of uptime.
  • fixbuildMonitorStats reports CoveredMs, and renderUptimeTiles prints how much history is behind a window rather than labelling a week "30 days".
  • new — certificate expiry comes from every check: liveHosts now includes CheckStatus bookmarks, not only monitored ones.
  • new — a recovery names its duration, TREND_SERIES gives the trend chart six series with a per-mode axis, /api/health/expectations-bulk mutes or unmutes a whole selection, and Settings.HealthCheckTimeoutSeconds replaces the fixed three-second dial timeout.

Search

  • new — negation. parseSearchFilters produces filters.not, and matchesAdvancedFilters runs the positive keys then the negative ones, so tag:dev -status:pinned is expressible in the bar the way it always was in a collection rule.
  • newstatus:untagged, status:tagged, status:noted and status:unnoted; :trash as a command onto #config/data-backups/trash; and Ctrl/Cmd+Enter on 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. showGroupedNotification translated undoCallback into nothing — the bulk-delete undo it also gates has been dead since it shipped.
  • new — category icons (openIconEditor, live preview into .category-title-icon) and Alt+←/→ to move a category, announced to screen readers.
  • newsetting-art.js draws 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: fetchDataRevision tracks the settings revision (GetSettingsRevision) and routes a change through refreshAfterConfigSettingsUpdate.
  • fix — a failed automatic backup is visible: autoBackupRunState records LastRunAt/LastRunError and 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-inbox command on Ctrl/Cmd+Shift+U, and tags carried through postInboxLink.

Docs

  • static/data/whats-new/v1.2.0.json and the index entry ahead of v1.1.2. DASHBOARD_RELEASE (2026.08-dashboard-release-v1.2.0) and NEXTDASH_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-driven hideFromModal cases 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.md and README.md carry 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 hideFromModal flag 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 read index[0] and have shown v1.1.1 since it shipped.
  • newDASHBOARD_RELEASE (2026.08-dashboard-release-v1.1.2) and NEXTDASH_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+E inline edit and Shift+V preview join move, tag, delete, checking, pin, share and reveal. ; and [ still work, undocumented, the way 0 still 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, so Shift+S always 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 keeps Shift+S reaching setupPageShortcuts over a selected row, and tests/shift-key-row-actions.spec.js (renamed from shift-s-two-meanings.spec.js) pins it.
  • new — bare letters act on keydown. C_HOLD_MS and the g-chord hold are gone, and with them the whole keyup handler in keyboard-navigation.js and SearchComponent.addShortcutLetter, which had no other caller. _activateGChordMode now runs on the first g; G_CHORD_MS (3s) is the only timer left.
  • newj/k alias ArrowDown/ArrowUp in the grid, matching config's section rail and bookmark list.
  • newShift+Home focuses the category header via focusCategoryHeader(), which reuses resolveFocusedCategoryEl. Delete on a focused header calls categoryMenu.runAction('delete', …) instead of opening the menu; Shift+F10 opens it.
  • fixhandleContextMenu took clientX/clientY at face value, which a keyboard-raised contextmenu reports as 0/0. menuPointFor() falls back to the row's rectangle; the category menu's own handler does the same.
  • fixresolveFocusedCategoryEl took an options argument: :width keeps the first-category fallback because the palette names what it acts on, Shift+W passes fallbackToFirst: false and falls through.
  • fixsearch.js kept a code-name list (KeyM/KeyD/KeyT/KeyB) that had fallen behind the keys added since; it tests /^Key[A-Z]$/ now.
  • fixsetupPageShortcuts was the only typing guard in the app without an isContentEditable check.

Discoverability

  • newDashboardContextMenu renders a kbd.move-popover-item-key per entry with a matching aria-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.
  • newsyncShortcutAriaHints() stamps aria-keyshortcuts on the toolbar, the header buttons and the first nine page tabs. It runs independently of showShortcutTooltips: 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

  • fixbuildPrintSections passed each row's printFallback to the caller's label resolver as a fallback, and generate-cheatsheet.cjs resolves from locales/en.json, where the i18n check guarantees every cheatKey exists. The short wording therefore never reached paper for any of the 23 rows that carry one. printLabel() uses it directly. validate-cheatsheet-registry.cjs gains a check built with the generator's resolver — with the identity resolver the other checks use, a printFallback wins either way and the check would pass against the bug.
  • new — printed rows for k/j, Shift+E and Shift+Home; nextDash-cheatsheet.html, nextDash-cheatsheet.pdf and the static/ copy regenerated.

Config

  • newlogFilterActivity is a fourth value for the log viewer's Show control, matched on Source == "activity" by logEntryMatchesFilter() rather than on severity. Activity lines already flowed into serverLogSink through log.Printf("activity: …") and parseServerLogLine already split the subsystem out; nothing about capture or NEXTDASH_ACTIVITY_LOG changes.
  • newHelp → About carries the wordmark and three addresses: nextdash.cc, GitHub, and jordibrw.nl. The help-signature sign-off is dropped, so each address appears once.

Dashboard

  • fix.category-title centres 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 a line-height tall enough to swallow the button added 3px to every header. .category-title--multiline keeps flex-start.
  • fix* carries the same guards as ! and ., with an exception for its own modal so it still closes the recent list.

Analytics

  • newsettings-snapshot widens from 21 to 45 fields and a new content-snapshot reports install size (bookmarks, pages, categories, tags, finders, collections, monitored, inbox totals). Every value is bucketed client-side; MAX_EVENT_PROPS = 50 is Umami's per-event limit, which is why it is two events. analytics_content.go counts server-side and returns "" when telemetry is off — analytics_content_test.go proves 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 reports custom.

Docs

  • MANUAL.md and README.md follow 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 rewritten config.helpKeyboardBody, keyboardFixedNoteGridNav without the hold, logLevelActivity, logActivityHint, helpAboutBody, helpSiteProject, helpSiteAuthor.
  • static/nextdash-wordmark.png is keyed out of logo-ascii-on-black-large.png by greenness rather than brightness; the existing logo-ascii-transparent.png clears 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 in tests/create-page-category-from-dashboard.spec.js and tests/dashboard-grid-shortcuts.spec.js, the category-menu chip case in tests/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

  • newCategory.Spread (spread in bookmarks-{page}.json) marks a category as allowed to run across grid columns; uncategorized and the smart collections keep theirs in settings.categorySpreads[pageId][categoryId], the shape categorySortModes already uses. dashboard-category-span.js owns both, so no caller has to know which applies.
  • new — the width itself is derived, never stored: spanForCount is min(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.
  • newapplyCategoryItemLimit multiplies the limit by the span, so a spread category shows its limit once per column and stays the height of its neighbours. refreshAllCategorySpans runs from finishIncrementalRefresh, which is what makes an added bookmark bring the next column with it rather than waiting for a reload; settleSpanChange follows 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: spreadUnavailableReason returns unlimited-items when categoryItemLimit is 0, and the config select disables Unlimited while anySpreadCategory holds. Both say why where they are asked for.
  • new — routes: DashboardCategoryMenu (entry flips to Back to one column, no aria-checked beside a label that already flips), Shift+W in keyboard-navigation.js, :width on|off|all, a data-cat-spread button per row in the categories editor, and Appearance → Layout → Categories across columns for the defaults and the reset.
  • new.category--wide spans with grid-column: span var(--category-span) and repeats the row's track pattern once per column, so subgrid alignment survives. The inner column gap is the grid gap plus 2 × --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. syncWideColumnTracks pins 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 ↔N badge 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-span rows per category from dashboard-packed-masonry.js, grid-auto-flow: row dense, a ResizeObserver per category. Bands were tried first and left a hole the height of the tallest column beside the wide block.
  • fixsyncCategoriesFromDom read 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. readCategoryElementsInOrder is the single inverse now, keyed on what is in the DOM, and getExistingCategories delegates to it.
  • fixsyncDashboardGridLayout rebuilds className wholesale and dropped packed-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. restoreConfigHash now saves on every move inside config and setActiveView stamps it on the way out. The TTL is 5 minutes (DashboardConfig and DashboardConfigLoader both), counted from leaving rather than from the last click inside.
  • newtype: 'action' panel controls with bindPanelActions, used for Turn spreading off everywhere; NEW_THIS_RELEASE is the one place naming where the twinkle points, drawn on the section, the sub-tab and the panel.
  • new#category-context-menu sizes to its content (width: max-content, capped at 24rem). The shared .move-popover cap is for the move/tag/delete pickers; at 16rem the widest row here needed 274px and got 237, so the Shift+W chip 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_ATTR and SUB_TAB_SECTION, so the deep link, the remembered location and the arrow-key walk come for free; handleOverviewGo accepts bmTab.

Views

  • new — Health, Inbox and Config → Bookmarks draw one card from feed-row.css instead of three copies. The rule was written out in health-view.css, dashboard-inbox.css and config-view.css and 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-select for 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

  • newspread-notice.js (a NoticeCard) and spread-tutorial.js, a four-step AppModal walkthrough in the shape of the inbox and health tours, reachable afterwards from Help → Pages & categories.
  • fixNoticeCard bound an action with querySelector, and a card naming its × through dismissName puts 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, and go generate ./... for asset_hashes_gen.go.

Don't miss a new nextdash release

NewReleases is sending notifications on new releases.