Skip to content

E2 — Upload & Encoding Pipeline#113

Open
aloewright wants to merge 2 commits into
mainfrom
conductor/alo-121-e2-upload-encoding-pipeline
Open

E2 — Upload & Encoding Pipeline#113
aloewright wants to merge 2 commits into
mainfrom
conductor/alo-121-e2-upload-encoding-pipeline

Conversation

@aloewright
Copy link
Copy Markdown
Owner

@aloewright aloewright commented May 8, 2026

Closes ALO-121.

Hardens the chunked upload pipeline so a 1GB upload over a flaky connection completes and plays back as HLS.

Summary

Resume on disconnect

  • GET /api/videos/upload/:uploadId/status returns { chunkCount, uploadedChunks[], fileName, fileSize, title, description } so the client can re-derive server-known chunks after a disconnect or page refresh. KV key is user-scoped, so cross-user reads 404.
  • DELETE /api/videos/upload/:uploadId aborts the R2 multipart and clears KV state on user-initiated cancel — keeps R2 storage clean instead of waiting on lifecycle policy GC.
  • localStorage persistence keyed by file fingerprint (name + size + lastModified) survives page reloads. TTL aligned with the server's 24h KV TTL.

Idempotent chunk retry

  • Chunk-0 retry now reuses the existing R2 multipart instead of opening a second one when an earlier 202 was lost in transit (would otherwise leak an orphaned multipart and split the parts manifest).
  • Re-sending an already-accepted chunk overwrites the part in R2 (multipart semantics) and recomputes totalBytes from the parts map so completion stays consistent under retries.

Frontend retry + offline awareness

  • Each chunk retries with exponential backoff + full jitter (4 attempts; 5xx, 408, 429, network errors classified as transient; 4xx fail-fast).
  • navigator.onLine checked before each attempt; uploader pauses on offline and resumes on online.
  • AbortController + Cancel button — calls DELETE /api/videos/upload/:uploadId so the server-side multipart is torn down too.
  • Resume banner on /upload when a stale localStorage entry matches the picked file's fingerprint.

Refactors

  • src/workers/upload-session.ts — KV read/abort/delete helpers + zod-validated session schema. The previous inline KV manipulation in videos.ts is now a single readUploadSession() call.
  • src/frontend/lib/upload-resume.ts — pure, testable upload pipeline (retry classification, backoff, fingerprinting, localStorage I/O) so the React component is mostly UI.

Test plan

  • npm test538 pass (60+ new unit tests across upload-session.test.ts, upload-resume.test.ts, and the new endpoint tests in videos.test.ts)
  • npm run lint — clean (incl. AI Gateway guard)
  • npm run type-check — clean
  • npm run build — clean
  • Manual: upload 1GB file with network throttling + tab close mid-upload, verify resume picks up at the last accepted chunk

🤖 Generated with Claude Code

Adds GET /api/videos/upload/:uploadId/status so the client can re-derive
which chunks the server already has after a disconnect, plus server-side
idempotency: re-sending an already-accepted chunk acks without paying
for a redundant R2 multipart part. Frontend now retries each chunk with
exponential backoff and persists uploadId in localStorage so a refresh
mid-upload picks up where it left off.
Copilot AI review requested due to automatic review settings May 8, 2026 15:03
@aloewright aloewright added the conductor Conductor-managed PR label May 8, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Warning

Rate limit exceeded

@aloewright has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 59 minutes and 8 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b239bd48-50af-416f-ab7f-9b707964a21d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3c13f and b77cf2c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • src/frontend/lib/upload-resume.test.ts
  • src/frontend/lib/upload-resume.ts
  • src/frontend/pages/Upload.tsx
  • src/workers/upload-session.test.ts
  • src/workers/upload-session.ts
  • src/workers/videos.test.ts
  • src/workers/videos.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch conductor/alo-121-e2-upload-encoding-pipeline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 8, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c55551a4c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/workers/videos.ts
}

const priorBytes = Object.values(uploadedPartsMap).reduce((sum, part) => sum + part.size, 0);
if (priorBytes + rawFile.size > MAX_VIDEO_BYTES) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid double-counting bytes on final chunk retries

When the final chunk is retried and uploadedPartsMap[partKey] already exists, priorBytes already includes that chunk, but this guard still adds rawFile.size again. For uploads near MAX_VIDEO_BYTES, a lost final response followed by retry can now fail with a false file_too_large 400, which breaks the new idempotent final-chunk resend path. Apply this size check only when uploading a new part (or subtract the existing part size).

Useful? React with 👍 / 👎.

Comment thread src/frontend/pages/Upload.tsx Outdated
Comment on lines +72 to +73
} else {
try { localStorage.removeItem(persistKey); } catch { /* private mode */ }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve resume key on transient status lookup failures

The resume bootstrap deletes persistKey for every fetchResumeState miss, including transient network/5xx failures where the server upload session may still exist. In the flaky-connection scenario this feature targets, a refresh during temporary outage will discard the uploadId and force a full restart instead of resuming. Only clear the key for definitive stale cases (for example, 404/chunk-count mismatch), not transport or transient server errors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Lets a creator pause/disconnect mid-upload and pick up where they left off
without re-sending bytes the server already has. Hardens chunk-0 retry so a
swallowed 202 doesn't leak a parallel multipart in R2.

Backend:
  - GET  /api/videos/upload/:uploadId/status — uploadedChunks + chunkCount
  - DELETE /api/videos/upload/:uploadId — abort R2 multipart + clear KV
  - chunk-0 retry now reuses the existing R2 multipart instead of creating
    a duplicate when the same uploadId comes back
  - Session state extracted to src/workers/upload-session.ts (read/abort/
    delete helpers + zod-validated meta with optional fileName/fileSize
    fingerprint)

Frontend:
  - src/frontend/lib/upload-resume.ts — pure, testable upload pipeline
    with exponential-backoff retry on 5xx/408/429/network errors,
    online/offline awareness, AbortController support, and localStorage
    persistence (24h TTL aligned with KV)
  - Upload.tsx wires the new module: resume banner when a saved session
    matches the picked file's name/size/lastModified fingerprint, status
    chip ("Retrying…", "Waiting for connection…"), and a Cancel button
    that aborts the multipart server-side

Tests: 60+ new unit tests covering resume helpers, status/cancel routes,
chunk retry classification, backoff jitter, and offline-wait sequencing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 9, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conductor Conductor-managed PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants