github jordibrouwer/nextdash v1.8.0

3 hours ago

Fixes

  • fix — the finder line was empty straight after a page load, with finders configured. ? said No finders set up yet and offered the way to add one, and a shortcut typed after it never gained its trailing space — both symptoms of the same thing: SearchComponent's constructor handed SearchFindersComponent an empty array and left the real list to updateData(). The search stack is fetched by the key that opens it, so the component is built after the dashboard's last render and nothing calls updateData() in between; the overlay opened on a set of finders it had never been told about, and only the next render — a page switch, a setting, anything — put them there. That is why it looked like a first-visit-only fault. The constructor passes the finders it was given.
  • fix — following the system dark mode left the old background nailed to the page when the switch happened in a background tab. A theme switch pins the outgoing background on <body> with !important so the repaint cannot flash, and released it two requestAnimationFrame ticks later. A hidden tab runs no animation frames, so a Mac going dark while the dashboard sat behind another tab came back with the light background stuck under the dark theme's text — white on white, or the reverse — and only a reload cleared it. The frames are the fast path now, with a 250 ms timer behind them and a visibilitychange for the tab that stays hidden longer than the timer; every path goes through the same idempotent release, and a switch on top of a switch lets the earlier pin go before making its own.
  • docs — Help and the manual say that following the system happens in a background tab too. Config → Appearance's help panel described the pairing and said nothing about when the swap happens, and the manual's troubleshooting list had no entry for a page whose colours came back wrong after the Mac went dark — the two places a reader looks when it does. Both cover it now, in all six languages, with the hard-refresh note for anyone still running the JavaScript from before the fix.
  • fix — the catalogue's "in view" check demanded that a whole settings panel fit on screen. Rewritten with the auto-open change, it asserted the row's top and the panel's bottom were both inside the viewport — which on CI's 720px-tall window a panel of several fields cannot satisfy, so it failed there three retries running while passing locally. It now checks what the test was written for: the new row's head is on screen and its fields start above the fold, rather than the whole panel fitting.
  • fix — the widget specs opened a panel that was already open, and sat out a timeout each time. Four helpers added a widget and then clicked Settings to reach its fields. Since a new widget arrives with its settings open that click is a toggle: it closed the panel, and the wait for a field that had just been hidden ran to its full 30 seconds. Seven tests failed that way in config-widget-panel, widget-secret-reveal, widget-preset-address and widget-cpu — the specs that sit in CI shards 3 and 6, which is why those two ran for double the time of the other four rather than failing outright. Each now clicks only when aria-expanded says the panel is closed, which is also what the four call sites that open a seeded widget's panel have always been doing. The affected files run in 2.2 minutes rather than 3.6.
  • fix — CI failed on the Spanish locale over a test that counted languages rather than naming them. tests/list-view-shell-i18n.spec.js asserted files.length === 5 against locales/, so the sixth language broke a check that was not about it: the test exists to prove every locale carries the list-shell strings, which the loop under it already does for whatever files are there. It now compares the directory against a named list, the way validate-locale-parity.cjs and its neighbours spell theirs out — a missing locale still fails, and a seventh language trips every list at once and on purpose. tests/dashboard-category-width.spec.js was stale in the other direction: its "nothing is cut off, in any language" loop never switched to Spanish, so the longest labels in the category menu went unchecked. It covers es now.

