Conversation
Fix decoding of quoted feed charset labels
Restore removed subscriptions from repeated OPML imports
The accessibility tree exposed every app dialog unnamed (#817): the <dialog> had no label and no association with the heading or prompt text it already shows. It now takes its accessible name via aria-labelledby from the cheat sheet's heading, or from the visible prompt/message text — which already identifies the item in delete confirmations. Browser specs pin the name for all three variants.
Name dialogs from their visible heading or prompt
Clearing the search box while its request was still in flight left the response free to repopulate the list (#815). exitSearch() now gives its in-flight request the same supersession treatment select() and runSearch() give theirs -- abort plus a fresh guard token -- so a late response is dropped and the cleared view holds. The no-selection branch also resets the paging state and the loading signal, which the aborted request can no longer clear for itself. Browser spec pins the behaviour with a held-then-released response.
Pressing o on a narrow screen opened the reader pane while the list holding focus was display:none'd, dropping focus to <body> and making the second o unreachable (#816). Opening now focuses the reader pane (preventScroll, so the reader's scroll position survives), and the pane listens for o itself to hand focus back to the same article row -- the only place the key can be heard once the list is hidden. Browser spec pins the round trip at 390px.
…arch-late-results
…ults Drop a search response that lands after the field was cleared
Two simultaneous confirmations of the same reset link both succeeded, each setting a different password, last write deciding (#809): the token check ran as a lookup before a write keyed only on users.id. The spend now rides in the UPDATE's WHERE clause inside the same transaction -- the row lock serialises the racers, the second re-check matches nothing, and the route answers the loser with the same 'This reset link is no longer valid.' any stale link gets. Sessions drop only with a winning commit. An integration test races two completions against a real database.
Move keyboard focus with the o pane toggle
Automatic cleanup deletes article rows straight through SQL, so an old unread newsletter could vanish while its source kept counting it: the badge said unread, and the list had nothing to show (#812). Each prune now returns the ids of the sources it shrank, and the cleanup job feeds them to recomputeUnreadCounts -- the same recount every other article change already goes through. The worker wiring is pinned by a unit test, the counter correction by a database integration test.
A push notification arriving while a source's feed job was running was answered ok and then dropped: the enqueuer's per-source job id made BullMQ dedupe the add away, and nothing remained to fetch the new content until the next poll -- for a verified websub source, up to a day (#813). A request landing on a merely-queued poll kept that poll's old payload, so a manual refresh silently lost its cache bypass. enqueueSource now looks before it adds: a waiting or delayed job gets its payload rewritten in place with the requested flags, and an active job records the request in a Redis marker that the worker consumes when the run ends, folding everything that arrived into one follow-up refresh. Marker writes are ordered so a cache bypass always dominates. Deferred runs keep their id and are skipped -- their retry reaches the request through the rewritten payload instead.
Recount unread totals for the sources cleanup prunes
Each of these runs the real oxlint binary against a temp fixture, and bun's default 5s per-test timeout measured the whole spawn, not the lint. On a loaded machine -- parallel test workers, language servers, other checkouts -- cold spawns blew past it and the suite flaked. 20s per test is still far under what a hang would take.
Make the first password-reset completion the only one
An activation link older than a day was a dead end: the account stayed inactive, registering again answered success and sent nothing, and the password reset could not reach an account that never activated (#810). Re-registration now reads the address's own state and answers only in the mailbox: a pending registration gets a fresh activation link, and an active account gets a notice -- the same success body either way, so the browser still cannot tell who exists -- carrying a reset link, the way in that never assumes the password still works. The page behind an activation link no longer activates on visit either: a mail preview or link scanner following the URL found the token spent by the on-mount POST. Activation now happens on the button, and the reset page was already submit-driven. Token digesting moves to a shared helper, since registration now stores reset digests too.
Answer a refresh that lands on a running feed job with a follow-up
…tivation-recovery
…very Recover accounts stuck behind an expired activation link
Brings staging up to main's tip so the release PR can merge (#827).
The follow-up refresh #813 promises never ran. runPendingRefresh called enqueueSource from inside the processor, where BullMQ still holds ParseSource-<id> as active -- so enqueueSource took its own active branch and wrote the pending marker straight back instead of queueing anything. The marker ping-ponged and the content still waited for the next poll, which for a verified websub source is up to a day: the exact symptom the issue is about. The unit test could not see it, stubbing enqueueSource out entirely. The id cannot be free until the processor returns, so the run answers the requests itself: take the marker, parse once more with the flags they asked for. A deferral or a failure leaves the marker for the next run, as before. This also drops a dead branch -- parseSource only rethrows HttpDeferredError, so the non-deferred catch never ran. Also: the re-registration path stored its fresh activation token after mailing it, so a failed write mailed a token the row never took and put the address back in the dead end #810 fixes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fold a mid-run refresh into the run instead of re-enqueueing it
A staging run's unit-test job hung at its first step and sat there for hours: no timeout-minutes meant GitHub's 6-hour default applied. Every later staging run then parked in docker-build's await_turn, which counts any older run still in flight, so one stuck runner stalled the whole release queue -- visible as "CI is stuck" with nothing failing. The caps are well above the real runtimes (lint ~20s, compile ~15s, unit ~30s, browser ~1m), so they only ever fire on a hang. Job names and ids are untouched: they are pinned as required checks on main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cap the quality jobs so a hung runner cannot hold the release queue
Free usage is exhausted and the next invoice is real money. Every job now
runs on GitHub-hosted runners, which are free for a public repository:
blacksmith-{2,4}vcpu-ubuntu-2404 -> ubuntu-latest, and
useblacksmith/setup-docker-builder -> docker/setup-buildx-action.
The image build could not follow that straight across. Blacksmith built
both architectures natively; ubuntu-latest would have to emulate arm64
through QEMU, which turns the Bun build from a minute into most of an
hour. So each architecture gets its own runner -- ubuntu-latest and
ubuntu-24.04-arm, both free here -- pushes an untagged blob, and reports
its digest. publish_images keeps its id and its name, and now assembles
the two digests into the manifest that claims the sha tag. It refuses to
publish unless both legs arrived: a one-arch manifest would strand
whichever node runs the other, and promote would copy it onward.
Layer caching moves with it. Blacksmith cached implicitly; type=gha does
not, and needs a scope per target and architecture, or the three targets
evict each other on every run.
Job-level timeouts fill in everywhere they were missing, 15 minutes
unless the work needs longer -- the same gap that let a hung unit-test
job hold the staging release queue for an afternoon.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The options page opened by reading chrome.storage.sync, and only wired up its inputs once that resolved. `load` fires when the script starts, not when the read lands, so a change arriving in that window reached no listener and was dropped in silence -- the field kept a value the page had already decided to reject, with nothing said. Too narrow for a person to hit by typing, wide enough for a test: the browser suite failed on it the first time it ran on a runner with different timing. Listeners now attach synchronously and the stored values fill in afterwards. A change in the window before hydration reverts to an empty field rather than the stored address, which is what the field already shows at that instant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…orage Attach the options listeners before reading storage
Move CI off Blacksmith runners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.