ImageCompare — Feature Coverage Dashboard

174/185 features tested · generated (run-time)
tested 174 failing 0 no test 11 stale 0

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 modality

Keyboard — 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 mode
webview 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 deleteTuple
webview an image-unit host gets deleteImage
webview a reordered strip still names the original column

Mouse / 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 step
webview 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 pixels
webview a line-mode wheel scrolls rows, not pixels

Tools window (floating panel)

● Crop button tested
webview shows the floating panel tools
webview C enters crop mode
● Delete button → deleteTuple tested
webview shows the floating panel tools
unit tupleDeleted -> refresh -> re-save in that order
unit re-saves after EVERY step
unit indices pre-shifted for earlier splices
unit deletes every file before executing the removal plan
unit files, tupleDeleted, refresh, save
● PPTX button → exportPptx (busy spinner, no-votes whole view) tested
webview shows the floating panel tools
webview PPTX export with no votes sends the whole view with null winners
webview PPTX button goes busy on click and ignores re-clicks
webview pptxError clears the busy state
unit emits one plain slide per modality
unit orders slides by modalityOrder
unit marks exactly the winner caption
unit a voted crop never ships without its parent
unit increments past the max existing number
unit speaks the canon sequence
unit 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 panel

Crop 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 dirs
unit clamps width and height to the image bounds
unit speaks the canon transcript
unit numbers past the max existing crop
unit injects the crop meta as a readable tEXt chunk
unit 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 from
unit separates two images whose stems collide across extensions
unit numbers against the image's own listing
unit names each crop after its own image in a file list
unit 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 them
unit 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 height
unit round-trips through scaleAndClampRect
● Screen↔image coordinate mapping tested
unit viewer center maps to image center
unit are inverses
unit carousel offset

Modality 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 tooltip
webview ] key reorder also keeps tooltips correct
● Watcher-inserted modality preserves user rearrangement + focus tested
unit a watcher-inserted modality preserves the user rearrangement
webview 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 up
webview a click, and a 3 px wobble, select the pill without reordering
webview a CDP pen tap on a pill selects it with exactly one click on the pill
webview a pen down and up with no click from the engine selects the pill with exactly one click
webview a lifted end pill, its lift scale included, stays within the row's ends at either end of a drag
webview under fractional pill widths the drop slot turns at the true midpoint and the pills slide by the true pitch
webview 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 -> 2
webview 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 pill
unit moving 0 -> 2 of four
webview 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 it
webview and a tap 50 ms after the drop selects
webview and a tap 50 ms after its fling selects
webview 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 passes
unit 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 nothing
webview a touchstart on a pill arrives default-prevented
● No ←/→ reorder buttons; [ ] still reorder tested
webview the reorder buttons are gone and [ ] still reorder

Winner 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-trips
unit maps winner indices to durable modality names
integration CRLF-encoded results
unit serializes the exact header and vote lines
unit round-trips through serialize and parse
unit empty winners delete the results file
unit winners serialize through the shared format

Carousel

● Thumbnail grid renders tested
webview renders the viewer and ingests fixtures
unit plans slots in scanline order
unit reports a slot whose modality the tuple lacks as missing
unit zero-item terminal case
unit missing slots error out first
unit zero items still posts the terminal progress tick
webview corrupt thumbnail payload falls back to the error placeholder
webview restores the tile and re-arms the retry
webview pool rebinds after a decode failure keep the placeholder
webview a recycled empty tile paints exactly like a never-filled one
webview every visible tile carries a decodable src
unit the blank tile src is a decodable 1x1 fully transparent png
webview 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 posted
unit an on-demand re-request posts the same tight binary shape
unit revokes the superseded url and never the incoming one
unit adopts the successor before revoking the loser
unit stores the placeholder data url but never revokes it
unit delete and clear revoke exactly what they drop
unit re-keys a row removal
webview a thumbnail payload paints from an object url
webview a superseded delivery releases exactly one url
webview pooled-row rebinds repaint from the cache without revoking a live url
webview the error placeholder releases the slot url and a re-init releases all of them
webview 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 viewport
webview an arrow key moves the carousel horizontally
● Alt accelerates carousel scrolling tested
unit multiplies a linear scroll
webview 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 count
webview doubling the modalities does not grow the DOM
● Materialized columns follow the horizontal scroll tested
unit advances by one column per pitch scrolled
webview scrolling the strip materializes later columns
● Strip tiles are centre-cropped tested
webview a fine primary pointer keeps the desktop layout
webview a coarse primary pointer gets touch-ui

Touch layout

