- home/detail video pollers no longer clearInterval on a single transient error
(give up only after 10 consecutive failures), matching the agent page
- agent page creates preview objectURLs inside useEffect so each has a matching
revoke under strict-mode double-invocation
- login pointermove throttled via rAF and skipped on coarse pointers
- source-analysis.html: changelog entry for this hardening pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- VideoNode upload now goes through backend (/jobs/upload via uploadCanvasVideo)
for a stable reloadable URL instead of a session-only blob: that leaked and
broke on reload; cleanNodeForStorage also strips blob: URLs
- useCachedMediaUrl: record real blob.size (chunked videos reported 0, making the
LRU byte cap a no-op); guard the catch path with the race token
- useApi: send credentials when reading reference images; drop the node-level
video poll that duplicated the Canvas-level syncPendingVideoNodes loop
- request.js: 60s timeout (was ~8.3h) + withCredentials
- remove dead getVideoTaskStatus/pollVideoTask that ignored taskId
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- run(): add timeout (download 600s via DOWNLOAD_TIMEOUT_SECONDS, else 300s);
TimeoutExpired now kills the child and fails the job instead of hanging forever
- create_job: validate_source_url() rejects file://, private/loopback/link-local
IPs and off-allowlist hosts (SOURCE_URL_ALLOWED_HOSTS) — closes SSRF/local-read
- per-job RLock guards save_state/update/update_generated_video and the retry
check-and-set so concurrent video workers can't clobber state.json
- db: psycopg_pool connection pool (graceful fallback if unavailable); write
failures surfaced via logging.error instead of silent print
- read-only media GET routes use job_path() (no mkdir) to stop empty-dir spam
- wrap remaining Image.open() in with-blocks to avoid fd leaks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>