Skip to content

[CHIA-4324] NFT gallery: filter by preview availability - #3034

Open
jlobue10 wants to merge 6 commits into
Chia-Network:mainfrom
jlobue10:nft-6-preview-filter
Open

[CHIA-4324] NFT gallery: filter by preview availability#3034
jlobue10 wants to merge 6 commits into
Chia-Network:mainfrom
jlobue10:nft-6-preview-filter

Conversation

@jlobue10

@jlobue10 jlobue10 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

A gallery filter for NFTs whose tile shows a placeholder instead of media — the "Preview is not available" / "File does not match the expected hash" / "No file available" tiles. Independent of the open #3029 / #3033: this branch is based directly on main and shares no commits with them.

What this PR changes and why

Finding the broken NFTs in a large collection currently means scrolling past every healthy one. The gallery's filter bar gains a third pill next to Types and Visible / Hidden:

  • Preview available / Preview not available checkboxes, each with a count, following the same four-state model as the visibility pill (both checked = all, one checked = only those, none = nothing).
  • Filtering and the counts update live as verdicts arrive, so with only "Preview not available" checked the gallery converges on the broken NFTs as tiles settle.

Where the status comes from

The tile decides what it shows only after it has verified the file, and the gallery is virtualized, so most NFTs never mount. The per-NFT status therefore has two sources, kept in a small store inside NFTProvider (useNFTPreviewStatuses):

  1. Tiles report the verdict they settle on. NFTPreview derives it from the verification state every preview-mode tile shares — available when the selected source is verified and could be served from the cache; unavailable for no file, a settled hash mismatch, or a download failure (the same outcome the hash badge reports, whatever the tile draws: iframe, type icon, or compact row). It stays undecided while verification is in flight and, for a failed data file, while the metadata has not settled. Only preview-mode tiles report; the detail view verifies the full data file rather than the thumbnail and can legitimately disagree.
  2. Unmounted NFTs are classified from what the cache already persisted. Commit 1 adds a read-only cacheAPI.getCacheInfos(urls) IPC that returns each URL's persisted CacheInfo (CACHED + checksum, the persisted ERROR, or NOT_CACHED) without ever downloading. getNFTPreviewStatusFromCache mirrors the walk useNFTVerifyHash performs for a preview-mode tile — preview video → preview image → data file, using the renderer's metadata store, which the gallery already populates for every NFT: any source with a cached file whose checksum matches its hash → available; unavailable only once every URI of every source has a settled failure (persisted download error, or cached bytes with the wrong checksum); a URI the cache has never seen — or failed only transiently (Request aborted / Response aborted, which the cache retries) — leaves the NFT undecided, as does metadata that is still loading (a thumbnail may yet verify; the store re-sweeps when it arrives). Sweeps are coalesced to one per 250 ms and run in batches of 200 URLs; persisted outcomes are memoized per URL for the session, so repeated sweeps only ask the main process about URLs not seen before.

Design invariants:

  • Nothing is fetched to compute the filter. Undecided NFTs count as available — a tile would still attempt the download, and only a settled failure places an NFT among the unavailable ones. The pill's count is therefore the number of NFTs known to be broken; it grows as tiles settle.
  • A live report always wins over a cache lookup, and invalidating an NFT (refresh) clears its verdict so the re-fetched tile reports anew.
  • Cache keys, sidecar format, and the existing filters are untouched; the new pill defaults to "all", so nothing changes until the user opens it.

Testing

  • New getNFTPreviewStatusFromCache unit tests: no file / no hash → unavailable; any matching cached URI → available (with and without the 0x prefix); all URIs settled failures → unavailable; a never-fetched URI or a transient error → undecided; a verified thumbnail with a dead data file → available; a dead data file with metadata still loading → undecided, and unavailable only once the thumbnail has a settled failure too; the URL list the classifier consults (unhashed preview sources excluded).
  • New CacheManager.getCacheInfos test: reports CACHED / ERROR / NOT_CACHED per URL plus an ERROR entry for an un-keyable URL, and downloadFile is never called.
  • Full gui jest suite green (338 tests); eslint + prettier clean on all touched files; tsc adds no new errors in touched files beyond the pre-existing Chip size="extraSmall" pattern the neighbouring pills already have.
  • Bugbot rounds 1–2 (thumbnail-only NFTs misclassified from the cache; compact/icon tiles disagreeing with gallery cards; stale verdict during a refresh) addressed in the three follow-up commits; replies in-thread.
  • Manually tested in a running GUI (my test checkout, with the full series applied) against my own collection: the new pill appears next to Types and Visible/Hidden, unchecking "Preview available" narrows the gallery to the broken tiles, and the counts settle as the cache lookups and tiles report in.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q


Note

Medium Risk
Touches NFT gallery filtering, NFTProvider state, and a new cache IPC path. Logic is carefully sequenced around invalidation, but incorrect classification could hide or miscount NFTs.

Overview
Adds an NFT gallery filter for tiles whose media is actually showable vs placeholder-only (no file, download failure, or hash mismatch). The new pill uses the same four-state checkbox model as visibility, with live counts.

Preview verdicts come from two sources without extra downloads: mounted preview tiles report after verification, and off-screen NFTs are classified from persisted cache state via a new read-only getCacheInfos IPC. Undecided NFTs count as available until a settled failure; live tile reports win over cache lookups, and refresh clears the verdict.

Reviewed by Cursor Bugbot for commit 9212010. Bugbot is set up for automated code reviews on this repo. Configure here.

jlobue10 and others added 2 commits August 21, 2026 09:37
CacheManager records the outcome of every download in a `-info` sidecar
next to the cached file, but the renderer could only reach that state by
asking for the content itself, which starts a download for anything not
yet cached. A new read-only `getCacheInfos(urls)` IPC returns the
persisted CacheInfo for a batch of URLs — CACHED with its checksum, the
persisted ERROR, or NOT_CACHED for a URL never requested — without ever
fetching. A URL the cache cannot key at all is reported as an ERROR entry
for that URL instead of failing the whole batch.

This lets the renderer classify NFTs it has not rendered from what
earlier visits and sessions already learned about their files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q
NFTs whose media cannot be shown — a dead host, a file that no longer
matches its on-chain hash, or no file at all — render a placeholder tile
but could not be singled out, so finding the broken ones in a large
collection meant scrolling past every healthy one. The gallery's filter
bar gains a third pill next to the Types and Visible/Hidden ones, with
"Preview available" / "Preview not available" checkboxes and counts that
follow the same four-state model as the visibility pill.

The status behind the filter comes from two sources, both kept in a
per-NFT store inside NFTProvider:

- Tiles report what they actually settled on showing. The decision is
  derived from the same booleans NFTPreview renders from, so the filter
  classifies an NFT exactly as its tile does. Only preview-mode tiles
  report; the detail view verifies the full data file rather than the
  thumbnail and can legitimately disagree.
- The gallery is virtualized, so most NFTs never mount. Those are
  classified from the cache's persisted outcomes via the new
  `getCacheInfos` IPC, mirroring the URI walk `useNFTVerifyHash`
  performs: a cached file matching the hash makes the preview available,
  and it is unavailable only once every URI has a settled failure. A URI
  the cache has never seen (or failed only transiently) leaves the NFT
  undecided, and undecided NFTs count as available — a tile would still
  attempt the download. Lookups run in batches of 200 URLs, once per NFT
  per session; a live report always wins over a lookup.

Invalidating an NFT clears its verdict so the refreshed tile reports
anew. Filtering and the statistics counts re-run as verdicts arrive, so
with "Preview not available" selected the gallery converges on the
broken NFTs as tiles settle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q
@jlobue10
jlobue10 requested a review from a team as a code owner August 21, 2026 16:38
Comment thread packages/gui/src/util/getNFTPreviewStatusFromCache.ts Outdated
Comment thread packages/gui/src/components/nfts/NFTPreview.tsx
jlobue10 and others added 2 commits August 21, 2026 09:57
… alone