● Touch layout only under a coarse primary pointer tested
webview a fine primary pointer keeps the desktop layout
webview 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 swipes
webview 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 it
webview a finger drag scrolls its rows and taps nothing
webview a single-tuple comparison (no ▦) has nothing before the pill row either
webview 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 circle
webview a finger on the image never reaches the mouse handlers
webview 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 rows

Viewer / status bar

● Viewer activates on init tested
webview renders the viewer and ingests fixtures
unit builds the init message with dense tuples
unit densifies tuples
unit 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 skipped
unit 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 imageError
unit a passthrough extension serves the original bytes
unit 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 it
webview help modal fits entirely within a small viewport
webview 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 clipboard
webview copy after navigation writes the currently displayed image
● Version shown in the help modal tested
webview help modal shows the ImageCompare version carried by init
webview 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 serve
webview a host that cannot reveal is never offered the item
● Local items never reach the wire tested
unit keeps the two local items reachable
webview a host action the webview cannot perform goes to the host
● Menu dismissal (Escape, click away) tested
webview Escape dismisses the menu
webview a click elsewhere dismisses the menu
webview keys do not act behind an open menu
webview 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 offers
webview the help modal names only the items this host offers
● Action notices, worded by the webview tested
unit keeps the wording the vscode notifications used
webview 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 reveal
webview 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 failure
webview 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 pill
webview 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 row
webview 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 clipped
webview a hovered, current end pill 200 px wide paints inside the row at either scroll end
webview 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 it

Backend — 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 is
unit excludes this app own _cropNN writes
unit 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 column
unit prefers the structure: one imageful subdir wins
unit 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 symlinks
integration 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 once
unit overlaps the subdirectory listings of a mode-1 root
unit caps the fan-out at exactly 16 and lists in waves
unit keeps the caller order when the directories resolve slowest-first
unit keeps the caller order across concurrency waves
unit propagates the earliest failing directory
unit sorts each directory naturally, whatever order the listing returns
unit survives a scrambled completion order over 12 directories
unit 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 slot
unit a taken slot means a new tuple
unit post-crop sequence
unit 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 directory
unit the cycle cost does not grow with the number of files
unit a real deletion is still detected
unit a modality directory that cannot be listed still reports its files gone
unit a tracked file that became a dangling symlink is reported gone
unit a tracked file under no listed directory keeps its own existence check
unit costs one pooled task per watched dir, not one per file
unit 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 frame
webview the last frame does not survive the last row
webview every modality removed leaves the same notice
webview a comparison whose folder is gone names the folder instead
webview a folder that comes back clears the notice and shows its images
unit says nothing while there is anything to draw
unit speaks at zero rows, whatever the columns say
unit speaks at zero columns, though the emptied rows survive
unit names the folder when the host established the root is gone
unit distinguishes an emptied comparison from a folder that went away
unit falls silent again as soon as content returns
unit the comparison root going away is reported by name
unit the root coming back clears the notice
unit a root that merely cannot be listed reports nothing
unit then re-adopts the column and rows when it comes back
unit re-arms a watcher for every re-adopted directory
unit 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 paths
unit Windows drive-letter paths disambiguate
unit a Windows watched dir reaches node as a drive-rooted path
unit a POSIX watched dir is handed through byte for byte
unit a delete it reports carries the tracked URI
unit still lands as a delete, though the probe calls it present

Backend — formats & metadata

● PNG crop metadata round-trip tested
unit round-trips crop metadata
unit injects and reads back a value
● Thumbnail packfile cache (warm open; survives a close) tested
unit round-trip — every key comes back byte-identical
unit uuid mismatch between idx and pack is rejected
unit dispose() publishes the pending pack instead of dropping it
unit flush() resolves only once the pack and idx are on disk
unit flush() also waits for the per-entry .jpg writes it started
unit the shared bed teardown does not return while a cache write is still in flight
unit never publishes an empty pack over a good one
unit 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 pixels
unit a fresh service re-reads the overwritten file
unit 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 sweep
unit a week-old pack nobody opened still expires
unit a pack deleted under a live session is republished by the close
unit a symlinked cache entry expires by age
integration 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 unavailable
unit 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 blocked
unit restores the process-global resolver after a successful WASM retry
unit returns null instead of throwing when the WASM tier fails too
unit does not retry an error that is not the CPU/loader signature