Config

  • new — a widget arrives with its settings already open, and the reveal scrolls to the fields rather than to the row's head. Adding one is almost always followed by filling it in, and the fields sat behind a second click on Settings. Only one settings panel is open at a time, so on a page that already carried a widget of the same kind that click also closed the panel being read — which is what made a second RSS block feel like it could not be given feeds of its own. addWidget now records the new block's index in _widgetSettingsOpen (via a new lastWidgetIndex, since the list is grouped by default and the new row is only last when sorting by page order), and revealNewWidget takes that index rather than picking the last row on screen. The caret still lands in the title box, so renaming first is unchanged.
  • new — Config → Bookmarks proposes tags in bulk, from the tags you already gave a host's other bookmarks. A new pure module (static/js/shared/tag-suggestions.js) groups bookmarks by host and by host plus first path segment, finds the tag a group agrees on — counted over its tagged members, so a host that is only half sorted still says something — and offers it to the ones lacking it, at most two proposals per bookmark. Every row names its source, because a suggestion you cannot account for is one you cannot judge. Rules of your own (Settings.TagRules, narrowed by sanitizeTagRules) outrank what the app noticed. Applying goes through the existing mutateSelected + bulkUndo path rather than a new endpoint, so the 8-second undo works exactly as it does for the bulk bar.
  • fix — the tag suggestions panel corrected: a rule it could never match, a row it drew twice, and a list it drew too much of. sanitizeTagRules stored reddit.com/r/selfhosted, www.github.com and github.com/ as rules, none of which patternsFor() can ever produce — it stops at the first path segment, strips a leading www. and never ends in a slash — so all three sat in Settings looking configured and firing never. A pattern deeper than one segment is refused now, and the other two are narrowed to the shape they will be compared against. A rule that only restates a group the app had already noticed produced two rows differing in nothing the reader can act on, and the pair ate both of that bookmark's two suggestion slots, hiding a genuinely different second proposal; suggest now offers a tag to a bookmark once, with the rule as the reason. Rows are ordered by group size rather than by how specific the pattern is, so a 45-bookmark host no longer sits under a 3-bookmark subreddit, and the list stops at 25 rows with a line saying how many are waiting. suggest also parses each URL once instead of once per rule — 10,000 bookmarks against 100 rules went from 423 ms of blocked rendering to 37 ms. Undo after applying a suggestion redraws the panel where the undo is, rather than relying on a full config re-render that skips itself while an inline edit is open.
  • fix — the keyboard legend removed from the top of Config → Bookmarks. j/k, x, Enter and Esc were spelled out in a strip under the toolbar, above the tag cloud, on every visit forever. The keys are in the cheat sheet and the rows announce what they do, so the strip was a manual sitting where the list should start.
  • new — the tag suggestions panel says what it is, folds away, and explains itself. It opened as an unnamed box holding one sentence, the words Your rules, and two unlabelled boxes — a reader could not tell what it was offering or what typing in it would do. It is a <details> now, the same shape as the tag cloud above it: the summary carries the name and a count, so a shut panel still says what it holds, and the open/shut choice is remembered per browser rather than derived from the content, which would have sprung it open again on every repaint. An ℹ beside the name opens the standard config dialog explaining both sources, the threshold behind a proposal, the one-segment limit on a pattern, and that nothing is tagged without Apply. The two rule boxes carry labels rather than only placeholders, with an example under them. And a pattern the server would have dropped — a whole address, or one two segments deep — now says so under the form instead of vanishing: sanitizeTagRules narrowed it in silence, so three rules typed in a row left no clue which one was wrong.
  • new — tag suggestions moved into a tab of their own, between List and Settings. Sitting above the bulk bar it competed with the list for the top of the page and pushed the bookmarks down on every visit, whether or not there was anything to review. The tab carries the number waiting beside its name, so the reason to open it is visible from the List tab without the panel occupying it. The proposals lie flat now — the tab is the disclosure — and only Your rules still folds, since a rule list grows without bound and sits under the proposals it would otherwise push away.
  • new — a shipped catalogue of 463 subjects, so a fresh collection has something to propose from the first visit. Until now the panel could only repeat what the reader had already done, which says nothing at all to someone who has tagged nothing — the honest but useless "nothing to propose yet". static/data/tag-patterns.json names 463 subjects and the 1,904 sites that belong to each, served as data the way overview-features.json is and read once per page load. There is no minimum group behind a catalogue row: the evidence is the file rather than the collection, so one bookmark on a known site is as good a match as forty, though the rows are still grouped by site so a site is one row rather than forty. Your own vocabulary wins — the catalogue supplies the subject, your tags supply the name, so a catalogue entry dev whose aliases include code is proposed as #code to someone who already tags that way, and the row names the subject it renamed. It says nothing at all about a site your collection has already settled: a reader who files every github.com link under #work is not offered #dev on top, which is the shipped vocabulary splitting a tidy collection in two. A missing or malformed file costs the catalogue rows and nothing else. scripts/validate-tag-patterns.cjs runs in CI and refuses a host claimed by two subjects, a host written with a scheme or a www. that could never match what patternsFor() emits, and a tag repeated in its own aliases.
  • new — a scan round reads the pages of bookmarks nothing else can place, and files them by what they say. The first three sources need no network: a rule you wrote, the tags you already gave a site's other bookmarks, and the catalogue keyed on the host. None of them reaches a page on a site the catalogue has never heard of — and the only thing that knows what such a page is about is the page. Read their pages in the Tag suggestions tab does exactly that, one request per bookmark, and states the cost first: "85 bookmarks have no keywords yet." Nothing runs on a timer and nothing rides along quietly; the round is a button, and the button is also the stop. The loop lives in the browser rather than on the server, in the shape RefreshAllBookmarkPreviews already uses, which is what makes stopping free — there is no job to end, only a slice nobody asks for. What is kept is a dozen derived words per page (meta keywords, every article:tag, og:section, the first h1), never the page's text: words are tens of bytes per bookmark, prose would be megabytes in the data directory and in every backup. A page that cannot be read is a skip rather than a failure, and is not asked again. Rows from this source are grouped by subject rather than by host, since these bookmarks are on different sites by definition — #networking · across sites · 3 bookmarks · the page text (dns). Two loose keywords are the floor, because one shared word is coincidence often enough to be worthless; a page that writes the subject's own name, or your word for it, has said what it is and needs no second word.
  • fix — the scan round shows the same progress overlay every other long walk in config does, and it can be stopped from there. It reported itself as a line of text beside its own button, which is not what reading a hundred pages looks like anywhere else in the app: importing sources, refreshing previews and making a backup all put up ProgressOverlay, and this walked a collection in exactly their shape while looking like nothing was happening. The overlay gained the one thing this round needed and the others do not — a way out. A blocking panel is right for a single request nobody could usefully interrupt; it is wrong for minutes of outbound requests started by a misplaced click, so a caller that can stop halfway passes onCancel and gets a quiet Stop under the bar. The rest are unchanged and get no button. showProgressOverlay was forwarding only its first two arguments, so the button was configured and never drawn.
  • new — your rules moved to a tab of their own, between Tag suggestions and Settings. They were a folding block under the proposals, which put the editor at the bottom of a list that grows: a reader with a screen of suggestions had to scroll past all of them to write a rule, and a reader with forty rules had to scroll past those to write the forty-first. The tab carries its own explanation and an ℹ that says what a rule beats and how deep a pattern may go, the form sits above the list rather than under it, and the form is carded exactly like the rules it produces — a form shaped differently from its own output reads as a different kind of thing. The Read their pages offer became a row of the same kind as the proposals above it, tinted rather than plain, with what it says on the left and what it does on the right. And a stored copy under Local copies is a card too: hairlines between rows made a group of captures look like ruled paper rather than like a list of things each of which can be downloaded or deleted.
  • fix — reloading on a bookmarks sub-tab filtered the list to a page named after that tab. The third segment of #config/bookmarks/… is two things at once: the page a filtered list is scoped to, and — when no page filter is set — the sub-tab. Reading a tab name back as a page id left the List tab scoped to a page called "tag-suggestions", which no collection has, so it showed nothing and offered a filter chip nobody had asked for. A page filter is always a page id and those are numbers, so a segment spelling one of the tabs can only be a tab.
  • fix — a row's More menu and its right-click menu now offer the same things. They were written out separately and had drifted: right-click knew Open in new tab, Edit, Pin, Checking, the three filters and Select; More knew nine of the sixteen entries and nothing else — same row, same bookmark, two different answers to what you can do with it. actionsFor() is the single list now and More is built from it, so an entry added to either appears in both. The click goes through the context menu's own dispatcher too, which already knew the handful of actions the row menu never learned; a submenu entry (Checking…) is anchored to the item that opened it, so the second menu appears where you clicked rather than beside the badge.
  • fix — the arrow keys walk the bookmark list, and "down" no longer leaves the page. The keys reached the rows only once focus was already inside the list, so on a freshly opened Bookmarks section the first press fell through to the config section shortcut: pressing down on a list of bookmarks moved to Appearance. With nothing focused and rows on screen the list now claims the key, which is the whole point of a keyboard cursor — a reader should not have to click a row before the keyboard works. ArrowDown and ArrowUp are mapped onto j and k rather than duplicated, since "up" and "down" on a list of rows is the arrow keys to almost everyone, and the legend that named j/k is gone. Fields keep their own keys: the arrows still move the caret in the search box, and typing in any input is untouched.
  • fix — a row menu opened with m can be walked and closed on the keyboard. It opened and then stranded you: Escape reached the view's own handler, which wiped the row cursor and left the menu hanging over the page, and j/k were refused inside a row control and then taken by the config section shortcut — pressing down in an open menu moved to the next section with the menu still up. The open menu owns the keys now: the arrows and j/k walk its items and wrap, Home and End jump to the ends, and Escape closes it and puts focus back on the More button with the row cursor where it was, so the next press carries on down the list. Enter is untouched, since focus is already on the item.
  • fix — the number on the Tag suggestions tab changed the moment you opened it. Arriving on Config → Bookmarks it read 1 and opening the tab turned that into 19: the shipped catalogue and the scanned keywords were fetched when the tab was bound, so until then the strip could only count what a rule of your own had found. A count that changes when you look at it is worse than no count. Both are loaded once when the section opens now, and the strip is corrected when they land — they were already cached per page, so the tab itself still costs nothing.
  • fix — the bookmark search box refused the letters j, k, g and G. They were handed to the list from inside the field: typing one left the box and moved the row cursor instead of typing, so github, json and jira could not be searched for at all, and a space opened whatever the cursor happened to be on. Two places let them through — the view's own guard and the list handler's — and both do now what a field is owed: every key belongs to the field. Escape still reaches the handler that clears the box, and the arrows still move the caret. The tag filter under Pages & tags keeps its arrows, which was always the defensible half of this.
  • new — a suggestion can be opened to see which bookmarks it means, and one can be left out of it. 47 bookmarks was a number to take on trust, and a group with a single wrong member left the reader nothing to do but refuse the whole row and tag the other forty-six by hand. The count is the handle: it opens a list naming each bookmark, and each carries a tick. What stays ticked is what Apply writes — untick the recipe someone linked from a gist and the other forty-six still get #code in one click.
  • new — the scan round reads the title and the description too, which more than doubles what it finds. It read the places a publisher files a page — meta keywords, article:tag, og:section — and the first h1. Most of the web publishes none of those: on a real collection of 107 bookmarks only 13 pages yielded a word. Reading the <title> and the description as well takes that to 32. They are read last and the twelve-word cap is what keeps them in their place: a keywords tag was written to file the page, a title to sell it, so the deliberate sources fill the slots first and the prose gets what is left. Titles are split on a dash and a colon as well as a pipe, since that is how a title separates its parts. Seventeen more words joined the list of things a page says because it is a page — breaking, home, power, mobile, inbox — each of which had matched a subject by name on that same collection and filed a meme site under #news, a file-sharing page under #home and a chat assistant under #energy.
  • new — the keywords read from your pages can be forgotten, and read again. Forget the scanned keywords sits in Data & backups beside the other cache actions, and in the panel itself once the round is done. It clears the words and nothing else: the same cache entries hold the title, the description and the picture, all fetched for the preview cards and none of this feature's to throw away. Read them again re-reads every page for a collection that has already been scanned — after a site redesign, or once the catalogue has grown enough to be worth weighing against — and keeps the words it has until they are replaced, so the panel goes on proposing while the round runs. Which pages are still unread is its own stamp now (keywordsAt) rather than the preview's fetchedAt: a page that could not be read is remembered as asked, so a dead link is not offered on every round forever, and forgetting the words makes a page askable again without touching its card.
  • new — both review offers can be switched off, in the group where everything unasked is answered. Two switches join the session tips under Behavior → General → OnboardingenableTagSuggestionNotice and enableHealthReviewNotice, both default on — which is also the first way to silence the link-review card without giving up link checking itself. An install that predates the keys keeps both: an absent key means never answered, and for an opt-out that is on.
  • new — a card in the corner offers a round of tag review. The panel only ever helped the reader who thought to go and look at it. tag-suggestions-notice says, now and then, that there is something worth accepting — in the shape that does not demand an answer: Start, Not today, Remind me in 30 days. Ten proposals before it says anything, and ten new ones before it asks again, so accepting part of a pile does not bring it straight back for the remainder. It counts only the sources that need no scan round. Start opens the Tag suggestions tab rather than a second copy of the panel over the dashboard.
  • new — a turned-down proposal can be taken back on its own. The refusals were a count and a single Offer them again, so wanting one back meant taking every refusal back with it. They are listed by name now — youtube.com → #video — each with its own way back, with the all-at-once button kept for a clean slate.
  • fix — scrolling the bookmark list closed an open row menu. The list draws a window of rows and redraws it as it scrolls, and that redraw threw away the menu hanging inside it: opened, scrolled past, gone mid-use. repaintBookmarkRowsOnly already declined to redraw under the move popover and the right-click menu for exactly this reason — the one menu that lives inside the rows rather than over them was missing from that guard.
  • fix — the suggestion rows had a column of counts that lined up with nothing. Each row was its own grid, so 1fr and every minmax were resolved against that row's own content: a long hostname or a longer reason moved the count in that row and nowhere else, and a list of fifteen rows had fifteen different left edges. The list owns the columns now and each row takes them with subgrid, which is the only way they line up without fixing every column's width and truncating the hosts — browsers without subgrid get exactly what they had, untidy rather than broken. The count itself became a number in a chip, right-aligned on tabular figures so 1, 9 and 112 sit on the same digit; fifteen rows each ending in the word "bookmark" was fifteen copies of what the column already means, and the full phrase stays as the chip's accessible name because a bare "3" read aloud is not a count of anything.
  • new — a proposal can be turned down, and the rows read as a table. Apply was the only answer a row took, so a suggestion the reader did not want had nothing to do but sit there being offered again on every visit. No thanks sits beside Apply now, recorded in settings as pattern|tag rather than as the bookmarks under it — the bookmarks change as the collection grows, and a refusal recorded against them would come back the moment one more link on that site was added. Both buttons are undoable from the toast, and a line under the list says how many are turned down and offers them again in one click. The rows themselves were a run of words with no shared edges: they are the bordered card the bookmark list uses, in five fixed columns, so the panel is scanned down a column instead of read line by line.
  • fix — the thinnest evidence produced the widest suggestion, and applying one wiped the rows you had ticked. The share a group had to agree on was counted over its tagged members, which is right — three of thirty carrying #code still says something — but nothing said how many that had to be. Two bookmarks of thirty carrying #todo agreed unanimously, and the panel offered #todo to the other twenty-eight, reading "your own tags (2 of 2)". A tag now has to appear on at least three bookmarks before it counts as agreed, which is the same three the empty panel asks the reader for. Applying went through mutateSelected, which clears the selection — correct for the bulk bar, since that is what it was acting on, but this panel acts on a group of its own, so ticks made in the list are restored afterwards. The source field on a proposal is gone: only the tests read it, and reason.kind already carried the same fact.
  • new — ten catalogue subjects that could never fire from a page's text now can. A page-text proposal needs two loose keywords, so a subject carrying one — or none — was unreachable from that source no matter what a page said. big-data, cloud-storage, containers, insurance, journals, meal-kits, newsletter, paleontology, research and school each gained three words that a page on the subject writes and a page on another does not. The validator's collision rule is what keeps them honest: a word claimed by two subjects is refused, so peer-reviewed belongs to journals alone and research is described in its own terms.
  • fix — a row menu in Config → Bookmarks closed itself a few seconds after opening. Health and the bookmarks editor share the row menu markup, class names and all, and both draw into #dashboard-layout — but health's closeAllMenus swept the whole document. Opening Config → Bookmarks calls health.clearKeyboardSelection(), health is loaded lazily, and the lazy proxy replays that call once the module arrives: a menu opened with m in the seconds before that vanished on its own, with nothing on screen to explain it. Health now closes only the menus inside its own shell, and claims Escape only for those.
  • fix — the count that opens a suggestion could only be opened with a mouse. The chip carries role="button" and aria-expanded but is a <span>, and only a click listener was bound — so Enter and Space, which fire a click on a real button and on nothing else, did nothing at all on the one control in the panel that is not a button. It answers both now, with Space swallowed rather than scrolling the panel underneath.
  • fix — Apply on a group with every member unticked did nothing, and said nothing. Untick the lot (an easy accident, since the ticks are how a single wrong member is left out) and the button wrote no tag, raised no toast and gave no reason. It now says so.
  • fix — a subject named in two words could only be read off a page that hyphenated it. Keywords are matched as single tokens, so the catalogue writes peer-reviewed and meal-kit — but the extractor split a page's text on whitespace, which left 354 of the 1,924 shipped keywords (18% of them) reachable only by a page that happened to write the hyphen itself. Adjacent words are now also offered as a hyphenated pair, in a second pass after the single words have taken the slots they wanted: a pair is weaker evidence than a word a publisher chose, and it rests on two words that each survived the furniture list on their own.
  • fix — the 177 KB tag catalogue was fetched twice per visit, and revalidated every time. Both readers of it — the review panel and the corner card — fetched it for themselves, so seeing the card and then opening the panel paid for the largest file the app ships twice over. Worse, both asked with cache: 'no-cache', which forced a conditional request on every visit even though /static/data is served immutable for a year. It is one loader now (static/js/shared/tag-catalogue.js) holding a single promise, asking for the file the way overview-features.json is asked for: versioned by the app fingerprint, and then cached until the next release. The suggestion engine itself no longer blocks the parser either — nothing needs it before the document is there.

