5 Commits
Author SHA1 Message Date
eehkayandClaude Fable 5 16efba4e19 fix: audio asset playback and orphan-cleanup thumbnail deletion
The audio proxy resolved ownership only through voice comments, so
R2_AUDIO video assets (which store the same proxy path in
videoAsset.sourceUrl) always got 403s. Resolve ownership the way the
image proxy does: query comments and video assets, merge into a
unique-owning-video map, deny on ambiguity.

r2-orphan-cleanup marked videoAsset.sourceUrl as referenced but not
videoAsset.thumbnailUrl, so every R2_VIDEO asset thumbnail older than
the TTL was deleted as an orphan. Widen the query to both columns.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 13:17:13 -07:00
eehkayandClaude Fable 5 d830c9a386 fix: play r2 direct uploads in the compare versions view
The compare page predates the r2 upload provider: r2 versions fell
through to a URL-safety check that throws on app-relative upload URLs,
so their panels rendered nothing and registered no player — the shared
controls drove an empty list and nothing played.

- add an R2Panel mapping a plain video element over the app upload
  route to the shared adapter, using the same playback-url resolution
  as the main video page
- make play/pause state detection work without the YouTube API loaded
  (numeric fallback), so bunny/r2-only comparisons can pause
- re-sync panels that drift more than 350ms from the source player
  once per second so playback stays aligned, not just starts aligned

Verified against the running app: both versions play in lockstep
(0.000s measured drift), pause together, and seek together.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 10:18:22 -07:00
eehkayandClaude Fable 5 00589f3453 fix: bigint serialization 500s in video and approval responses
Two endpoints return 500 whenever they succeed, because their success
payloads include VideoVersion rows whose sizeBytes column is a BigInt
that JSON.stringify rejects:

- PATCH /api/projects/[projectId]/videos/[videoId] included all versions;
  respond with scalar video fields only, which is all any caller reads
- POST /api/approvals/[requestId]/decision included the full version row
  in the resolved request; select the scalar fields the response and
  notifications actually use

The approval bug is reachable the first time any approver responds to a
request; the decision itself commits, but the requester sees an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 09:17:57 -07:00
eehkayandClaude Fable 5 9595698b5b fix: emit one json-ld script per schema object
A single ld+json script holding a top-level array crashes naive
structured-data consumers (Safari extension content scripts) that read
parsed['@context'] without checking for arrays. Emit one script per
object so every payload has a top-level @context, and escape < in the
root layout like the marketing pages already did.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 09:15:57 -07:00
eehkayandClaude Fable 5 d405fc09da chore: rename commitlint config to mjs so esm rules actually load
commitlint.config.js uses export default but the package is CommonJS,
so the loader silently fell back to an empty ruleset and rejected every
commit with empty-rules. The .mjs extension loads under node and bun.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 09:15:04 -07:00