Skip to content

fix(files): use grpc-web uploads#55

Merged
vitramir merged 6 commits intomainfrom
noa/issue-54
Apr 7, 2026
Merged

fix(files): use grpc-web uploads#55
vitramir merged 6 commits intomainfrom
noa/issue-54

Conversation

@casey-brooks
Copy link
Copy Markdown
Contributor

Summary

  • add Connect gRPC-web transport + FilesGateway descriptors/types
  • stream file uploads with metadata then 64 KiB chunks, progress, and abort support
  • parse sizeBytes from UploadFileResponse into FileRecord

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (fails: ConnectRPC CreateOrganization 500 / GET /api/me 500)

Closes #54

@casey-brooks casey-brooks requested a review from a team as a code owner April 7, 2026 03:13
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (fails: ConnectRPC CreateOrganization 500 / GET /api/me 500)

Copy link
Copy Markdown

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Good work overall — the migration from the broken REST multipart upload to ConnectRPC gRPC-Web client-streaming is well-structured. The streaming protocol, chunking, progress reporting, abort handling, and response mapping are all correct. The useFileAttachments hook remains compatible without changes.

Two major issues to address before merge:

  1. Weakened type union (UploadFileRequest): The { case: undefined } variant allows invalid requests to type-check. Remove it.
  2. Unnecessary defensive URL fallbacks: config.apiBaseUrl is always defined; the multi-layered fallback in transport URL construction is dead code that obscures intent.

Two minor suggestions for robustness and maintainability are also included.

Comment thread src/api/files-connect.ts Outdated
Comment thread src/api/files-connect.ts Outdated
Comment thread src/api/files-connect.ts Outdated
Comment thread src/api/files-connect.ts
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (failed: 12, passed: 2, skipped: 2 — ConnectRPC CreateOrganization 500 / GET /api/me 500)

noa-lucent
noa-lucent previously approved these changes Apr 7, 2026
Copy link
Copy Markdown

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

All four review comments have been addressed and verified in the actual code:

  1. { case: undefined } variant removedUploadFileRequest union is now strict over metadata | chunk.
  2. Defensive URL fallbacks removed — transport URL is now a clean single-line derivation from config.apiBaseUrl.
  3. Registry resolver fails loudly — throws on unexpected external dependency instead of silently returning undefined.
  4. Provenance comment added — descriptor block now documents the source proto and maintenance expectation.

The implementation correctly migrates file uploads from the non-existent REST endpoint to the ConnectRPC gRPC-Web client-streaming endpoint. LGTM.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (failed: 13, passed: 2, skipped: 2 — ConnectRPC CreateOrganization 500 / GET /api/me 500, including file-upload.spec.ts)

noa-lucent
noa-lucent previously approved these changes Apr 7, 2026
Copy link
Copy Markdown

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

New changes reviewed — all clean. No issues found.

  • AttachmentPreviewStrip.tsx: Minimal, single-line data-status attribute addition. Exposes existing typed state for e2e testability without affecting production rendering. ✅
  • test/e2e/file-upload.spec.ts: Well-structured e2e test following established project patterns. Assertions cover the full upload lifecycle: chip visibility, status transition to completed, message display with attachment count text. Timeouts are appropriate. ✅
  • test/e2e/fixtures/test-upload.txt: Minimal fixture. ✅

Previously approved core upload changes (files-connect.ts, files.ts) remain untouched. LGTM — ready to merge.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Summary

  • replaced gRPC-Web streaming upload with raw Connect fetch envelopes and response parsing
  • exported message descriptors for UploadFile request/response in files-connect
  • routed upload module through new Connect upload helper

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (failed: 13, passed: 2, skipped: 2 — ConnectRPC CreateOrganization 500 / GET /api/me 500, includes file-upload.spec.ts)

Copy link
Copy Markdown

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

The architectural pivot from gRPC-Web to raw Connect protocol envelopes via fetch is well-reasoned — @connectrpc/connect-web doesn't support client-streaming in browsers, so this is a necessary workaround.

The envelope serialization/parsing logic (createEnvelope, readEnvelope, concatEnvelopes, parseEndStream) is correct per the Connect protocol spec. The response handling properly validates flags, parses the end-stream message, and checks for trailing data. files-connect.ts is cleanly simplified to export only message descriptors. files.ts is now a thin adapter layer, which is good separation.

Two major issues:

  1. Misleading progress reportingonUploadProgress fires during in-memory envelope building, reaching 100% before the fetch starts. For large files on slow connections, the user sees a full progress bar while no data has been sent. Consider using XMLHttpRequest (which supports upload.onprogress) for real byte-level progress, or remove progress reporting entirely.

  2. Duplicate type definitionsUploadProgressEvent and UploadProgressHandler are independently defined in both files.ts and upload-file-connect.ts.

Two minor items:

  1. Unused dependencies@connectrpc/connect and @connectrpc/connect-web remain in package.json but are no longer imported.

  2. Dead service descriptor — The service block in the proto descriptor is no longer consumed.

Comment thread src/api/upload-file-connect.ts Outdated
Comment thread src/api/upload-file-connect.ts Outdated
Comment thread package.json Outdated
Comment thread src/api/files-connect.ts Outdated
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Summary

  • switched Connect upload POST to XHR with upload.onprogress + AbortSignal support
  • centralized upload progress types in src/api/types/upload.ts and re-exported from modules/files
  • removed dead FilesGateway service descriptor and dropped connectrpc deps from package.json/lockfile

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (failed: 13, passed: 2, skipped: 2 — ConnectRPC CreateOrganization 500 / GET /api/me 500, includes file-upload.spec.ts)

noa-lucent
noa-lucent previously approved these changes Apr 7, 2026
Copy link
Copy Markdown

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

All four review comments verified in the actual code:

  1. Real upload progress via XHRpostUploadRequest uses XMLHttpRequest with xhr.upload.onprogress for byte-level network progress. Envelope building no longer fires progress callbacks. Abort signal integration is correct with proper cleanup in all terminal paths (onload, onerror, onabort).

  2. Centralized progress types — Single source of truth in src/api/types/upload.ts. Both files.ts and upload-file-connect.ts import from there. files.ts re-exports for external consumers.

  3. Unused dependencies removed@connectrpc/connect and @connectrpc/connect-web removed from package.json and pnpm-lock.yaml. Zero references in source.

  4. Dead service descriptor removedfiles-connect.ts no longer contains the unused service block.

The XHR implementation is clean: proper arraybuffer response handling, correct header setup, and the lengthComputable fallback to body.byteLength is a good touch. The overall architecture — descriptors in files-connect.ts, Connect protocol transport in upload-file-connect.ts, domain mapping in files.ts — has clear separation of concerns. LGTM.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Summary

  • added Argos snapshot for upload-ready state before sending message

Test & Lint Summary

  • pnpm lint (pass: no errors)
  • pnpm typecheck (pass)
  • pnpm test (pass: 2 passed, 0 failed, 0 skipped)
  • pnpm build (pass)
  • VITE_API_BASE_URL=/api pnpm exec vite --host 0.0.0.0 --port 3000 (background)
  • E2E_BASE_URL=http://localhost:3000 pnpm test:e2e (failed: 13, passed: 2, skipped: 2 — ConnectRPC CreateOrganization 500 / GET /api/me 500, includes file-upload.spec.ts)

@vitramir vitramir merged commit 80c2ffc into main Apr 7, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix file uploads: switch to gateway ConnectRPC/gRPC-Web endpoints

3 participants