The cache lookup walked only the data URIs against the data hash, but a
preview-mode tile verifies preview video, preview image, and data file in
that order and shows the first verified one. An NFT whose thumbnail would
verify while every data URI is a settled failure was therefore stored as
unavailable, and under "Preview available" it stayed filtered out — never
mounting, so no tile could ever correct it.

The classifier now mirrors the tile: it takes the NFT's metadata state
and walks the same sources in the same order; any verified source makes
the preview available, and it is unavailable only once every URI of every
source has a settled failure. Metadata that is still loading leaves the
verdict undecided — a thumbnail may yet verify — so the store re-sweeps
when metadata arrives, via a new global change event on the metadata
store. The metadata store is already populated for every NFT by the
gallery's search and statistics, so consulting it adds no requests.

Sweeps are coalesced to one per 250ms and persisted outcomes are memoized
per URL for the session, so the repeated sweeps during initial load only
hit the main process for URLs not seen before. Invalidating an NFT forgets
its URLs along with its verdict.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q
…er path

Every preview-mode tile reported into the shared store, but the verdict
was derived from what the tile happened to draw: a compact tile for a
non-image type and a document or model tile show a type icon without
opening the iframe, so a file that failed to download still counted as
available there, while the gallery card for the same NFT hit
prepareError and reported it unavailable. Whichever tile mounted last
won, and an autocomplete row could flip the gallery's verdict.

The verdict now follows the verification state all of these tiles
share: the preview is available when the selected source is verified and
could be served from the cache, unavailable when there is no file, a
settled mismatch, or a download failure — the same outcome the hash
badge reports, whatever the tile draws. It stays undecided while
verification is in flight and, for a failed data file, while the
metadata has not settled, since a thumbnail may still verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q
@jlobue10
jlobue10 force-pushed the nft-6-preview-filter branch from 580b5b2 to 95d0f69 Compare August 21, 2026 16:58
Comment thread packages/gui/src/components/nfts/provider/NFTProvider.tsx Outdated
Moving the verdict reset into the refresh's finally block left the
gallery classifying a refreshing NFT from what its files used to be for
as long as the metadata fetch took — with a slow or hung metadata host,
the whole timeout.

The verdict is now dropped up front, together with the memoized outcomes
of the data files, and dropped again once the deletions have completed:
the preview uris are only known after the metadata round-trip, and a
cache lookup that overlaps the deletions could memoize outcomes the
refresh is about to remove. After the second reset the store holds
nothing about the NFT, so the next lookup sees the files as not cached
and only the refreshed tile's own verification decides.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 862ce0c. Configure here.

Comment thread packages/gui/src/components/nfts/provider/NFTProvider.tsx Outdated
Comment thread packages/gui/src/components/nfts/provider/NFTProvider.tsx Outdated
@jlobue10

Copy link
Copy Markdown
Contributor Author

Manual test done on my end: ran the branch (tip 862ce0c) in the GUI against my own collection. The new pill shows up next to Types and Visible / Hidden, unchecking "Preview available" narrows the gallery to the NFTs whose tiles show a placeholder, and the counts settle as the cache lookups and the tiles themselves report in. Working well.

Two gaps around the refresh reset:

- Waiting with Promise.all meant one rejected deletion (a uri the cache
  cannot key) threw before the late reset ran, and threw while the other
  deletions were still in flight. The refresh now waits for every
  deletion with allSettled, resets, and only then re-raises the first
  failure — so the reset can neither be skipped nor race a deletion.

- A cache lookup whose IPC round-trip started before the files were
  deleted could return after the late reset, memoize CACHED outcomes for
  files that no longer exist, and settle the NFT on them; later sweeps
  then skipped it. The store now counts invalidations, and a lookup that
  spans one discards its result and starts the sweep over, since the
  NFTs the invalidation reset are unsettled again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKEGodEgVdEuvUM8dza52q
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants