Skip to content

Cycle 37: media upload feature (byte cap + composer UI) - #6

Merged
Vornecs merged 6 commits into
mainfrom
cycle-37-media-upload
Jul 3, 2026
Merged

Vornecs merged 6 commits into
mainfrom
cycle-37-media-upload

Conversation

@Vornecs

@Vornecs Vornecs commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Stacks on #5 (not yet merged). Operator asked for uploadable images/files in channels with client-side compression and limits looser than Discord's — split three ways:

  • Codex-18 (done directly by Claude) — raised MAX_ATTACHMENT_BYTES from 25MB to 50MB. Found and fixed a deeper issue while doing it: the limit was duplicated between app.ts and an independent .max() on initiateUploadRequestSchema in protocol.ts — bumping only one would have left uploads silently capped at the old value via schema rejection. Moved the constant into @cove/contracts as the single source of truth for both.
  • AG-20 (Grok CLI, reviewed + landed) — extends the existing Composer.tsx with drag-drop, file-picker wiring, and client-side image compression (canvas resize to a 2560px longest edge + JPEG/WebP re-encode, only for images over 2MB, non-images and small images pass through untouched). Presentation-only — no network calls; the actual upload flow wiring into App.tsx is a separate next step.
  • AG-21 (inline attachment rendering in messages) — left queued for Antigravity's normal rotation, not part of this PR.

Also fixed a real correction discovered mid-session: the Grok CLI's default model had resolved to grok-composer-2.5-fast, which its own model registry labels "Cursor's latest coding model" (agent_type: "cursor") — not xAI's. All invocations from AG-20 onward pin -m grok-build explicitly, verified via --debug-file output showing model: "grok-build" against cli-chat-proxy.grok.com.

The backend attachment pipeline itself (initiate/upload/serve/quarantine-approve/attach-to-message) was already fully built before this PR — confirmed by reading the code, not assumed.

Test plan

  • npm run typecheck — clean across all 5 workspaces
  • npm test — 278/278 passing (123 core + 117 web + 24 contracts + 14 desktop)
  • npm run format:check — clean
  • Reviewed both landed items against their task specs; AG-20 in particular checked for correct compression gating, error fallback, and object-URL cleanup

🤖 Generated with Claude Code

mistral-vibe and others added 5 commits July 2, 2026 23:47
Operator asked for uploadable images/files in channels with client-
side compression and limits looser than Discord's. Investigated the
existing pipeline first: the backend is already fully built and
working end-to-end (initiate/upload/serve/attach-to-message) - this
is purely a frontend gap.

Assigned: Codex-18 (bump the 25MB cap to 50MB, fix two hardcoded
error-message strings that would otherwise drift from the constant),
AG-20 (composer drag-drop/file-picker/client-side image compression),
AG-21 (inline attachment rendering in messages). Also corrected AG-19's
brief - there is no approve/reject route to wire against; uploads
auto-approve today, so nothing is actually stuck in quarantine yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MAX_ATTACHMENT_BYTES was duplicated between app.ts (a local const)
and initiateUploadRequestSchema's size .max() in protocol.ts, both
hardcoded to 25MB independently - bumping only one would have left
the schema silently capping uploads at the old limit regardless.
Moved the constant into @cove/contracts as the single source of
truth, bumped it to 50MB, and updated both call sites plus the two
hardcoded "25 MB" error-message strings to derive from it instead of
repeating the number.

Updated the existing over-limit test's threshold and added a test
for the new ceiling. 2 new tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codex-18 done directly by Claude, AG-20 (composer attachment UI)
running via Grok CLI tonight, AG-21 (inline rendering) left queued
for Antigravity's normal rotation, per operator's explicit 3-way
split request.

Also documents a real correction: the Grok CLI's default model had
resolved to grok-composer-2.5-fast, which its own model registry
labels as Cursor's model (agent_type: "cursor"), not xAI's. Verified
via --debug-file output that -m grok-build correctly resolves to
xAI's own model against cli-chat-proxy.grok.com. All invocations from
here on pin -m grok-build explicitly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the existing Composer.tsx (message textarea/reply-bar/send)
with attachment support:

- Wires the previously-inert "Add attachment" button to a hidden
  file input, plus drag-and-drop over the whole composer form.
- Client-side image compression: only for image/* files over 2MB,
  scales the longest edge to at most 2560px via canvas, re-encodes
  as JPEG and WebP in parallel and keeps whichever is smaller, with
  a graceful fallback to the original file on any error. Small
  images and non-image files pass through untouched.
- Pending-attachment strip above the textarea (thumbnail for images,
  filename chip for everything else) with per-item remove.
- New props: pendingAttachments, onFilesSelected, onRemoveAttachment.
  Composer stays presentation-only - no network calls, no permanent
  file-list ownership; the actual initiate/upload calls happen where
  this gets wired into App.tsx next.

Built via a parallel Grok CLI trial run in a disposable clone (this
time pinned to -m grok-build explicitly after discovering the CLI's
default had resolved to Cursor's Composer model), reviewed and
independently re-verified before landing. 6 new tests, including
canvas/Image/URL mocking for the compression path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 06:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Updates the attachment upload experience end-to-end for Cycle 37 by raising the backend attachment size cap to 50MB (with a single shared constant in @cove/contracts) and adding a presentation-only attachment picker/drag-drop + client-side image compression UI to the web composer (with unit tests and minimal styling).

Changes:

  • Centralize and raise the attachment size limit to 50MB across both backend enforcement and request-schema validation.
  • Extend Composer with attachment selection (button + drag/drop), pending-attachment preview strip, and client-side image compression for large images.
  • Add/extend unit tests for both the backend attachment pipeline limit and the composer attachment behaviors.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
WORKLOG.md Records Codex-18 + AG-20 landing notes and queue updates.
status/grok.md Status report for AG-20 work (composer attachment UI).
services/core/src/app.ts Uses shared MAX_ATTACHMENT_BYTES and updates limit error strings accordingly.
services/core/src/app.test.ts Updates/extends tests for the new 50MB initiation cap.
packages/contracts/src/protocol.ts Introduces MAX_ATTACHMENT_BYTES and uses it in initiateUploadRequestSchema.
apps/web/src/styles.css Adds composer attachment strip/chip styling and drag-over highlight.
apps/web/src/Composer.tsx Adds attachment UI hooks, compression logic, drag/drop handlers, and preview URL lifecycle management.
apps/web/src/Composer.test.tsx Adds jsdom-friendly tests covering picker, drag/drop, preview strip, removal, and compression gating.

Comment thread apps/web/src/Composer.tsx
Comment thread apps/web/src/Composer.tsx Outdated
Comment thread apps/web/src/styles.css
- compressImage now keeps the original file if every re-encoded
  candidate (JPEG/WebP) turns out larger than the source - some
  source formats (e.g. certain PNGs) don't shrink well, and blindly
  swapping to the "smallest of the re-encodes" could increase upload
  size despite being on the compression path. Added a test that
  forces oversized re-encodes and asserts the original is kept.
- Attachment chips now use a stable per-File key (a WeakMap-backed
  id assigned on first render) instead of the array index. Removing
  an item from the middle of the list shifts every subsequent index,
  which could cause React to reuse the wrong chip's DOM (and its
  <img>/blob URL) for a different file.
- Removed a redundant `grid-column: 1 / -1` declaration on
  `.attachment-strip` - it's already covered by the combined
  `.reply-bar, .attachment-strip` rule a few lines down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Vornecs
Vornecs merged commit 414d5b9 into main Jul 3, 2026
2 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.

3 participants