Covers the board.js journal exemption on the 1× card-pulse-local
flash. Creates two probe cards via the raw API, flags both as
"just created locally" in state.recentlyCreatedIds, runs a render,
then asserts that the journal-format one (title 9999-12-31 — a
year we can't collide with real entries) skipped the pulse class
while the __prefixed__ control card still got it.
Both probes are deleted in a finally block so a failed assertion
doesn't leave them behind for later suites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test-shield (#test-shield) was added in loader.js (always fresh
via plugin manager ?v=time()) but the removal lives in runner.js
hideProgress(). runner.js was still cached at ?v=3 in the browser,
so the shield stayed after the run. Bumping to ?v=4 in loader.js +
all 42 test files forces a fresh runner.js load.
chore: bump version to 0.9.15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Focus restoration:
* editor.js closeModal(): blur activeElement after the close so board
shortcuts (arrows, bare keys) work immediately without clicking.
* detail.js closeDetail(): same blur, skipped when openedFromSearch
(search input should keep focus in that case).
* search.js closeSearch(): blur after removing .open.
* calendar.js closeCalendar(): blur after closeDayPopup.
All four check activeElement !== body before blurring to avoid no-ops.
Test shield (loader.js):
* ensureProgressBar() now also creates #test-shield — a transparent
fixed overlay (z-index one below the progress bar) covering the
viewport below the bar. Blocks all real mouse interaction while
tests run; synthetic events from the test suite still work because
they bypass pointer-events. cursor:not-allowed as visual feedback.
Removed by hideProgress() alongside the progress bar.
ESC closes test results (runner.js):
* The existing keydown listener now also handles Escape when the
test-panel is visible. stopPropagation prevents the app's Escape
chain from firing a second close underneath.
Tests (board.test.js):
* New describe "Board: Focus restoration after overlay close":
- closing the editor returns focus to body
- closing the detail view returns focus to body
- closing search returns focus to body
- arrow keys work after editor round-trip (enter → enter → escape)
— the exact flow the user reported as broken. Dispatches
ArrowDown → Enter (detail) → Enter (editor) → Escape (close) →
asserts body focus → ArrowDown → asserts .card-focused exists.
* Fixed .not.toBe() → (x !== y).toBe(true) since the custom runner
doesn't have a .not chain.
chore: bump version to 0.9.14
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* card-interactions.test.js: group delete tests used `.dialog-ok`
(class selector) but the button has `id="dialog-ok"`. querySelector
returned null, the click was skipped, the dialog stayed open and
blocked every downstream test. Fixed to getElementById.
* runner.js closeAll(): added #dialog, #bucket-super-popover (remove),
and #gitea-overlay to the cleanup list. A failed test that leaves any
of these open no longer poisons the rest of the suite.
* runner.js copy handler: when there are failures or flaky tests, the
"copy" button now builds the clipboard text from lastResults —
only failed + flaky lines (suite name, test name, timing, error,
original error for retried). When everything passed, copies the
full output as before. lastResults is set in renderResults().
* bucket-context.test.js: move-left test used `[data-bucket]` to find
user bucket tabs, which misses super-bucket tiles (they carry
`data-bucket-super`). Changed to `[data-bucket-drag]` which is on
both regular and super-bucket tiles but not on fixed filters.
* context-menu.test.js: make-ticket pre-fill assertion now polls up
to 2s for the title input to be populated (async openCreateForm
repo-fetch was racing against the immediate assertion).
* card-join.test.js: clickJoin sleep bumped from 400ms → 800ms.
joinSelectedCards does 3+ serial API calls (fetch bodies, PATCH
winner, N×DELETE losers, _reloadAll) which sometimes exceeded the
old 400ms window on slow runs.
* Runner cache: RUNNER_V bumped from ?v=2 → ?v=3 in loader.js + all
42 test files so the browser picks up the new runner.js content
(copy handler, closeAll, retry progress bar) without a hard reload.
super-buckets: remember last selection per group
* buckets.js: module-scope lastSuperSelection Map tracks the last
active member per super-bucket parent. Updated in renderBar()
whenever activeBucket matches a group member. tileFor() uses it
as a fallback when the user is on a different bucket — the tile
shows the last-picked child's name and color instead of reverting
to the parent. The dropdown popover also highlights the last
selection when no member is currently active.
chore: bump version to 0.9.11
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* runner.js: shows a 30px fixed-top progress strip during runs (label
"test x/total:", current suite/test name with ellipsis on the left,
fixed 50%-wide progress fill on the right that turns danger-red on
the first failure). The DOM node is created in loader.js so it appears
immediately on shortcut press; runner.js only updates it. The fill
width is fixed at flex:0 0 50% so it doesn't jiggle as the test name
changes length. Suites now live on window._testSuites as a defensive
fallback so two runner instances stay in sync.
* Cache invalidation: every test file's `from './runner.js'` is now
`from './runner.js?v=2'`, and loader.js imports the runner with the
same RUNNER_V suffix. Same URL → one module instance → suite
registration and runAll see the same array. Bumping RUNNER_V (in
loader.js + every test file in lock-step) is the way to force a
re-fetch when runner.js changes.
* loader.js: ensureProgressBar() draws the strip via setProperty(...,
'important') with z-index 2147483647 attached to documentElement so
nothing in user CSS, scanline overlay (z 9999) or modals can occlude
it. Auto-run path (?test=1) also pre-renders the strip.
chore: bump version to 0.9.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>