Skip to content

th-1fca98: persist a user turn's images so other clients re-render them - #564

Merged
brentrager merged 1 commit into
mainfrom
th-1fca98-persist-turn-images
Sep 8, 2026
Merged

brentrager merged 1 commit into
mainfrom
th-1fca98-persist-turn-images

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

A photo uploaded from the iOS Big Smooth app shows locally but not in the desktop app's view of the same conversation — desktop sees text only. Images on send_message are consumed for the live LLM turn (with_user_images) but the inbound message is persisted text-only (MessageContent::from_text), so any other client reading the shared conversation history renders text with no picture.

Fix

  • ContentItem gains an optional url field and an image() constructor; type may now be "text" or "image".
  • MessageContent::from_text_and_images builds a text item plus one image item per URL.
  • persist_message takes an image_urls: &[String]; the inbound user turn passes the turn's image URLs, the outbound reply passes &[].
  • spec/domain/message.schema.json ContentItem updated (image added to the enum, url property added) to keep the contract in sync.

Text-only turns are byte-for-byte unchanged (from_text still used when no images). The addition is backward-compatible on the wire: url is optional (skip_serializing_if), image is additive to the enum.

Scope note: this is the Rust server path that Big Smooth (smooth-daemon) runs on. The polyglot cores (go/ts/python/dotnet) have their own ContentItem/persist paths; bringing them to parity is a follow-up, tracked separately — Big Smooth does not use them.

Verification

  • smooai-smooth-operator domain unit test: content_with_images_serializes_image_items_web_reads asserts the exact wire shape clients parse (items[].type / items[].url) and that text-only is unchanged.
  • smooai-smooth-operator-server end-to-end test: persist_turn_images::inbound_turn_images_are_persisted_for_cross_client_render drives a real turn with an attached image against in-memory storage and asserts the stored inbound message carries the image content item.
  • Full smooai-smooth-operator lib suite (249 tests) + spec_fixtures green; fmt + clippy clean.

The matching consumer change (bump the git rev in SmooAI/smooth + render image parts in smooth-web history) lands separately.

🤖 Generated with Claude Code

Images attached to send_message rode the live LLM turn only; the inbound
message was stored text-only (MessageContent::from_text), so a DIFFERENT
client reading the conversation history (desktop viewing a photo the iOS
app sent) saw text with no picture — cross-client parity gap.

ContentItem now carries an optional `url` and an "image" type; the runner
persists each of the turn's image URLs as an image content item alongside
the text (persist_message takes an image_urls slice; outbound passes &[]).
Text-only turns are byte-for-byte unchanged, and the addition is
backward-compatible on the wire (url optional, image additive to the
schema enum). Covered by a domain serialization test and an end-to-end
runner test that drives a turn with an image and asserts the stored
inbound message carries the image content item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVAvzYG7unJjCD9c3B9j1f
@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6df55f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@smooai/smooth-operator Patch
@smooai/smooth-operator-web-chat-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager
brentrager merged commit 8ce1ac5 into main Sep 8, 2026
8 of 9 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.

1 participant