Second review pass over #1910: races, duplication, dead code - #2007
Merged
johan-bell merged 2 commits intoSep 4, 2026
Conversation
Races: a slow encode start could write another document's hlsUrl and key onto the one now open, and leaked its EventSource; the Encode button went "checking" on every poll tick and window focus; the poller could re-arm after unmount; encrypted video loaded twice in the app; the CMS preview could take a stale key after a document switch. Duplication: one unmaskKeyHex, one mediaUrl resolver and one fetchHlsKey in shared; one effective-bucket rule; one loadBucket and one MASTER in documentProcessing, using isInOurStorage. Dead code: the app's video.js/videojs-*/m3u8-parser/iso-639-2 dependencies, their shims and stale mocks; LDialog's unused props; a stale coverage snapshot. Also: no-store on the credential endpoint, a relative enums import, two `any`s typed, and the flagged comment paragraphs cut to a why.
Only a change request stamps parentMedia, so after the upgrade the app read neither parentMedia.hlsUrl nor video and every migrated post showed no video until its parent was next saved; the idempotency guards meant a re-run could not repair it.
johan-bell
requested review from
MrDirkelz
and removed request for
MrDirkelz
September 4, 2026 11:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second review pass over #1910 (duplication, comments, races, dead code, best practice). Every change here is decision-free; the findings that need a call are listed at the end and are not in this PR.
Race conditions and lifecycle
EditContentis reused acrosspost/edit/:idroutes, so if the encoder's trust prompt heldstart()open while the editor moved from post A to post B, the lateencodingevent wrote A'shlsUrl/hlsKeyonto B and leaked the EventSource.useMediaEncodernow carries adisposedflag checked after everyawait,onMediaReadynames the document the result is for,EditContentMediarefuses a result for any other document, and the parent-id watcherstop()s the previous stream first. (cms/src/composables/useMediaEncoder.ts,EditContentMedia.vue)refreshAvailabilityflipped to"checking"on every 10 s poll tick and every window focus — i.e. exactly when the editor switched back from the encoder and clicked. Only the first check shows as"checking"now.await; guarded by the same flag.sourcewas truthy beforekeyHexarrived, soLuminaryPlayerloaded, failed on the key, and reloaded.VideoPlayer.vuenow withholds the source until the key question is answered (keyResolved). Unencrypted content is unaffected — the flag is set synchronously when there is no key to fetch.VideoPreviewsurvives a document switch; a slow sidecar fetch for A could become B's key. Sequence-guarded.Duplication
unmaskKeyHexexisted three times. The CMS copy is gone; the CMS importsluminary-shared's.api/src/util/maskKey.ts's comment named the CMS copy as its counterpart — it now names shared.isBucketRelative/toAbsoluteMediaUrllived in bothcms/src/util/mediaUrl.tsandapp/src/util/videoSource.ts. Moved toshared/src/util/mediaUrl.ts; both consumers use it.VideoPlayer.vueandVideoPreview.vue. NowfetchHlsKey(parentId, { cms })in shared, which also gives the previously unusedHlsEncryptionKeyDatatype its job. Specs mockfetchHlsKeyinstead ofgetRest; the mask/unmask vector moves toshared/src/util/hlsKey.spec.ts.EditContentMedia,MediaBucketSelect). NowstorageSelection().effectiveMediaBucketId().deleteMediaCollectionandmigrateMediaCollectioneach hand-rolled the "is this URL external?" test thatisInOurStoragealready implements,deleteMediaCollectioninlined its ownloadBucket, and the two files disagreed onMASTER("/master.m3u8"vs"master.m3u8"). OneloadBucket, oneMASTER, one predicate.Dead code
video.js,videojs-mobile-ui,videojs-youtube,@types/videojs-mobile-ui,m3u8-parser,@types/m3u8-parser,iso-639-2— no imports inapp/srcsince the player moved to the encoder'splayer-web-legacy. Their two.d.tsshims and two stalevi.mock("video.js")calls go with them, as doesapp/test-coverage.md(a coverage snapshot of files that no longer exist).LDialog.vue'swide/preventBackdropClosepass-throughs (nothing uses them;VideoPreviewtalks toLModaldirectly).Best practice
GET /storage/encoderconfigreturns live S3 write credentials withoutCache-Control: no-store; the far less sensitive sidecar endpoint had it. Added.MediaDto.tsimportedfrom "src/enums"where every sibling uses../enums.(b: any)inprocessPostTagDto→StorageDto;children as any[]in v21 →ContentDto[].Comments
Deleted the orphaned JSDoc in
useMediaEncoder.ts(a stale block stacked on top of the live one), the// Process medialabel, theconfiguration.tsheader that reproduced a type's shape, and the "crypto object" comment inEditContentVideo.spec.ts(it's a sidecar). Cut the multi-paragraph//blocks inprocessPostTagDto,migrateMediaCollection,sidecar.controller,useEditContentSource,VideoPlayer.vueandvideoSource.tsto one or two lines of why, perCLAUDE.md.Separate commit: v21 stamps
parentMediav21copiedchild.videoontoparent.media.hlsUrlbut never setchild.parentMedia— only a change request does that (processPostTagDto.ts). The app readsparentMedia?.hlsUrl || video, so after the upgrade every migrated video disappeared until someone re-saved the parent in the CMS, and the idempotency guards mean a re-run cannot repair it. The migration now stampsparentMedia/parentMediaBucketIdon all of a migrated parent's children (including translations that never had avideoof their own). It is its own commit so it can be dropped if you'd rather handle it elsewhere.Verified
shared:vue-tscclean;vitest src/util— 697 pass, 1 fail inresponseCache.spec.ts(pre-existing: fails identically on the untouched epic tip990d8bc3).api:tsc --noEmitclean; Jest fordocumentProcessing/*,v21,maskKey,encoderConfig,sidecar— 210 pass.cms:vue-tsc --buildclean; fullvitestsuite green.app:vue-tsc --buildclean; fullvitestsuite green;package-lock.jsonrefreshed withnpm install.Not in this PR — needs a decision
MediaDtodrift (blocker). Verified empirically again by a third reviewer: a legacy audio-only post fails validation (hlsUrl must be a string), and a post with both video andfileCollectionshas the audio silently stripped on its next save (whitelist), then re-stamped onto every translation'sparentMedia. shared sayshlsUrl?+fileCollections; the API sayshlsUrlrequired and nofileCollections. Resolve one way or the other before 1878 api cms hls media data model #1910 merges.processMediaDtooverwrites the key sidecar during processing; the parent is upserted later inprocessChangeRequest. A throw in between leaves the stored document pointing at the old collection with the sidecar holding the new key — the live video is unplayable until the editor retries. Fix is to persist the sidecar after the parent write, or key sidecars per collection.migrateMediaCollection) — same shape as Add API based on NestJS #2, the other direction. Rare; a design choice./query; weaker than intended for the key-harvesting defence (ADR 0019).isParentAvailablefetches every child Content doc (include_docs, incl.ftsarrays) to test three scalars, per encrypted video start. Wants a view orfieldsprojection.mediais a back-patch field). Documented server-wins semantics; changing it is a contract decision.MediaFileDto.ts,MediaUploadDataDto.ts,IsAudio.ts,s3-audio/audioFormatDetection.ts,music-metadata— zero non-test consumers on the epic. Same question as Add unit test workflow for app #1.error.messageis surfaced verbatim in CMS warnings (endpoint host, bucket name). Seven new CMS specs are flat rather than in__tests__/percms/CLAUDE.md.getHlsKeySidecaris test-only. All low.