Session 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 declares
webview the help row and the button hint follow the declared unit
webview a tuple-unit host keeps the row wording
webview 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 parallelism
unit an explicit width override wins
unit 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 prefix
unit turning debug on at runtime starts logging without re-activation
unit disposes the output channel when the extension subscription is disposed
unit an idle window stops repeating itself
unit a pool with work in it is reported every cycle
unit 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 marks
unit time between marked spans is reported as `other`, never absorbed into a neighbour
unit sendInitData closes the trace and logs exactly one [IC-OPEN] line, ahead of the sweep
unit reports the serialized init payload size, measured on the payload that was posted
unit counts the image files handed to the matcher, and times the matcher inside the scan
unit the html mark sits at the html assignment
unit the ready mark sits at the top of the ready handler
unit 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 channel
unit the init payload is serialized for its size only inside the trace gate
unit debug off: the scan carries no stats at all
unit debug off: serving thumbnails updates no counter at all
unit turning debug off at runtime stops the counters mid-session
unit no debugLog call in fileService.ts runs without a debugEnabled() guard
unit 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 bytes
unit a wave whose FIRST slot has no image still logs both lines
unit a wave with a hole in the middle still logs both lines
unit 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 push
unit a user-facing image posted mid-wave is not queued behind speculative bytes
unit 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 wire
unit every thumbnail has crossed the wire in under 1.5 virtual seconds
unit withholds every speculative push while a bulk sweep drains
unit 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 budget
unit bounds speculation in bytes, not in messages
unit lets one over-budget image go alone
unit is unlimited on a local session, whatever the setting says
unit 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 prologue
unit releases the wire when the sweep stops making progress
unit does not cut short a sweep that is still settling slots
unit 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 both
unit a removed tuple drops its own posts and shifts the ones behind it down
unit 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 seen
unit drops a payload parked inside the rename window when the delete commits
unit drops the stale payload when the file comes back under its own name
unit drops the stale payload when a rename lands on the slot
unit drops the stale payload when the file is rewritten in place
unit drops the stale payload a forceReload retry is asking past
unit never lets a held burst payload for a deleted slot reach the wire
unit 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 panel
unit 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 memory
unit a fresh service reading the published pack counts as pack
unit a per-entry JPEG with no pack counts as disk
unit six concurrent pack hits report their own work
unit the one-off pack read is reported once
unit the tiers BELOW the pack inherit the same shared wait
unit the verbose per-thumbnail line separates the item ms from the wait
unit the sweep rollup prints the shared read once
● results.txt CRLF parsing (cross-platform) tested
integration CRLF-encoded results
unit 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 fallback
unit gives each native target npm flags that match its platform
unit passes the matrix libc through to npm
unit prunes the natives npm restores, keeping only the target pair
unit registers a packed-VSIX binary expectation for every matrix target
unit keeps the scan body free of the quote that would truncate it
unit accepts every pruned leg, including the two win32 shapes and universal
unit rejects the four-tier linux-arm64 shape that 0.4.0 shipped
unit rejects the pre-prune shape on every leg that can carry one, universal included
unit runs the whole step under a real shell: exit 0 for a conforming VSIX, exit 1 for a stray
unit 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 about
unit builds every platform target and universal on a pull_request
unit keeps the ten-leg recipe in exactly one workflow
unit cannot reach either marketplace, or the post-publish check
unit skips its own test and test-full jobs on a pull_request
unit still runs the whole chain on a tag when both gates pass
unit refuses to build on a tag when either gate failed or was cancelled
unit cancels a superseded pull-request run and never a tag run
unit models the Actions semantics these cases lean on

Loading policy

