Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions atlas/domains.atlas
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ domain media "Images & assets" {
contains mod:rosetta_store::assets::{renditions, image_refs}, mod:rosettad::image_renditions
touches mod:rosettad::maintenance_pace
contains path:crates/adapters/*/src/images.rs, file:crates/adapters/claude-code/src/media.rs
contains mod:rosetta_core::presented_media, mod:rosetta_adapter_support::presented_media, mod:rosetta_pipeline::presented_media
contains path:crates/adapters/*/src/presented_media.rs
touches mod:rosetta_capture::ledger::document
contains path:clients/room/src/components/file-viewer/, file:clients/room/src/lib/image-media.ts
see "docs/ARCHITECTURE.md#412-authored-images-2026-09-09"
}
Expand Down
1 change: 1 addition & 0 deletions clients/app/design/transcript-record.html
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,7 @@ <h2>Images stay in view</h2>
<p><strong>Layout.</strong> The prompt keeps its filled text bubble, with images in a separate scrolling row beneath it. Replies use the same row beneath their prose. Every activity group collects its tool, team, peer, report and assignment images in one row below the group summary, outside both fold levels. Opening either fold keeps that row in place without duplicating thumbnails. Rounded square thumbnails are 144px wide (112px in a narrow column), and keep the row compact even for long screenshots. A press opens the whole image in the existing viewer; Escape returns to the same place.</p>
<p><strong>Browse the row · 2026-10-01.</strong> A multi-image row opens one gallery in the existing viewer. A bottom strip shows every slot in order, with the current thumbnail outlined and its position shown. Previous/next buttons and Left/Right keys move between available images; Home/End reach the row’s ends. Navigation stops at the ends, keeps Fit/1:1 and fullscreen, and never crosses into another row. Missing images keep a disabled placeholder. Loading or retry leaves the strip available. Closing restores the original thumbnail or text link, including inside a full-message popup.</p>
<p><strong>Image loading · 2026-10-01.</strong> The row reserves its tile geometry while small WebP thumbnails load. The bottom strip uses smaller thumbnails, Fit uses a display preview, and 1:1 loads the full image. Moving through the conversation releases offscreen image elements without changing the row’s place. Loading and retry keep gallery navigation available. Every generated size comes from the recorded source; the ledger preserves the original, while the image cache holds compressed WebP. SVG/AVIF tool-file previews retain browser rendering without adding original cache copies.</p>
<p><strong>Sent videos · 2026-11-12.</strong> A video an agent sent the reader sits in the same row as the run's images, in transcript order. It differs from two laws below, on purpose. <em>I-2:</em> its tile is not square. It keeps the row's height (144px, 112px or 96px) and takes the video's own shape, 16:9 when the dimensions were not recorded, never narrower than a square nor wider than 21:9. <em>I-2 and I-6:</em> its name and size are printed on the tile instead of living in a tooltip, because there is no poster to recognise it by: nothing is requested for a video until the reader presses play, not a thumbnail and not the file. A press plays it in place with the browser's own controls; it does not open the room's viewer, and the gallery stays images only. While the video has focus, the arrow, Home and End keys are the video's and do not walk the row. The file is read through the session and played from an object URL the room revokes, so it can be scrubbed; a host whose address honours byte ranges may offer that address instead, and this app offers none because the daemon serves a file whole. <em>Could not play video</em> keeps the slot and offers Retry. A video that was sent but not captured keeps the same tile shape, dashed, with <em>Video not captured</em>, its name and its size; it is not a button and has no Retry. An image sent the same way says <em>Image not captured</em>, which narrows I-4: <em>Image data was not recorded</em> now covers missing bytes, and a recorded reference says it was not captured.</p>
</section>
<div class="im-review" id="image-review">
<div class="im-controls">
Expand Down
1 change: 1 addition & 0 deletions clients/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"test:activity-chart": "electron test/activity-chart.electron.mjs",
"test:models-chart": "electron test/models-chart.electron.mjs",
"test:room-host": "electron test/room-host.electron.mjs",
"test:video-tile": "electron test/video-tile.electron.mjs",
"test:cold-import": "electron test/cold-import.electron.mjs",
"typecheck": "tsc --noEmit"
},
Expand Down
9 changes: 7 additions & 2 deletions clients/app/renderer/src/host/rosetta-ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,13 @@ export function sameOriginAssetUrl(client: Pick<RosettaClient, "baseUrl">, asset
}
}

/** Every image and attachment by the daemon's own URL: this daemon serves
* an `<img src>` without a credential. */
/** Every image and attachment by the daemon's own URL: this daemon serves an
* `<img src>` without a credential. A video has no address here
* (`mediaAssetUrl` is left out): the daemon answers a member whole, without
* byte ranges, and a `<video>` loaded from such an address plays but cannot
* seek (every seek lands at 0, measured in this app's Electron, 2026-11-12).
* The room therefore reads the member's bytes and plays an object URL, which
* seeks. */
export function assetsOver(client: RosettaClient): AssetSource {
return {
imageAssetUrl: (asset, variant) => client.imageAssetUrl(asset, variant),
Expand Down
Loading
Loading