Promote coherent media platform (#319, #312, #321) - #344
Merged
Conversation
… + label sniffed types Widen the magic-byte inline allowlist from MP4/WebM to every video container mainstream browsers can natively play: QuickTime (.mov — including screen recordings misnamed .mp4), M4V, Ogg, 3GPP, 3GPP2, and Matroska. The decision still comes only from server-side byte sniffing, never the filename extension, and SVG/HTML/active formats remain opaque octet-stream downloads. When a sniffed container stays a generic download (AVI, WMV, MPEG-TS…), preserve the sniffed MIME as a new optional crystal field detectedContentType so the download row can say 'AVI video' instead of application/octet-stream. The canonical crystal union accepts the field only alongside an octet-stream contentType, keeping it display metadata that can never widen inline serving; client normalization mirrors that. Since codec support inside an allowed container varies per browser (HEVC QuickTime on Firefox, MKV on Safari), a failed <video> element now degrades to its named download row instead of an inert black player. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Attachment crystals gain optional title (<=200 chars, single line) and description (<=2000 chars, newlines allowed) - sanitized, canonical (closed shape with the two optional keys), projected in public metadata, and absent-never-empty. POST /api/v1/attachments/annotate sets/clears them on an owned READY attachment (ready-only so finalize cannot clobber early annotations) with exact storage-delta accounting like markReady. Client PublicAttachment + normalize + useApi carry the fields; mediaPageUrl() names the /media/:id deeplink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/v1/things?id=<attachmentId> now returns the attachment's post-shaped projection (reactions, comments, views - the resolvers are target-generic) plus parent/root resolving to the post it is bound to, powering the /media/:id Thing page. Attachment kind stays protected: annotate is the only crystal mutation path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post image attachments now flow as a natural-aspect CSS-columns masonry (responsive 1/2/3 columns, title chip overlay). Clicking opens the new MediaLightbox: arrow-key/edge navigation, description caption, download, and an Open deeplink to /media/:id - every attachment's own Thingtime page. The media page renders the attachment as a full PostCard (its own reactions, comments, and views - relational things targeting the attachment id), owner title/description editing via annotate, and a link back to the parent post. PostCard gains a mediaThing mode that keeps interactions but drops edit/privacy/delete (attachment kind is protected; lifecycle belongs to the parent post). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s) + lightbox Esc close - AttachmentAnnotatePopover: shared pencil popover editing title/description, saved via /api/v1/attachments/annotate, optimistic apply + revert with a Lopu toast on failure; fits 375px viewports - wired into ready composer tiles (visual + file rows) via a new updateAttachment patch in useAttachmentUploads, and into the edit-mode AttachmentReorderGallery (id order untouched, so no false order change) - MediaLightbox: close on Escape from the window keydown listener too — autoFocus is off, so Chakra's closeOnEsc never hears the key when focus sits outside the modal (found in preview E2E) - TESTING.md: media thing pages checklist (masonry, lightbox, /media/:id, annotate, delete cascade) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…erver validation - crystal.mediaLayout on posts: mode masonry|rows|grid, rows pattern (1-25 entries of 1-6, extras repeat the last row size), grid columns 1-6 + per-image spans (wide/tall/big; redundant normals dropped) — sanitized in sanitizePostCrystal with strict bounds, legacy crystals stay valid; PATCH sets/clears it through the same validator - projected as PublicPost/PublicComment.mediaLayout (toPublicPosts project + buildComment + addComment echo) so cards, comments, shares, and media things all carry it - PostAttachments renders the three modes: masonry unchanged; rows via pattern chunks (hero 16:9, pairs 4:3, 3+ square); grid via CSS grid with span-aware aspect ratios and dense flow — lightbox order stays attachment order in every mode - 8 new schema tests (test:schemas 61/61); TESTING.md layout checklist Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ayout editor)
- MediaLayoutControls: Auto 🧱 / Rows 🥞 / Grid 🔳 segmented picker with a
live pattern preview ('1-2-3' style input, bounded parse) and a grid
columns stepper; SpanCycleButton cycles a tile normal → wide → tall → big
- PostComposer owns the layout state (seeded from editPost.mediaLayout),
shows the picker from 2+ visual attachments, prunes spans to the outgoing
visual set, and sends crystal.mediaLayout through create, edit (full-crystal
replace), and the frozen create expectation (sameJson stays exact)
- createPost/addComment map body.mediaLayout into the sanitized crystal
- AttachmentComposer + AttachmentReorderGallery grow a tileExtras render prop
(badge bottom-left; grip/pencil corners unchanged), active in grid mode
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tor) - MediaLayoutCanvas: live CSS-grid preview of the actual layout with a columns slider (1-6, relayouts live) and a ⤡ corner handle per tile — pointer-event drag (mouse + touch, touchAction none, pointer capture) snaps tiles between normal/wide/tall/big at ~45% of a cell, with a live outline preview; arrow-key fallback on the focused handle (right wider, left narrower, down taller, up shorter) - shown in the composer + edit mode whenever Grid is active with 2+ visual attachments; writes the same layoutSpans/columns state as the tier-2 badges, so canvas drags, badges, and the saved crystal never drift - span geometry helpers (spanColumns/spanRows/spanAspect) exported from PostAttachments so render and editor share one source of truth Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
handlePointerDown calls preventDefault to keep the drag from selecting or scrolling, which also suppresses the native focus — so clicking a tile's resize handle and then pressing an arrow key did nothing. Take focus explicitly (preventScroll so the canvas does not jump) and the documented keyboard fallback works after a click, not only after tabbing to the handle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tals Ready attachments finalized before PR #319's magic-byte detection keep crystal contentType application/octet-stream with no detectedContentType, so browser-playable uploads still render as file cards. Add an admin-only POST /api/v1/attachments/backfill-detected-types that re-runs detection from the object's first 8 KiB and publishes exactly what completion would have: browser-playable containers flip to their inline contentType and mediaKind, other canonical sniffed types gain detectedContentType, and undetectable bytes stay untouched so a later, wider detector can claim them. Names, sizes, object keys, and object versions never change; the crystal's JSON-payload delta settles through the same transactional storage ledger markReady uses. Each pass is bounded (at most 200 rows, 5 workers, 25s wall clock), idempotent, cursor-paginated, and supports dryRun. New store methods listReadyUndetected and upgradeReadyCrystal (markReady-modeled, name and size immutable, object-version-fenced); rate key attachments.detectionBackfill; route + import map + apiDocs registration; service and route tests mirroring the completion-test mocks; TESTING.md checklist line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Included scope
This is the deliberately scoped production promotion for the coherent media dependency closure:
mainand is the base for this branchdev.thingtime.comto the exactdevelopGit branch after live drift bound it to the whole pre-production environment and let PR preview deployments move the stable signed-in S3 staging originThe live domain is now verified as
gitBranch: develop,customEnvironmentId: null, and aliases the exact READYdevelopdeployment58ef890c61030ccf1b75baad6bee14189399b78c.The runtime files in this promotion byte-match current
develop. The branch intentionally excludes unrelated develop work, including Components, Connections, Fable Five, passkeys/FedCM, Commander, iOS feature work, and unrelated CI/deployment changes.Graphify's generated graph/manifest/report/cache refreshes are isolated in generated-only commits so the hand-authored runtime commits remain reviewable.
Verification on this exact branch
npm --prefix remix run test:unit— passednpm --prefix remix run build— passed, including Vercel static-shell/CSP output verificationgit diff --check— cleanorigin/develop; no unrelated runtime file is presentdev.thingtime.comruntime reports🌱 developafter the binding repairPost-merge safety gate
After production is READY:
nextCursorto convergence;Media layout transport reconciliation
Source fix #345 is merged into
developas1e9bab325941d75abb2e8472557598c54aef81dd, and this promotion carries the same focused transport change. On the exact READY develop deployment, a fresh signed-in S3 run created two unique raster attachments with Grid=4, one wide span and one tall span. Reload preserved the rendered geometry; reopening Edit reported Grid selected, four columns, and the same wide/tall spans. This closes the pre-promotion live gate for the create transport regression.