● Tuple arrival requests only the on-screen modality tested
webview a navigation requests only the modality on screen
unit 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 side
unit never widens past three columns, however wide the tuple
unit never speculates on a hidden column
unit issues the visible column and its nearest siblings, not every modality of every neighbour
unit never speculates on a neighbour column the user is nowhere near
webview 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 column
unit is three columns per tuple at the field shape, not ten
unit 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 order
unit orders nearest-first, forward before backward at equal distance
unit measures distance over the user-rearranged display order
● Hidden modalities are never speculated on tested
webview hidden modalities are never speculated on
unit 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 VISIBLE
unit 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 for
unit 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 tuples
unit 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 tie
unit fills the focused row and column interleaved, then the rest row-major centre-out
unit stops the column arm at the radius and finishes row-major
unit a radius past the ends of the grid is simply the whole column
unit a radius of zero is still one row of cross
unit stops the cross at the screenful the webview reported
unit alternates the two arms one slot each
unit an arm that runs out lets the other continue
unit skips empty cells without spending an arm turn on them
unit ranks columns by the strip as shown
unit hidden columns are swept last, never dropped
unit the focused column is swept first even when the user has hidden it
unit aims at the column the webview reports
unit drops nothing while the host reports the same tile from a fresh object
unit a centre moved mid-walk re-aims the remainder
unit the current row and its neighbours are dispatched first
unit starts at the row the panel opened on and walks outward
unit re-aims mid-sweep when setCurrentTuple moves the panel
unit serves the new centre after one running batch
unit serves the row the user jumped to after one running batch
unit drops only the sweep's own queue
unit dispatches its first chunk as one centre-out band, reading the centre once
unit finishes a grid under a centre that moves on every read, instead of livelocking
unit re-aims the sweep exactly once, after the burst ends
unit still cancels the tuple being left on every keystroke
unit a hidden panel dwells without re-aiming
unit leaves no dwell pending when the panel is disposed mid-burst
unit holds the dwell open for a whole burst and re-aims once
unit waits the navigation debounce, not a dwell of its own invention
unit opens the sweep at the tuple its host reports
unit drives the same burst through the real provider and the real standalone adapter
webview clicking a tile in another column reports that column at once
webview the reported column is the display position within the reported order
webview an arrow-key column move reports its column once the burst settles
webview a held arrow key reports once, at the column the burst ended on
webview a digit jump reports the column it landed on
webview a bracket reorder reports the strip it rearranged
webview a click inside the dwell window cancels the pending key report
webview a pill click reports its column at once, with no dwell
unit holds a held key open for the whole burst and reports once
unit reports exactly once for a settled keypress
unit reports once per settled keypress
unit reports a picked column at once, with no dwell of any kind
unit lets a pick cancel a burst still waiting
unit 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 jumps
unit a returned slot is handed out again exactly once
unit every slot is delivered exactly once when the centre moves on every batch
unit settles a disposed panel's cancellations instead of returning them
unit stops reading the grid the moment the panel is disposed
unit reads nothing new after the close, where it used to read the rest of the grid
unit a host that does not cancel its own queue still stops within one chunk
unit resolves without dispatching anything when the host was already gone at sweep start
unit abandons what the cursor still holds, where a re-aim returns what it dropped
unit every planned slot is delivered exactly once, and the tail is swept
unit rows with no planned items are skipped
unit a centre past the ends is clamped to the grid
unit hands out every planned slot exactly once across 350 seeded schedules
unit hands out every planned slot exactly once across 300 seeded schedules
unit hands back a slot the walk has passed on the column axis
unit returns a slot at the radius boundary the remainder has already passed
unit a non-finite radius reads as no report
unit a slot returned after the aim moved is re-placed by the NEW aim
unit drops the queue when only the COLUMN moves
unit 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 settle
unit the default dispatch bound is SWEEP_CHUNK
unit 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 whole
unit keeps plain FIFO within a group
unit a group that joins late enters the rotation
unit cancelling one group leaves the other group's order and the rotation intact
unit serves the second tab within one batch instead of after the first tab's whole queued chunk
unit splits the bulk slots roughly evenly once both tabs are sweeping
unit 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 batch
unit delivers every owed slot exactly once when the panel comes back
unit a sweep paused before its first dispatch waits for the resume
unit a dispose while paused still ends the sweep
unit reads nothing more while hidden and finishes every slot exactly once when shown again
unit gives the wire claim back when a hidden panel is disposed
unit the sweep's chunk is the whole cost of hiding

Standalone

