Add FF1 artwork render status reporting - #249
Conversation
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
Critical issues
-
src/services/CanvasService.ts:223/src/app/playlist/playlist-client.tsx:480:setRenderStatus()broadcasts a new fullcastInfo, so every render lifecycle update re-enters the playlist command switch. FordisplayPlaylist, this rebuilds playlist state and reappliescurrentIndex, which can clear and reschedule timers. Render status observation should not behave like a fresh playback command, or consumers need to ignore status-only cast-info updates. -
src/services/CanvasService.ts:532,src/services/CanvasService.ts:291,src/services/CanvasService.ts:741: index-changing paths keep the previous artwork’srenderStatus. A remote can move from a ready artwork to a new item and immediately getindex: <new>withrenderStatus: readyuntil React catches up. Reset topendingwhen the active artwork changes, including move/update/deferred refresh/shuffle promotion paths. -
src/components/artwork-player/ArtworkPlayer.tsx:387: the same-URL slot handoff guard was removed. Adjacent playlist items can sharepreviewURLbut differ byitemIdentity; a late load from the outgoing slot can now rebindincomingSlotRefand steal readiness from the actual incoming slot. Please restore the guard or key readiness by slot identity, not URL alone. -
src/components/artwork-player/ArtworkPlayer.tsx:400/src/components/artwork-player/ArtworkPlayer.tsx:798: render failures are accepted from any slot without checking that the slot still represents the currentpreviewURL/identity. During a transition, an outgoing slot error can mark the new artwork failed and show the modal; later success sets status ready but does not clear the modal. The failure path needs the same stale-slot guard as successful load handling.
Medium issues
src/services/CanvasService.ts:63: source protocol rejection is a new cast/DP1 command-contract change, and this concern recurred in two reports. The PR now rejects whole Now Display/refresh commands for anything outsidehttp:,https:, anddata:instead of accepting the playlist and reporting render failure. If this belongs here, document the accepted schemes and add compatibility coverage for production DP1/cast source shapes; otherwise split it out.
Missing coverage / handoff
-
Add coverage for render status across control paths:
moveToArtwork,updateIndex, queued shuffle/refresh promotion, and deferred refresh consumption. -
Add consumer-path coverage showing status-only updates do not cause
PlaylistClientto reprocess playback commands or reschedule timers. -
Add same-URL/identity transition tests and stale outgoing media-error tests, including video or HLS.
-
The PR validation lists Go commands for
components/feral-controld, but this PR changes TypeScript/React player code. Please provide the repo-requirednpm run post-implement-checkandnpm run verifyresults, or explain why they could not be run.
Skipped as not worth raising actively: the unused hook dependencies cleanup is minor. There were no PR discussion comments indicating any of the active issues were already deferred or tracked elsewhere.
|
Dependency impact check for PR #249. Summary: Required downstream updates:
Evidence:
|
|
Automated human review request for PR #249. Risk zone: yellow This PR needs targeted human review on the scoped areas below, not a full second pass of the whole PR.
|
|
The player should reject invalid item source URLs at the playlist/item validation layer. It only needs to validate the playlist and its items; if an item's |
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
Critical Issues
-
renderStatuscan be stale for the newly selected artwork during index-only transitions.moveToArtwork, playlistupdateIndex, and deferred playlist promotion updateindexthroughsetCastInfo()without resettingrenderStatus, whilesetCastInfo()preserves the previous status viacastInfo.renderStatus ?? this.renderStatus(src/services/CanvasService.ts:207-224,src/services/CanvasService.ts:299-311,src/services/CanvasService.ts:540-550,src/app/playlist/playlist-client.tsx:241-245). A status poll between that index change andArtworkPlayer’s latermarkArtworkPending()can report the new item with the prior item’sready/failedstate. This same stale-status concern recurred across multiple reports and is central to the new reporting contract. -
renderStatusis persisted with cast info and can be reported after recovery as if it describes the current page render.setRenderStatus()writes the status intocastInfoand notifies persistence (src/services/CanvasService.ts:231-241),useCastInfostores that object unchanged (src/services/custom-hooks/useCastInfo.ts:50-59), and recovery restores it intoCanvasServicebefore the artwork has mounted and reported a live lifecycle (src/context/AppContext.tsx:176-178,src/services/CanvasService.ts:454-462). Please striprenderStatusfrom persisted cast info or reset recovered render status to pending/undefined until the current artwork reports. -
The new source preflight rejects URL forms the existing player path can resolve, such as relative and protocol-relative sources.
isSupportedArtworkSource()usesnew URL(normalizedSource)without a base (src/services/CanvasService.ts:70-83), but media loading resolves withnew URL(url, window.location.href)and then assigns the original source to the element (src/utils/mediaLoader.ts:53-74,src/utils/mediaLoader.ts:184-195). This newly narrows the DP1/cast contract beyond the render-status change and can return{ ok: false }while leaving the previous artwork on screen. The discussion clarified that invalid item URLs should fail, and I am not re-raising theabout:blankdownstream compatibility item as an active blocker; this finding is specifically about browser-supported relative/protocol-relative sources.
Missing Tests / Validation
- Please add coverage for render status reset semantics across
moveToArtwork, timer/source-endupdateIndex, deferred playlist promotion, and persisted recovery containing storedready/failed. - The PR validation currently lists only Go commands for
components/feral-controld; this PR changes TypeScript/React playback, cast status, and remote config behavior, so the handoff should include the repo’s JS verification path such asnpm run verifyand any relevant manual smoke evidence for the playback-facing status flow.
…from persistence Co-authored-by: Cursor <cursoragent@cursor.com>
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 2, request-changes: 1).
Critical Issues
src/services/CanvasService.ts:103still preserves stalerenderStatuswhen a refresh replaces the current item'ssourcebut keeps the same itemid. Two reviewers raised this, and re-review confirms the path:refreshPlaylistspreadspriorinto the next cast info,resolveRenderStatusForCastInfocompares only selected item ids, and then returns the previousready/failedstatus. The playlist route will reload/render the changed source, so an immediate status poll can reportreadyfor media that has not rendered yet. The reset check should include render-affecting identity, at minimum the selected itemsource, and there should be CanvasService coverage for same id/index with a changed source.
Medium Issues
src/services/CanvasService.ts:103also compares raw indices while the playlist route normalizes accepted positive indices.moveToArtwork({ index: 2 })is accepted for a 2-item playlist, and the route displays index0, but the resolver readsnextItems.at(2)asundefinedand can preserve the previousready/failedstatus instead of resetting topending. Normalize the compared indices consistently withPlaylistClient, or reject positive out-of-range indices before committing cast state. Add CanvasService coverage for this accepted normalized-index case.
Missing Tests / Validation
- The PR validation only records Go commands under
components/feral-controld, but this PR changes TypeScript/React player status and artwork-rendering behavior. Please record relevant FF Player verification, at minimumnpm run verify, plus the targeted render-status tests that cover the changed status contract.
Considered But Not Re-Raised
- The discussion already called out the new rejection of unsupported artwork source protocols and the author clarified that invalid item sources should fail playlist validation. I’m not raising that as an active finding here, assuming the downstream
ff-appalignment noted in the thread is handled outside this PR.
|
Dependency impact check for PR #249. Summary: The player now rejects non- Required downstream updates:
Evidence:
|
Wire model-viewer load/error into render status and keep stale-slot guards. Co-authored-by: Cursor <cursoragent@cursor.com>
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 1, request-changes: 2).
Critical issues
-
src/services/CanvasService.ts:103-130resetsrenderStatusbased only on selected itemid. All reviewers raised this in some form. ArefreshPlaylistcan keep the same active item id while changing itssource, andrefreshPlaylistcommits the new playlist atsrc/services/CanvasService.ts:979-984. During that window,getStatus()can report the new source with the oldready/failedlifecycle untilArtworkPlayerlater marks it pending. Please treat render-relevant identity as at leastid + source, and add service coverage for same-id/different-source refresh. -
src/components/artwork-player/ArtworkPlayer.tsx:1163-1216can leave status stuck atfailedafter WebGL recovery.handleWebGLLost()publishes failed, recovery reloads the active iframe, and the normal ready path reachesmarkArtworkReady()vialoadedSource()/ slot-ready handling, butmarkArtworkReady()now returns early for any currentfailedstatus atsrc/components/artwork-player/ArtworkPlayer.tsx:239-247. That guard seems intended for model-viewer failure ordering, but it also suppresses successful WebGL recovery from publishingready. Please scope the suppression or explicitly publish pending/ready during recovery, with a regression test.
Missing tests / validation
-
Add tests for the two cases above: selected item same id but changed source, and WebGL loss followed by successful iframe recovery returning status to
ready. -
The PR validation currently lists only Go commands under
components/feral-controld. This PR changes React playback/status behavior inff-player, so the repo-requirednpm run post-implement-checkandnpm run verifyevidence is still missing.
Considered but not re-raised
- Reviewers flagged the new
http(s)/data:source restriction as a cast contract change. The PR discussion explicitly confirms invalid item sources should be rejected at playlist/item validation, and the dependency impact comments already call out requiredff-appnormalization work, so I’m not treating that as an active blocker here beyond ensuring the downstream follow-up remains visible.
Co-authored-by: Cursor <cursoragent@cursor.com>
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 2, request-changes: 1).
Critical issues
src/components/artwork-player/ArtworkPlayer.tsx:239prevents everyfailed -> readytransition. That protects the model-viewer error path, but it also breaks WebGL recovery:handleWebGLLost()publishesfailed,startWebGLRecovery()later callsreloadIframe(), and the iframeonLoadpath can recover visually whilemarkArtworkReady()returns early. This leavesgetStatus().renderStatusstuck atfailedafter a successful recovery, which defeats the new status contract. This should be scoped to the model-viewer failure ordering case instead of blocking all failed-to-ready recovery paths.
Medium issues
src/services/CanvasService.ts:103-130only compares raw previous/next item ids when deciding whether to resetrenderStatustopending. Two reviewers independently flagged this, and re-review confirms it is valid. ArefreshPlaylistcan replace the current item’ssourcewhile reusing the same DP1id; CanvasService then preserves the previousready/failedstatus even thoughsrc/app/playlist/playlist-client.tsx:446-450will render the new source. The same raw.at(index)comparison also misses accepted positive out-of-range indices frommoveToArtwork, while the playlist client normalizes those before display. Please compare the effective playback identity, including source where needed, using the same normalization rules as the player.
Missing tests
-
Add coverage for WebGL recovery after a failed status: WebGL loss publishes
failed, recovery reloads the current iframe, and the recovered artwork can publishpending/loadingand finallyready. -
Add CanvasService or PlaylistClient coverage for a refresh where the current item keeps the same
idbut changessource, asserting status resets topendingbefore the new artwork reports ready. Also cover positive out-of-rangemoveToArtworkif wrapped indices remain accepted behavior.
Notes considered
-
The repeated dependency-impact comments about rejecting non-
http(s)/data:sources appear intentional for this PR, and the author explicitly confirmed invalid item sources should fail playlist load. I am not raising that as an active ff-player finding here; the downstreamff-appnormalization work should be handled separately. -
The PR validation currently lists Go checks under
components/feral-controld, but this PR changes the TS/React player. Before merge, the handoff should include the repo-requirednpm run post-implement-checkandnpm run verifyevidence.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Review follow-up completed in this branch: Fixed here:
Out of scope:
Validation: |
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
Critical issues
-
[P1]
resolveRenderStatusForCastInfo()compares raw indices with.at().moveToArtworkaccepts out-of-range positive indices, whilePlaylistClientnormalizes them modulo playlist length. Moving from0to3in a two-item playlist renders item1, but this comparison sees no next item and preserves the oldready/failedstatus. Normalize both indices before comparing identities, and add coverage for this case. -
[P1] Relative and protocol-relative sources are explicitly accepted, but MIME detection still calls
new URL(previewURL)without a base. This throws before extension/MIME inference, causing valid relative image/video sources without an explicit MIME type to fall back to an iframe instead of their native renderer. This relative-source concern has already recurred in discussion; resolving the iframe display URL fixed only the later iframe path, not this MIME-detection path. Resolve before MIME detection and use the resolved URL consistently; add native image/video coverage for both relative forms.
Medium issues
-
[P2]
showRenderLoadingOverlay: falsedoes not suppress the model loading overlay.ArtworkPlayerhides its own spinner for models, butModelViewerScreenalways renders “Loading 3D model.” Thread the setting through or narrow the documented contract; add GLB/glTF coverage. -
[P2] A successful
refreshArtworkreply can be followed immediately by a stalereadyorfailedstatus poll. The service returns{ ok: true }after scheduling React state, whilependingis only published later by the reload effect. PublishRenderStatus.pendingsynchronously once the refresh handler accepts the command, with a command-level immediate-poll test. -
[P2]
docs/DEVICE_LOCAL_PLAYER.mdremoves the still-applicable setup-overlay background artwork contract. Restore that operational documentation alongside the new render-status/source-validation sections.
Missing validation
- The required player-route manual smoke remains incomplete for slow loads, failures, same-ID refreshes, and disabled loading overlays. This PR changes runtime playback/status behavior, so the pending smoke evidence should be completed before merge.
I did not re-raise the separately owned ffos-user typed status bridge or the deferred ff-app source-normalization work; both are explicitly documented as downstream follow-ups.
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 1, request-changes: 2).
Critical issues
CanvasService.setRenderStatus()republishescastInfowith the previouscastCommand. For adisplayPlaylistcast, each lifecycle update (pending,loading,ready, orfailed) is therefore reprocessed as a new display command byPlaylistClient, replacing the playlist state and re-arming its timer. Slow or failed renders can extend/reset finite artwork durations. Use a status-only notification path, or explicitly ignore render-status-only updates in the route. Add route-level coverage proving lifecycle changes do not replay display setup or reset timers.
Follow-ups considered
- The
ffos-usertypedrenderStatusbridge is explicitly documented as an out-of-scope downstream follow-up, so it is not raised again here. - Manual player-route smoke coverage remains pending as recorded in the PR body.
- The mixed-case
DATA:report is not valid:new URL()normalizes the scheme todata:before the protocol allowlist check. - The HLS non-fatal default-error behavior predates this PR; this change reports that existing terminal path rather than introducing the teardown policy.
|
Review follow-up for review 4804214898: Fix now — completed
Out of scope
Invalid / no change needed
Validation: |
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 2, request-changes: 1).
- Medium —
isSupportedArtworkSource()accepts everydata:-prefixed value without validating its payload, so malformed values such asdata:;base64,%%%%are acknowledged by now-display, schedule-play, and refresh and can commit unusable playback state. This conflicts with the documented contract that malformed sources return{ ok: false }before state changes. Validatedata:URL structure/payload and add command-level coverage for malformed data URLs. (src/services/CanvasService.ts:92)
The repeated manual player-route smoke gap was considered but is already explicitly recorded as a human-owned PR follow-up, so it is not re-raised as an active code finding. Likewise, the downstream ffos-user typed status bridge is explicitly deferred and remains out of scope for this player-side PR.
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
src/services/CanvasService.ts:93—isValidDataUrl()returnstruefor every non-base64data:payload, so malformed percent escapes such asdata:image/svg+xml,%ZZare accepted and can replace active playback or be scheduled. This contradicts the documented validation contract. Validate malformed escapes while retaining the intended raw-percent SVG compatibility, and add Now Display, Schedule Play, and refresh coverage.
The pending manual player-route smoke scenarios and the downstream ffos-user status bridge were explicitly recorded as deferred/out-of-scope follow-ups, so I am not raising them as active findings.
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
Critical issues
isValidDataUrlstill accepts incomplete/invalid non-base64 percent escapes such asdata:image/svg+xml,broken%,%2,%A, and%G-. The regex atCanvasService.ts:98only evaluates%followed by two alphanumeric characters, so these malformed inputs bypass the new validation and can replace active playback or be scheduled despite the documented{ ok: false }contract. Reject incomplete/invalid attempted escapes while retaining the intended raw-SVG literal-percent compatibility. This was independently identified by all three reviewers.
Missing tests
- Add regression coverage for incomplete and partially malformed percent escapes across Now Display, Schedule Play, and refresh, alongside the existing
%ZZcase.
The ffos-user typed renderStatus bridge is explicitly documented as a separately owned follow-up, so I am not raising it again here. Likewise, the pending manual player-route smoke is acknowledged in the PR body as operational follow-up rather than a separate code finding.
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 0, request-changes: 3).
-
Critical — iframe/PDF failures can be reported as
ready.ArtworkPlayerpromotes iframe and PDF content to ready solely onload. Browsers commonly fire that event for an error document after a failed navigation, while iframeerroris not a dependable remote-navigation failure signal. This makes the new status contract falsely reportreadyfor supported artwork that did not render as intended. Avoid treating iframeloadas verified success, or introduce a reliable failure/verification path; add browser-level coverage for this case. -
Manual playback smoke remains a merge gate. This was raised independently by reviewers 2 and 3 and remains explicitly unchecked in the PR: slow load, render failure, same-ID source refresh, and
showRenderLoadingOverlay: false. The repository workflow requires manual smoke evidence for this playback-facing lifecycle change. This recurrence should be resolved before merge.
I re-checked the reported hook-dependency issue: it is not active because ArtworkPlayer.tsx intentionally disables react-hooks/exhaustive-deps, and pauseSlotPlayback is a stable callback. I also considered the downstream ffos-user status bridge and upstream source normalization concerns; both are explicitly recorded as separately owned/out-of-scope follow-ups, so they are not raised again here.
feralfile-bot
left a comment
There was a problem hiding this comment.
Reviewers: 3 (approve: 0, comment: 1, request-changes: 2).
Critical issues
-
ArtworkPlayer.tsx:816lets a stale iframeloadmutate current state before confirming its slot/layer is current. In particular, an outgoing iframe load can invokehandleWebGLLost(), publishrenderStatus: failed, and start recovery for newer artwork. GuardhandleIframeLoadwithisCurrentArtworkSlot(slotIndex, layer)before any side effect. Add a stale-iframe-load regression test. -
Valid base64
data:image/...sources are accepted by the new cast contract but cannot reliably becomereadyon the normal playlist route. That route supplies no MIME type, andgetContentTypeFromURL()appends cache-busting text to the data payload before itsHEAD; this invalidates base64 payloads, falls back to an iframe, and iframe lifecycle intentionally remainsloading. Derive the MIME type locally from a data URL and select the renderer without aHEADrequest. Add playlist-route coverage assertingdata:image/...renders and reportsready.
Missing tests
- The pending manual player-route smoke coverage is already explicitly recorded in the PR, so it is not repeated as an active finding.
- The separately owned
ffos-usertyped status bridge is documented as a downstream follow-up and is likewise not raised again here.
Problem
Expose FF1 artwork render status through the player status response, keep it correct across artwork transitions and same-ID source refreshes, and define the artwork-source validation contract used by display, schedule, and refresh commands.
Why It Matters
Controllers need reliable pending/loading/ready/failed state without persisting stale lifecycle values. Device-local and remote DP1 playlists may legitimately use relative sources, while known non-web sources should be rejected before they replace playback state.
Acceptance Checks
{ ok: false }before committing playback or a schedule.npm run post-implement-checkandnpm run verifypass locally.showRenderLoadingOverlay: false(pending device/browser route execution).PlayerStatus.renderStatusbridge and consumer round-trip coverage before this becomes end-to-end visible in the app.Human Owner
PuPha / FF1 playback owner.
How The Agent Will Be Used
The agent reconciled main with the render-status branch, added source-contract documentation, ran lint/type-check/unit/build verification, and performed a separate full-diff review. Manual device-route smoke and the downstream ffos-user bridge remain human-owned coordination work.
PR or Deploy Link
#249