Keyboard — navigation
●
→ next modality
tested
webview Right/Left switch modality
●
← previous modality
tested
webview Right/Left switch modality
●
↓ next tuple
tested
webview Down/Up move between tuples
●
↑ previous tuple
tested
webview Down/Up move between tuples
●
1–9 jump to modality N
tested
webview number keys jump to a modality
●
Space (hold) flip to previous modality
tested
webview Space (hold) flips to the previous modalityKeyboard — actions
●
] reorder modality right
tested
webview ] key reorder also keeps tooltips correct
●
[ reorder modality left
tested
webview reorders the current modality left
●
Enter toggle winner
tested
webview Enter toggles a winner and posts setWinner
●
C toggle crop mode
tested
webview C enters crop mode
●
Esc reset zoom / cancel crop / close help
tested
webview Escape cancels crop modewebview wheel zooms in; Escape resets
●
Del / Backspace deletes what the host says: the tuple, or the one image
tested
webview a tuple-unit host gets deleteTuplewebview an image-unit host gets deleteImagewebview a reordered strip still names the original columnMouse / pointer
●
Scroll wheel zoom in/out
tested
webview wheel zooms in; Escape resets
●
Drag to pan
tested
webview drag pans the image when zoomed in
●
Click modality pill to switch
tested
webview clicking a modality pill switches modality
●
Alt accelerates zoom (compounded, not scaled)
tested
unit compounds the zoom stepwebview Alt makes a zoom notch travel further
●
Every axis clamps to its content
tested
unit clamps at both ends
●
Wheel deltas honour deltaMode (lines, pages)
tested
unit converts a line-mode delta into pixelswebview a line-mode wheel scrolls rows, not pixelsTools window (floating panel)
●
Crop button
tested
webview shows the floating panel toolswebview C enters crop mode
●
Delete button → deleteTuple
tested
webview shows the floating panel toolsunit tupleDeleted -> refresh -> re-save in that orderunit re-saves after EVERY stepunit indices pre-shifted for earlier splicesunit deletes every file before executing the removal planunit files, tupleDeleted, refresh, save
●
PPTX button → exportPptx (busy spinner, no-votes whole view)
tested
webview shows the floating panel toolswebview PPTX export with no votes sends the whole view with null winnerswebview PPTX button goes busy on click and ignores re-clickswebview pptxError clears the busy stateunit emits one plain slide per modalityunit orders slides by modalityOrderunit marks exactly the winner captionunit a voted crop never ships without its parentunit increments past the max existing numberunit speaks the canon sequenceunit posts pptxError exactly once
○
Minimap navigator + viewport rect
no test
○
Drag panel to reposition
no test
●
Collapse/expand panel
tested
webview collapse button toggles the floating panelCrop tool
●
Enter crop mode (crosshair)
tested
webview C enters crop mode
●
Draw crop rectangle
tested
webview drawing sets a crop rect
●
Confirm → cropImages message
tested
webview Enter posts cropImages
●
Cancel crop (Esc / ✗)
tested
webview Escape cancels crop mode
○
Resize via 8 handles
no test
○
Move rectangle by dragging inside
no test
○
Double-click cardinal handle → snap to square
no test
●
Crop output naming (_cropNN shared across modality dirs; per image where they share one) + rect scale/clamp + shared flow order
tested
unit max across modality dirsunit clamps width and height to the image boundsunit speaks the canon transcriptunit numbers past the max existing cropunit injects the crop meta as a readable tEXt chunkunit reports cropComplete when renderCrop returns plain Uint8Array bytes
●
A file list names each crop after its own image, so columns sharing a directory never write one path twice
tested
unit names each crop after the image it is cut fromunit separates two images whose stems collide across extensionsunit numbers against the image's own listingunit names each crop after its own image in a file listunit keeps the one shared name where the columns are directories
●
A file list’s crops land on disk only — both hosts, since one used to grow a row per crop
tested
unit lands a file list's crops on disk without adding themunit still lands every arrival, then cropComplete, then thumbnails
●
Crop rect crosses modalities in relative form (shared toRelativeRect)
tested
unit divides x/w by the width and y/h by the heightunit round-trips through scaleAndClampRect
●
Screen↔image coordinate mapping
tested
unit viewer center maps to image centerunit are inversesunit carousel offsetModality reorder
●
Reorder swaps pill name
tested
webview a pill drag moves both the pill name and its path tooltip
●
Reorder swaps pill tooltip (path)
tested
webview a pill drag moves both the pill name and its path tooltipwebview ] key reorder also keeps tooltips correct
●
Watcher-inserted modality preserves user rearrangement + focus
tested
unit a watcher-inserted modality preserves the user rearrangementwebview custom order and focus survive an insert
●
Drag a pill to reorder (mouse)
tested
webview dragging a pill 0 -> 2 moves it, the pills between close upwebview a click, and a 3 px wobble, select the pill without reorderingwebview a CDP pen tap on a pill selects it with exactly one click on the pillwebview a pen down and up with no click from the engine selects the pill with exactly one clickwebview a lifted end pill, its lift scale included, stays within the row's ends at either end of a dragwebview under fractional pill widths the drop slot turns at the true midpoint and the pills slide by the true pitchwebview a press during a landed pill's glide leaves the glide running and measures the row at rest
●
Hold a pill, then drag (touch)
tested
webview a held pill lifts with one vibration and drags 0 -> 2webview a held first pill is drawn within the row's near end from the moment it lifts
●
A move shifts the pills between; votes, peek target and shown modality follow
tested
unit a vote on a pill strictly between from and to follows that pillunit moving 0 -> 2 of fourwebview dragging a pill 0 -> 2 moves it, the pills between close up
●
Ending a drag never clicks the pill under it
tested
webview dragging a pill that is not current does not select itwebview and a tap 50 ms after the drop selectswebview and a tap 50 ms after its fling selectswebview a real mouse click 50 ms after a mouse drag drop selects the pill
●
A pill's own capture loss does not end the drag (Android)
tested
webview a lostpointercapture at the lifted pill mid-drag does not end the drag
●
Long row: a drag restyles only passed pills and commits once
tested
webview long row: a drag restyles only the pills it passesunit exactly at a neighbour midpoint does not pass it; strictly beyond does (right)unit moving right: the pills it passed slide left by one shift
●
A finger pans and flings the pill row; a tap selects
tested
webview a quick swipe scrolls the row and reorders nothingwebview a touchstart on a pill arrives default-prevented
●
No ←/→ reorder buttons; [ ] still reorder
tested
webview the reorder buttons are gone and [ ] still reorderWinner voting
●
Toggle winner via Enter
tested
webview Enter toggles a winner and posts setWinner
●
Toggle winner via carousel circle click
tested
webview clicking a carousel winner circle posts setWinner
●
Mouse voting disabled on tiny tiles (3x circle guard)
tested
unit mouse voting is disabled below 3x the winner-circle size
○
Win counts shown on pills
no test
●
Persistence to results.txt
tested
integration write then read round-tripsunit maps winner indices to durable modality namesintegration CRLF-encoded resultsunit serializes the exact header and vote linesunit round-trips through serialize and parseunit empty winners delete the results fileunit winners serialize through the shared formatCarousel
●
Thumbnail grid renders
tested
webview renders the viewer and ingests fixturesunit plans slots in scanline orderunit reports a slot whose modality the tuple lacks as missingunit zero-item terminal caseunit missing slots error out firstunit zero items still posts the terminal progress tickwebview corrupt thumbnail payload falls back to the error placeholderwebview restores the tile and re-arms the retrywebview pool rebinds after a decode failure keep the placeholderwebview a recycled empty tile paints exactly like a never-filled onewebview every visible tile carries a decodable srcunit the blank tile src is a decodable 1x1 fully transparent pngwebview a genuinely missing slot still shows the
●
Thumbnails on the binary wire (bytes+mime, object-url lifetime)
tested
unit a pack-slice thumbnail is copied tight before it is postedunit an on-demand re-request posts the same tight binary shapeunit revokes the superseded url and never the incoming oneunit adopts the successor before revoking the loserunit stores the placeholder data url but never revokes itunit delete and clear revoke exactly what they dropunit re-keys a row removalwebview a thumbnail payload paints from an object urlwebview a superseded delivery releases exactly one urlwebview pooled-row rebinds repaint from the cache without revoking a live urlwebview the error placeholder releases the slot url and a re-init releases all of themwebview standalone build boots the real webview
●
Virtualized carousel (pooled rows bind to visible tuples)
tested
webview virtualized carousel binds a pool row to each visible tuple
●
Click thumbnail to navigate
tested
webview clicking a carousel thumbnail navigates
○
Scroll carousel
no test
○
Horizontal carousel scroll (wide many-modality sessions)
no test
○
Drag resize handle
no test
●
Arrows move the carousel horizontally too
tested
unit centres the item in the viewportwebview an arrow key moves the carousel horizontally
●
Alt accelerates carousel scrolling
tested
unit multiplies a linear scrollwebview Alt makes a carousel wheel notch travel further
●
Wheel burst applied once per frame
tested
webview travels their exact sum, in one apply
●
Thumbnail arrivals never search the DOM
tested
webview searches the carousel DOM zero times
●
Gesture binds blank, fills progressively after
tested
webview a gesture binds blank tiles, and they fill once it settles
●
Columns virtualized: DOM bounded by the viewport
tested
unit is bounded by the viewport, not the modality countwebview doubling the modalities does not grow the DOM
●
Materialized columns follow the horizontal scroll
tested
unit advances by one column per pitch scrolledwebview scrolling the strip materializes later columns
●
Strip tiles are centre-cropped
tested
webview a fine primary pointer keeps the desktop layoutwebview a coarse primary pointer gets touch-uiTouch layout
●
Touch layout only under a coarse primary pointer
tested
webview a fine primary pointer keeps the desktop layoutwebview a coarse primary pointer gets touch-ui
●
Swipe ←/→ switches modality (skips hidden)
tested
webview a sideways swipe on the image steps modality and skips a hidden one
●
Swipe ↑/↓ switches tuple
tested
webview an up swipe on the image steps to the next tuple, a down swipe back
●
Touch and hold flips to the previous modality
tested
webview touch and hold on the image flips to the previous modality until release
●
Pinch zooms, two fingers pan
tested
webview a two-finger pinch zooms, and lifting one finger never swipeswebview two fingers dragged together pan the image
●
Tap the zoom readout to reset zoom
tested
webview a tap on the zoom readout resets zoom and pan
●
▦ opens the full-width strip; a tile tap navigates and closes it
tested
webview opens a full-width strip; a tile tap navigates and closes itwebview a finger drag scrolls its rows and taps nothingwebview a single-tuple comparison (no ▦) has nothing before the pill row eitherwebview follows the strip class main.ts sets, and the shell CSS uses no :has()webview the ▦ icon is an SVG centred in its 40 px button
●
No context menu, tooltip, Tools panel or vote circles
tested
webview no context menu, path tooltip, Tools panel or vote circlewebview a finger on the image never reaches the mouse handlerswebview a hovered pill keeps its resting look where the primary pointer cannot hover
●
Help shows the touch gestures
tested
webview help shows the touch rows and hides the keyboard rowsViewer / status bar
●
Viewer activates on init
tested
webview renders the viewer and ingests fixturesunit builds the init message with dense tuplesunit densifies tuplesunit defaults colors to the positional palette
●
Modality pills render (per modality)
tested
webview renders a modality pill per modality
●
Active pill reflects current modality
tested
webview active modality pill reflects current modality
●
Hide/show modality pills; arrows skip hidden
tested
unit a hidden neighbour is skippedunit a run of hidden pills is skipped in one step
●
Canvas renders the image
tested
webview renders the viewer and ingests fixtures
●
Full-image serve: one terminal reply (image XOR imageError), passthrough vs convert
tested
unit a missing image posts exactly one imageErrorunit a passthrough extension serves the original bytesunit a convert failure posts exactly one imageError
○
Zoom % readout in status
no test
○
keepZoomOnTupleChange setting (zoom survives tuple switch)
no test
●
Help modal (? open / close)
tested
webview opens the help modal and Esc closes itwebview help modal fits entirely within a small viewportwebview help table lists mode-dependent and mouse inputs
●
Copy Image (context menu / Ctrl+C) writes the displayed image
tested
webview copy writes the displayed image to the clipboardwebview copy after navigation writes the currently displayed image
●
Version shown in the help modal
tested
webview help modal shows the ImageCompare version carried by initwebview no version in init renders no footer
●
Right-click image: menu items
tested
webview right-clicking the image offers the image items
●
Right-click pill: menu items + Hide/Show
tested
webview right-clicking a pill offers the pill items
●
Menu hides what the host cannot serve
tested
unit the same menu minus exactly the items it cannot servewebview a host that cannot reveal is never offered the item
●
Local items never reach the wire
tested
unit keeps the two local items reachablewebview a host action the webview cannot perform goes to the host
●
Menu dismissal (Escape, click away)
tested
webview Escape dismisses the menuwebview a click elsewhere dismisses the menuwebview keys do not act behind an open menuwebview the menu closes when the host re-indexes
●
Help modal names only offered items
tested
unit derives help text that names only what this host offerswebview the help modal names only the items this host offers
●
Action notices, worded by the webview
tested
unit keeps the wording the vscode notifications usedwebview a host event is worded by the webview
●
Reveal action only where the host can
tested
unit offers the reveal action only to a host that can revealwebview a host that cannot reveal gets the same words and no action
●
Faded action notice stops taking clicks
tested
webview a faded action notice stops taking clicks
●
Failures toned as failures, one channel
tested
unit never offers an action on a failurewebview a failed export is toned as a failure
●
Modality pills: one scrollable row
tested
webview the pill row is one scrollable strip
●
Nothing before the pill row: no empty ▦ box, no gap
tested
webview nothing in the bar precedes the pill row: no empty ▦ box and no gap before the first pillwebview a single-tuple comparison (no ▦) has nothing before the pill row either
●
Wheel scrolls the pill row, without navigating
tested
webview a wheel over the pill row scrolls it
●
Navigation teleports the pill row to the selection
tested
webview an arrow key teleports the pill rowwebview a digit jump and a pill click centre the same way
●
Active pill ring fully visible, all four sides
tested
webview the active pill's focus ring is not clippedwebview a hovered, current end pill 200 px wide paints inside the row at either scroll endwebview a hovered, current sole pill 200 px wide paints inside the row
●
No scrollbar paints over the pills
tested
webview no scrollbar paints over the pill row
●
Switching modality does not rewrite labels
tested
webview mutates classes only, never the labels
●
Navigation slides, wheel jumps
tested
webview navigation slides the pill row, a wheel jumps itBackend — scanning & matching
●
Mode 1: dir with modality subdirs
tested
integration mode 1: single dir
●
Mode 3 from a folder: loose images open as the file list they are, _cropNN excluded
tested
unit a directory of loose images opens as the file list it isunit excludes this app own _cropNN writesunit shows the crops anyway when excluding them would leave nothing
●
Mode 1 at one column: a single image subdir opens, and structure wins over loose images
tested
unit opens a single image subdir as mode 1 with that one columnunit prefers the structure: one imageful subdir winsunit keeps mode 2 at two columns
●
Partial tuples (missing modality)
tested
integration partial tuple when a modality is missing
●
Symlinked modality dirs and images are scanned
tested
unit scanForImages follows symlinksintegration a dangling symlink types as SymbolicLink and never as File
●
Modality dirs listed concurrently (capped), assembled in caller order
tested
unit issues all 11 directory listings at onceunit overlaps the subdirectory listings of a mode-1 rootunit caps the fan-out at exactly 16 and lists in wavesunit keeps the caller order when the directories resolve slowest-firstunit keeps the caller order across concurrency wavesunit propagates the earliest failing directoryunit sorts each directory naturally, whatever order the listing returnsunit survives a scrambled completion order over 12 directoriesunit one very slow directory in the middle finishes last
●
PPMX files scanned as images
tested
integration PPMX files are scanned
●
Trie matching: exact basenames
tested
unit matches identical basenames
●
Trie matching: pred matches gt not crop
tested
unit _pred file matches the _gt original
●
Watcher arrival placement: slot-fill vs new tuple (shared with standalone crops)
tested
unit exact-basename sibling fills the free slotunit a taken slot means a new tupleunit post-crop sequenceunit shifts winners and the current index up
●
Existence sweep: deletions derived from the cycle's directory listings (one pooled task per dir, not per file)
tested
unit a steady-state cycle costs one pooled task per watched directoryunit the cycle cost does not grow with the number of filesunit a real deletion is still detectedunit a modality directory that cannot be listed still reports its files goneunit a tracked file that became a dangling symlink is reported goneunit a tracked file under no listed directory keeps its own existence checkunit costs one pooled task per watched dir, not one per fileunit whose existence probe reports the link, not its target
●
Emptied comparison / removed folder: the terminal notice instead of a spinner over the last frame, and recovery when content returns
tested
webview every row deleted leaves a notice, not a spinner over the last framewebview the last frame does not survive the last rowwebview every modality removed leaves the same noticewebview a comparison whose folder is gone names the folder insteadwebview a folder that comes back clears the notice and shows its imagesunit says nothing while there is anything to drawunit speaks at zero rows, whatever the columns sayunit speaks at zero columns, though the emptied rows surviveunit names the folder when the host established the root is goneunit distinguishes an emptied comparison from a folder that went awayunit falls silent again as soon as content returnsunit the comparison root going away is reported by nameunit the root coming back clears the noticeunit a root that merely cannot be listed reports nothingunit then re-adopts the column and rows when it comes backunit re-arms a watcher for every re-adopted directoryunit adds nothing a second time when the next sweep sees the same recreated tree
●
Path disambiguation, and the URI-vs-filesystem path space at the node fs seam (POSIX/Windows parity)
tested
unit same display names for equivalent POSIX pathsunit Windows drive-letter paths disambiguateunit a Windows watched dir reaches node as a drive-rooted pathunit a POSIX watched dir is handed through byte for byteunit a delete it reports carries the tracked URIunit still lands as a delete, though the probe calls it presentBackend — formats & metadata
●
PNG crop metadata round-trip
tested
unit round-trips crop metadataunit injects and reads back a value
●
Thumbnail packfile cache (warm open; survives a close)
tested
unit round-trip — every key comes back byte-identicalunit uuid mismatch between idx and pack is rejectedunit dispose() publishes the pending pack instead of dropping itunit flush() resolves only once the pack and idx are on diskunit flush() also waits for the per-entry .jpg writes it startedunit the shared bed teardown does not return while a cache write is still in flightunit never publishes an empty pack over a good oneunit the pack a close left behind serves the next open
●
Thumbnail cache key sees an in-place overwrite (ctime); superseded entries evicted
tested
unit an in-place overwrite with identical mtime and size serves the new pixelsunit a fresh service re-reads the overwritten fileunit a superseded key is evicted
●
Thumbnail cache expires by last use (an in-use pack survives the sweep; a deleted one is republished)
tested
unit a week-old pack that is serving this session survives the sweepunit a week-old pack nobody opened still expiresunit a pack deleted under a live session is republished by the closeunit a symlinked cache entry expires by ageintegration a symlinked cache entry lists as File|SymbolicLink and still expires by age
●
Jimp fallback tier (Sharp unavailable → real thumbnails, same box and format)
tested
unit generates a real thumbnail when Sharp is unavailableunit converts a full image to PNG when Sharp is unavailable
●
Sharp WASM tier selection (native blocked, wasm32 exempt, resolver restored)
tested
unit retries on Unsupported CPU with native @img blockedunit restores the process-global resolver after a successful WASM retryunit returns null instead of throwing when the WASM tier fails toounit does not retry an error that is not the CPU/loader signatureSession files
●
Save Session As (Ctrl+S / title bar)
tested
unit serializeSessionFile — relative only when every path is inside destDir
●
Session-file version gate (future versions rejected)
tested
unit version gate — missing and current pass, future is rejected
●
The selection shape’s behaviour is one table both hosts read, not a mode number tested at each site
tested
unit the mode table (modePolicy.ts, real code)unit carries whichever delete unit the host declareswebview the help row and the button hint follow the declared unitwebview a tuple-unit host keeps the row wordingwebview standalone opens a folder of loose images
●
A PPTX export lands in the comparison’s own root: base dir, modality parent, or the first image’s directory
tested
unit export output directory (exportTarget.ts, real code)Extension lifecycle
●
Extension activates
tested
integration extension is present and activates
●
openInCompare command registered
tested
integration openInCompare command is registered
●
Work-pool width sized from usable parallelism, capped at the libuv ceiling (imageCompare.maxConcurrentReads overrides)
tested
unit sizes from usable parallelismunit an explicit width override winsunit clamps the pool width
●
Debug logging to the ImageCompare output channel (imageCompare.debug)
tested
unit writes a tagged line to the ImageCompare channel with an elapsed prefixunit turning debug on at runtime starts logging without re-activationunit disposes the output channel when the extension subscription is disposedunit an idle window stops repeating itselfunit a pool with work in it is reported every cycleunit a cycle that finds a deletion still reports it
●
Open path attributed in one [IC-OPEN] rollup (scan/watchers/boot/init/hand-off)
tested
unit renders one line whose spans are the differences between consecutive marksunit time between marked spans is reported as `other`, never absorbed into a neighbourunit sendInitData closes the trace and logs exactly one [IC-OPEN] line, ahead of the sweepunit reports the serialized init payload size, measured on the payload that was postedunit counts the image files handed to the matcher, and times the matcher inside the scanunit the html mark sits at the html assignmentunit the ready mark sits at the top of the ready handlerunit the sweep mark sits at the sweep
●
Instrumentation is free while debug is off
tested
unit debug off: the message thunk is never invoked and nothing reaches the channelunit the init payload is serialized for its size only inside the trace gateunit debug off: the scan carries no stats at allunit debug off: serving thumbnails updates no counter at allunit turning debug off at runtime stops the counters mid-sessionunit no debugLog call in fileService.ts runs without a debugEnabled() guardunit serializes the init payload exactly once with debug on, and never with it off
●
Prefetch waves logged on issue and on drain (sparse tuples included)
tested
unit a dense wave logs issuance and a rollup carrying the loaded bytesunit a wave whose FIRST slot has no image still logs both linesunit a wave with a hole in the middle still logs both linesunit still rolls the wave up on the debug channel
●
User-facing image pushes are never delayed by wire backpressure
tested
unit the policy never withholds a user-facing pushunit a user-facing image posted mid-wave is not queued behind speculative bytesunit never withholds a user-facing push — not while sweeping, not over budget
●
Prefetch pushes yield the wire while the thumbnail sweep drains
tested
unit the sweep drains without a prefetch wave on the wireunit every thumbnail has crossed the wire in under 1.5 virtual secondsunit withholds every speculative push while a bulk sweep drainsunit deferred speculation is delivered, not dropped, once the sweep ends
●
Speculative transfers bounded in bytes in flight (remote sessions only)
tested
unit speculative bytes in flight stay inside the budgetunit bounds speculation in bytes, not in messagesunit lets one over-budget image go aloneunit is unlimited on a local session, whatever the setting saysunit with the budget off (local session) the wave still head-of-line blocks
●
The thumbnail sweep always gives the wire back (settle, throw, or stall)
tested
unit releases the wire when a synchronous throw escapes the sweep prologueunit releases the wire when the sweep stops making progressunit does not cut short a sweep that is still settling slotsunit ends once when a dispose abandons it
●
Parked and held image posts re-index with the splice that moves their slots
tested
unit a new tuple inserted ahead of them shifts bothunit a removed tuple drops its own posts and shifts the ones behind it downunit a modality splice drops them, exactly as it clears loadedImages
●
Parked and held image posts die with the slot they name (no ghost after a delete, no drop for a live slot)
tested
unit drops the parked payload the moment the delete is seenunit drops a payload parked inside the rename window when the delete commitsunit drops the stale payload when the file comes back under its own nameunit drops the stale payload when a rename lands on the slotunit drops the stale payload when the file is rewritten in placeunit drops the stale payload a forceReload retry is asking pastunit never lets a held burst payload for a deleted slot reach the wireunit leaves the park alone when the byte cache merely evicts a live distant slot
●
Payloads parked by a scrub burst trickle out one per tick (the one un-gated route to the wire)
tested
unit a park released mid-scrub trickles out one payload per tick
●
A closed panel leaves no transport timer armed
tested
unit dispose clears the outstanding ack watchdogs
●
A closed panel leaves no fs.watch handle and no existence-poll interval behind
tested
unit every fs.watch handle the open created is closed with the panelunit the delete-poll interval is cleared with the panel
●
Thumbnail cache-tier accounting (memory/pack/disk/generated)
tested
unit a cold read counts as generated, the repeat as memoryunit a fresh service reading the published pack counts as packunit a per-entry JPEG with no pack counts as diskunit six concurrent pack hits report their own workunit the one-off pack read is reported onceunit the tiers BELOW the pack inherit the same shared waitunit the verbose per-thumbnail line separates the item ms from the waitunit the sweep rollup prints the shared read once
●
results.txt CRLF parsing (cross-platform)
tested
integration CRLF-encoded resultsunit parses CRLF-encoded results text
●
Platform targets published (every host VS Code can ask for, plus universal)
tested
unit builds every VS Code platform target plus a universal fallbackunit gives each native target npm flags that match its platformunit passes the matrix libc through to npmunit prunes the natives npm restores, keeping only the target pairunit registers a packed-VSIX binary expectation for every matrix targetunit keeps the scan body free of the quote that would truncate itunit accepts every pruned leg, including the two win32 shapes and universalunit rejects the four-tier linux-arm64 shape that 0.4.0 shippedunit rejects the pre-prune shape on every leg that can carry one, universal includedunit runs the whole step under a real shell: exit 0 for a conforming VSIX, exit 1 for a strayunit still fails a VSIX that lost the WASM tier, or its own native binary, or names no known target
●
A pull request builds every target and publishes nothing (gates, cancellation, one recipe)
tested
unit defines exactly the jobs these cases reason aboutunit builds every platform target and universal on a pull_requestunit keeps the ten-leg recipe in exactly one workflowunit cannot reach either marketplace, or the post-publish checkunit skips its own test and test-full jobs on a pull_requestunit still runs the whole chain on a tag when both gates passunit refuses to build on a tag when either gate failed or was cancelledunit cancels a superseded pull-request run and never a tag rununit models the Actions semantics these cases lean onLoading policy
●
Tuple arrival requests only the on-screen modality
tested
webview a navigation requests only the modality on screenunit requests exactly the on-screen modality and defers every sibling
●
A prefetch wave takes the on-screen column and its nearest siblings, not the whole tuple
tested
unit leads with the on-screen column, then the nearest sibling either sideunit never widens past three columns, however wide the tupleunit never speculates on a hidden columnunit issues the visible column and its nearest siblings, not every modality of every neighbourunit never speculates on a neighbour column the user is nowhere nearwebview tupleFullyLoaded reports the strip as displayed
●
A wave fetches every neighbour's on-screen column before any sibling column
tested
unit issues every neighbour's on-screen column before any sibling columnunit is three columns per tuple at the field shape, not tenunit caches the on-screen column of both neighbours
●
Siblings wait for a dwell that navigation cancels
tested
webview a burst of navigations never asks for the siblings of the tuples it passes
●
Siblings ordered by display distance, nearest first
tested
webview the siblings follow after the dwell, nearest-first over the display orderunit orders nearest-first, forward before backward at equal distanceunit measures distance over the user-rearranged display order
●
Hidden modalities are never speculated on
tested
webview hidden modalities are never speculated onunit skips hidden modalities entirely
●
Flip inside the dwell window loads at VISIBLE
tested
webview flipping to a sibling inside the dwell window loads it at once
●
A flip after the dwell re-asks a tail-ranked slot at VISIBLE
tested
webview a flip after the dwell re-asks a tail-ranked slot at VISIBLEunit re-asks when the slot is queued below the rank now needed
●
Sibling tail never takes a slot the sweep could use
tested
unit the sibling tail never takes a slot another class has queued work forunit yields every slot to the thumbnail sweep while it has work
●
Leaving a tuple cancels its queued image loads
tested
unit keeps at most the current tuple queued while browsing six tuplesunit cancels the panel's tuple loads on dispose
●
Open-time thumbnail sweep starts at the tile the user is on (cross first, then row-major) and re-aims when the user jumps
tested
unit walks outward from the centre row, forward first on a tieunit fills the focused row and column interleaved, then the rest row-major centre-outunit stops the column arm at the radius and finishes row-majorunit a radius past the ends of the grid is simply the whole columnunit a radius of zero is still one row of crossunit stops the cross at the screenful the webview reportedunit alternates the two arms one slot eachunit an arm that runs out lets the other continueunit skips empty cells without spending an arm turn on themunit ranks columns by the strip as shownunit hidden columns are swept last, never droppedunit the focused column is swept first even when the user has hidden itunit aims at the column the webview reportsunit drops nothing while the host reports the same tile from a fresh objectunit a centre moved mid-walk re-aims the remainderunit the current row and its neighbours are dispatched firstunit starts at the row the panel opened on and walks outwardunit re-aims mid-sweep when setCurrentTuple moves the panelunit serves the new centre after one running batchunit serves the row the user jumped to after one running batchunit drops only the sweep's own queueunit dispatches its first chunk as one centre-out band, reading the centre onceunit finishes a grid under a centre that moves on every read, instead of livelockingunit re-aims the sweep exactly once, after the burst endsunit still cancels the tuple being left on every keystrokeunit a hidden panel dwells without re-aimingunit leaves no dwell pending when the panel is disposed mid-burstunit holds the dwell open for a whole burst and re-aims onceunit waits the navigation debounce, not a dwell of its own inventionunit opens the sweep at the tuple its host reportsunit drives the same burst through the real provider and the real standalone adapterwebview clicking a tile in another column reports that column at oncewebview the reported column is the display position within the reported orderwebview an arrow-key column move reports its column once the burst settleswebview a held arrow key reports once, at the column the burst ended onwebview a digit jump reports the column it landed onwebview a bracket reorder reports the strip it rearrangedwebview a click inside the dwell window cancels the pending key reportwebview a pill click reports its column at once, with no dwellunit holds a held key open for the whole burst and reports onceunit reports exactly once for a settled keypressunit reports once per settled keypressunit reports a picked column at once, with no dwell of any kindunit lets a pick cancel a burst still waitingunit lets a key re-arm after a pick
●
Re-centring never loses, duplicates or strands a sweep slot; an abandoned host stops it dead
tested
unit every slot is handed out exactly once however often the centre jumpsunit a returned slot is handed out again exactly onceunit every slot is delivered exactly once when the centre moves on every batchunit settles a disposed panel's cancellations instead of returning themunit stops reading the grid the moment the panel is disposedunit reads nothing new after the close, where it used to read the rest of the gridunit a host that does not cancel its own queue still stops within one chunkunit resolves without dispatching anything when the host was already gone at sweep startunit abandons what the cursor still holds, where a re-aim returns what it droppedunit every planned slot is delivered exactly once, and the tail is sweptunit rows with no planned items are skippedunit a centre past the ends is clamped to the gridunit hands out every planned slot exactly once across 350 seeded schedulesunit hands out every planned slot exactly once across 300 seeded schedulesunit hands back a slot the walk has passed on the column axisunit returns a slot at the radius boundary the remainder has already passedunit a non-finite radius reads as no reportunit a slot returned after the aim moved is re-placed by the NEW aimunit drops the queue when only the COLUMN movesunit settles every slot exactly once and ends, across 24 seeded runs
●
The sweep feeds the pool in bounded chunks instead of the whole grid
tested
unit keeps at most `chunk` slots outstanding and refills as they settleunit the default dispatch bound is SWEEP_CHUNKunit keeps at most SWEEP_CHUNK slots in flight, not the whole grid
●
Two comparison tabs sweeping at once take turns in the pool instead of one draining first
tested
unit alternates between two groups where a single FIFO drained the first group wholeunit keeps plain FIFO within a groupunit a group that joins late enters the rotationunit cancelling one group leaves the other group's order and the rotation intactunit serves the second tab within one batch instead of after the first tab's whole queued chunkunit splits the bulk slots roughly evenly once both tabs are sweepingunit gives a second comparison tab a slot in the first batch
●
A hidden comparison pauses its thumbnail sweep and resumes it, owing every slot, when shown
tested
unit stops taking bulk slots within one running batchunit delivers every owed slot exactly once when the panel comes backunit a sweep paused before its first dispatch waits for the resumeunit a dispose while paused still ends the sweepunit reads nothing more while hidden and finishes every slot exactly once when shown againunit gives the wire claim back when a hidden panel is disposedunit the sweep's chunk is the whole cost of hidingStandalone
●
Standalone HTML builds, boots, and matches via the real scanForImages
tested
webview standalone build boots the real webview and matches via the real scanForImageswebview standalone landing shows the ported dropzone chromeunit standalone artifact freshness
●
Browser shims provide every member the bundled shared modules call
tested
unit provides every Buffer static the shared modules callunit isBuffer discriminates shim buffers from plain Uint8Array
●
Standalone schedules IO through the shared work pool (width clamp shared with the extension)
tested
unit clamps the pool width
●
Standalone external-change detection (poll + observer, shared pollPlan decisions)
tested
unit pollPlan diffSnapshotsunit pollPlan pairRenamesunit commitSlotRemovalwebview standalone poll detects an external new file as a granular tupleAddedwebview standalone poll delivers external deletes, content changes and results.txt editswebview standalone re-open cancels the previous rootwebview standalone poll re-verifies a removal with stat before reporting itwebview standalone re-open stops the old root poll timer and observer
●
Modality-dir adoption shared with the provider (poll adopts a copied-in dir; a dir rename lands as remove-then-adopt)
tested
unit adoptionPlan newModalityDirCandidatesunit adoptionPlan adoptableImagesunit adoptionPlan applyModalityInsertwebview standalone poll adopts a modality directory copied in after bootwebview standalone poll executes a modality dir rename as remove-then-adoptwebview standalone poll orders a same-cycle dir rename remove-then-adopt within one cyclewebview standalone re-open picks up a modality dir added while the comparison was open
●
Provider existence-sweep decisions shared via pollPlan (barren-dir policy, name-first diff, per-dir arrivals/candidates)
tested
unit pollPlan planSweepDirsunit pollPlan planDirSweep
●
Firefox/Safari read-only drag-drop (webkitGetAsEntry walker into the FileList backend)
tested
webview standalone no-FSA drop path walks webkit entries into the read-only backendwebview standalone landing falls back to the read-only picker without FSA
●
Page published next to an imagecompare.json opens that comparison, its listed files read-only (Claude artifacts, any static host)
tested
webview standalone page published next to an imagecompare.json opens that comparison, its listed files read-onlywebview copies a published image's URL as its pathwebview standalone page with no imagecompare.json beside it keeps the landingwebview standalone page reports a manifest that reaches outside its directory instead of opening itunit returns the listed relative paths in manifest orderunit rejects a manifest with no filesunit rejects paths that leave the page directoryunit reports a page that is not JSON as a parse failure, not a comparisonunit lists the manifest as a folder whose subdirectories are the columnsunit reads each file through the injected fetch by its manifest-relative path, lazily, not at listing timeunit is read-only: nothing a published page lists can be written or deleted
●
Published comparison: votes kept per viewer in the browser (survive a reload), copied out by the results.txt button
tested
webview keeps the viewer's votes across a reload and copies them out as results.txtwebview the results.txt button shows with copyResults and voting, and posts copyResultswebview the results.txt button stays hidden where voting is offunit words the results.txt copy outcomesunit survives a reload: a fresh layer over the same store and namespace sees the viewerunit falls back to memory for the visit when the store refusesunit loses only the write the store refuseswebview copies results.txt on a plain-http host, where the async clipboard API is absent
●
Published comparison: crops kept per viewer in the browser and deletable; Delete greyed on listed originals
tested
webview keeps the viewer's crops across a reload and deletes them, never an originalwebview standalone published comparison does not pollunit is writable, and merges its own files into the published listingunit reads its own files without a fetch, and published ones through itunit prefers the layer when it holds a path the listing also hasunit refuses to write or delete a published file or directoryunit and a delete of nothing is not found
●
Published comparison: the manifest's host names the regime (claude-artifact offers no PPTX; an unknown host is reported)
tested
unit states the flags a published page offers per regimeunit holds the claude-artifact publish limits, decimal megabytesunit throws for a regime it does not have rather than defaultingunit reads the hosting regime, absent meaning staticunit reports a host the regime table does not know instead of openingunit claims neither a PPTX export nor a results copy when nothing is statedwebview standalone page reports a manifest naming an unknown host instead of opening itwebview a host that cannot hand over a file shows no PPTX button and is never sent an exportwebview a host that can shows the PPTX button, and no results.txt button unless it copies voteswebview an init stating no flags offers neither
●
Published comparison: two comparisons on one origin never share votes or crops
tested
unit to another sharing the storeunit drops the manifest filename, query and fragmentunit separates sibling and nested comparisons on one originwebview standalone published comparisons in sibling directories keep separate votes
●
Published comparison: strip thumbnails from packed JSON, full-image fallback when missing or malformed
tested
webview draws the strip from packed thumbnails without fetching the imageswebview falls back to the full images when the thumbs file is missingwebview falls back to the full images when the thumbs file is malformedwebview decodes an image the thumbs file has no entry forwebview loads the open row while the thumbs file is still downloadingunit parses entries keyed by listed pathunit drops a missing, malformed or unknown-format file and keeps the othersunit gives up on a file that never answers instead of stalling the stripunit decodes bad base64 to null, so the caller decodes the full imageunit reads the thumbs list, absent meaning noneunit reports a thumbs list that is empty, not a list, or leaves the page directory
●
Sharing script stages a comparison byte-for-byte or refuses (unservable types, size and count limits), never altering it
tested
unit lays a folder of image columns out as it is, ignoring loose files, non-images and results.txtunit makes each directory of a multi-directory session a column named by its labelunit refuses a file-list session whose files share a name, which a flat layout cannot holdunit stages the page, manifest, thumbs, plan and every image byte for byteunit packs one thumbnail per listed image: longest side 200, never enlarged, PNG for PNG and JPEG otherwiseunit hands sharp the image bytes, never its pathunit refuses types the host does not serve, naming every file, and stages nothingunit refuses an image over the per-image limit, naming it and its sizeunit refuses a non-empty --out together with every other violation, leaving --out as it wasunit removes what it staged when staging itself fails midwayunit counts page, manifest and thumbs files toward the per-version file limitunit batches within the per-publish caps, page, manifest and thumbs first, the page again in every later batchunit splits so every file is within the limit, losing no entryunit stages colliding-tail directories under their derived names, the slash made a lookalikeunit stages a label with a colon under its lookalike, and the staged manifest opens in the pageunit maps every unsafe character of a file name through the one tableunit refuses a label no lookalike can make safe, naming it, and stages nothingunit refuses two columns that would share a folder once made safeunit refuses two columns whose folders differ only in caseunit refuses two files whose staged paths differ only in case or Unicode normalizationunit names every staged path the pageunit refuses a file-list session the page would read back without its _cropNN filesunit refuses a folder whose only image is one loose fileunit refuses a multi-directory session with fewer than two image directoriesunit refuses before building any thumbnailunit refuses a label holding a control character, which no lookalike replacesunit refuses an input that does not exist, or is neither a folder nor a sessionunit refuses a folder with neither image subdirectories nor loose imagesunit refuses a session that does not parse, naming the sessionunit refuses a session listing no existing image or directoryunit refuses a file-list session of one image, which the page would not openunit refuses a file name no lookalike can make safe, never dropping the fileunit refuses an --out that is a file, and leaves it as it wasunit bundles sharePlan, shareNodeIo and hostRegimes for node without resolving vscodeunit counts the thumbs files it builds
●
Delete is greyed and Del ignored for a read-only image (published originals, read-only browser roots)
tested
webview a row holding a read-only file greys Delete and Del does nothing; a writable row deleteswebview with an image unit only the read-only image greys Deletewebview a structural change re-decides it: removing the read-only row frees Delete without navigatingwebview an arrival carries no flag: a viewer-made row is deletablewebview standalone no-FSA drop path walks webkit entries into the read-only backendunit flags readOnly exactly the real files the host says it cannot deleteunit leaves the wire without a readOnly key when the host states no predicateunit flags only published paths read-only
●
Standalone right-click opens the app menu
tested
webview standalone right-click opens the comparison menu
●
Standalone help promises only what it offers
tested
webview standalone help modal promises only what this host offers
●
A published page keeps order, hidden pills, position and zoom across a reload, per comparison
tested
webview standalone published comparison restores order, hidden pills, row, column, zoom and pan after a reloadwebview standalone published comparisons in sibling directories keep separate viewswebview standalone published comparison with blocked storage opens at defaults and shows no errorwebview a host stating restoresView is read once and written as the view changeswebview applies the saved view at the first init and not at a later re-init
●
A saved view restores by name, skipping what is gone
tested
unit view state (viewState.ts, real code)
●
The extension saves and restores no view
tested
webview with the extension's capabilities getState and setState are never calledTest tooling
●
Mutation harness mutates a sandbox copy, never the working tree (every handleable exit restores; SIGKILL leaves a stale temp dir; a seam-narrowed subset run is branded and cannot exit 0)
tested
unit mutation harness isolation
●
Webview suite sizes its workers from usable parallelism, not the reported core count, and keeps the HTML report outside the test output folder
tested
unit webview suite worker sizing