● Standalone HTML builds, boots, and matches via the real scanForImages tested
webview standalone build boots the real webview and matches via the real scanForImages
webview standalone landing shows the ported dropzone chrome
unit standalone artifact freshness
● Browser shims provide every member the bundled shared modules call tested
unit provides every Buffer static the shared modules call
unit 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 diffSnapshots
unit pollPlan pairRenames
unit commitSlotRemoval
webview standalone poll detects an external new file as a granular tupleAdded
webview standalone poll delivers external deletes, content changes and results.txt edits
webview standalone re-open cancels the previous root
webview standalone poll re-verifies a removal with stat before reporting it
webview 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 newModalityDirCandidates
unit adoptionPlan adoptableImages
unit adoptionPlan applyModalityInsert
webview standalone poll adopts a modality directory copied in after boot
webview standalone poll executes a modality dir rename as remove-then-adopt
webview standalone poll orders a same-cycle dir rename remove-then-adopt within one cycle
webview 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 planSweepDirs
unit 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 backend
webview 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-only
webview copies a published image's URL as its path
webview standalone page with no imagecompare.json beside it keeps the landing
webview standalone page reports a manifest that reaches outside its directory instead of opening it
unit returns the listed relative paths in manifest order
unit rejects a manifest with no files
unit rejects paths that leave the page directory
unit reports a page that is not JSON as a parse failure, not a comparison
unit lists the manifest as a folder whose subdirectories are the columns
unit reads each file through the injected fetch by its manifest-relative path, lazily, not at listing time
unit 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.txt
webview the results.txt button shows with copyResults and voting, and posts copyResults
webview the results.txt button stays hidden where voting is off
unit words the results.txt copy outcomes
unit survives a reload: a fresh layer over the same store and namespace sees the viewer
unit falls back to memory for the visit when the store refuses
unit loses only the write the store refuses
webview 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 original
webview standalone published comparison does not poll
unit is writable, and merges its own files into the published listing
unit reads its own files without a fetch, and published ones through it
unit prefers the layer when it holds a path the listing also has
unit refuses to write or delete a published file or directory
unit 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 regime
unit holds the claude-artifact publish limits, decimal megabytes
unit throws for a regime it does not have rather than defaulting
unit reads the hosting regime, absent meaning static
unit reports a host the regime table does not know instead of opening
unit claims neither a PPTX export nor a results copy when nothing is stated
webview standalone page reports a manifest naming an unknown host instead of opening it
webview a host that cannot hand over a file shows no PPTX button and is never sent an export
webview a host that can shows the PPTX button, and no results.txt button unless it copies votes
webview 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 store
unit drops the manifest filename, query and fragment
unit separates sibling and nested comparisons on one origin
webview 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 images
webview falls back to the full images when the thumbs file is missing
webview falls back to the full images when the thumbs file is malformed
webview decodes an image the thumbs file has no entry for
webview loads the open row while the thumbs file is still downloading
unit parses entries keyed by listed path
unit drops a missing, malformed or unknown-format file and keeps the others
unit gives up on a file that never answers instead of stalling the strip
unit decodes bad base64 to null, so the caller decodes the full image
unit reads the thumbs list, absent meaning none
unit 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.txt
unit makes each directory of a multi-directory session a column named by its label
unit refuses a file-list session whose files share a name, which a flat layout cannot hold
unit stages the page, manifest, thumbs, plan and every image byte for byte
unit packs one thumbnail per listed image: longest side 200, never enlarged, PNG for PNG and JPEG otherwise
unit hands sharp the image bytes, never its path
unit refuses types the host does not serve, naming every file, and stages nothing
unit refuses an image over the per-image limit, naming it and its size
unit refuses a non-empty --out together with every other violation, leaving --out as it was
unit removes what it staged when staging itself fails midway
unit counts page, manifest and thumbs files toward the per-version file limit
unit batches within the per-publish caps, page, manifest and thumbs first, the page again in every later batch
unit splits so every file is within the limit, losing no entry
unit stages colliding-tail directories under their derived names, the slash made a lookalike
unit stages a label with a colon under its lookalike, and the staged manifest opens in the page
unit maps every unsafe character of a file name through the one table
unit refuses a label no lookalike can make safe, naming it, and stages nothing
unit refuses two columns that would share a folder once made safe
unit refuses two columns whose folders differ only in case
unit refuses two files whose staged paths differ only in case or Unicode normalization
unit names every staged path the page
unit refuses a file-list session the page would read back without its _cropNN files
unit refuses a folder whose only image is one loose file
unit refuses a multi-directory session with fewer than two image directories
unit refuses before building any thumbnail
unit refuses a label holding a control character, which no lookalike replaces
unit refuses an input that does not exist, or is neither a folder nor a session
unit refuses a folder with neither image subdirectories nor loose images
unit refuses a session that does not parse, naming the session
unit refuses a session listing no existing image or directory
unit refuses a file-list session of one image, which the page would not open
unit refuses a file name no lookalike can make safe, never dropping the file
unit refuses an --out that is a file, and leaves it as it was
unit bundles sharePlan, shareNodeIo and hostRegimes for node without resolving vscode
unit 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 deletes
webview with an image unit only the read-only image greys Delete
webview a structural change re-decides it: removing the read-only row frees Delete without navigating
webview an arrival carries no flag: a viewer-made row is deletable
webview standalone no-FSA drop path walks webkit entries into the read-only backend
unit flags readOnly exactly the real files the host says it cannot delete
unit leaves the wire without a readOnly key when the host states no predicate
unit 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 reload
webview standalone published comparisons in sibling directories keep separate views
webview standalone published comparison with blocked storage opens at defaults and shows no error
webview a host stating restoresView is read once and written as the view changes
webview 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 called

Test 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