Docs

  • docs — v1.8.0 published across every surface. static/data/whats-new/v1.8.0.json with index.json naming it first, both tokens in whats-new-stub.js moved (2026.09-dashboard-release-v1.8.0, whats-new-v282) and tests/whats-new-hidden-release.spec.js moved with them. Three spotlights carrying since: "v1.8.0" in static/data/overview-features.json — the suggestions tab, your own rules, and the two review offers — which is what Config → Overview and About → News & features draw from. MANUAL.md gained a Tag suggestions subsection under §12 and a paragraph on the bookmark list's keyboard in §16, and the sub-tab table now names all five Bookmarks tabs. README.md gained one line: the feature list is what a new reader reads, and bulk tagging is a capability rather than a release note. Config → Help says what the section's five tabs are and what each of the four sources is, helpVersionBody names 1.8.0, and two tips joined the catalogue (tipEditTagSuggestions, tipTuneReviewOffers). Asset hashes regenerated.
  • docs — the six locale files translated for this release. The keys this feature added ship translated rather than English-with-a-key: the panel, the rules editor, the scan round, the corner card and the two switches in nl, de, fr, es and zh, plus the fifteen new overview-feature keys, the two tips, the Bookmarks help and the version panel. validate:locale-parity, validate:locale-placeholders and validate:help-i18n all pass.

Don't miss a new nextdash release

NewReleases is sending notifications on new releases.