Skip to content

Show a Placeholder While the Assistant Generates an Image - #91

Merged
lukaskollmer merged 1 commit into
page-scaffoldfrom
chat-and-models-pictured
Sep 21, 2026
Merged

lukaskollmer merged 1 commit into
page-scaffoldfrom
chat-and-models-pictured

Conversation

@PSchmiedmayer

@PSchmiedmayer PSchmiedmayer commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

♻️ Current situation & Problem

While the assistant drew a picture the chat showed nothing until the image arrived, and the conversation lost its place when it did. Opening a picture or a file viewer left the keyboard standing on iOS, the image viewer did not build on macOS, and the chat and LLM pages had screenshots of an older chat.

⚙️ Release Notes

ConversationComposer with a quoteCitationsImage viewerTool callFollow-up
ConversationComposer with a quoteCitationsImage viewerTool callFollow-up
  • A picture the assistant announces gets a placeholder in the conversation right away; LLMContext.complete(assistantImage:) fills it in and removeGeneratingImages(for:) drops what an interaction never delivered. The context encodes the placeholder, so a restored conversation shows it too.
  • Pictures fade in where their placeholder was, the keyboard goes away before a viewer opens, and the viewer fits the picture on macOS as it does on iOS.
  • The chat's documentation walk seeds a "Health Assistant" conversation with charts drawn as pictures; the chat and LLM pages show a conversation, the composer with a quote, a follow-up, citations, the viewer and a tool call.

📚 Documentation

The chat page gets its images and a walk through the composer; the LLM page documents the placeholder and points at the chat.

✅ Testing

Unit tests for the placeholder's coding and the context's completion, the zoomable view's fitting, and the chat and LLM UI suites on an iPhone 17 Pro with iOS 26.5 plus the macOS unit legs.

Code of Conduct & Contributing Guidelines

By creating and submitting this pull request, you agree to follow our Code of Conduct and Contributing Guidelines:

Copilot AI lite review requested due to automatic review settings September 7, 2026 18:07
@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: e9d3f060-373a-4731-8662-7d51028db978


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

🟡 Changes recommended

There are confirmed build-blocking issues (an extension placed outside its #if canImport(GroveChat) guard and missing UIKit import) plus missing .png.license sidecars for newly referenced DocC screenshots.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves the chat and LLM user experience around assistant-generated images by introducing an in-chat “generating image” placeholder that preserves scroll position, enhancing image/file viewing interactions (including keyboard dismissal), and updating DocC documentation screenshots plus supporting tests.

Changes:

  • Add a generating-image placeholder flow end-to-end (LLMContext placeholder storage/encoding → GroveChat placeholder UI → completion/removal APIs).
  • Improve image viewing and zoom fitting behavior (fade-in reveal, viewer filtering of placeholders, refit-on-bounds-change).
  • Refresh documentation + screenshot capture UI tests for chat and LLM onboarding, including a seeded documentation conversation.
File summaries
File Description
Tests/GroveLLMTests/UITests/TestAppUITests/DocumentationScreenshots.swift Adds UI-walk to capture OpenAI onboarding screenshots for DocC.
Tests/GroveLLMTests/UITests/TestApp/LLMLocal/Onboarding/LLMLocalOnboardingWelcomeView.swift Switches onboarding welcome to PageView.
Tests/GroveLLMTests/LLMContextImageTests.swift Adds unit tests for announced/completed/removed generated images.
Tests/GroveChatTests/ZoomableImageViewTests.swift Adds test ensuring refit on bounds change without image inflation.
Tests/GroveChatTests/UITests/TestAppUITests/DocumentationScreenshots.swift Adds UI-walk to capture chat documentation screenshots.
Tests/GroveChatTests/UITests/TestApp/DocumentationConversation.swift Adds seeded “Health Assistant” conversation and rendered chart images for docs.
Tests/GroveChatTests/UITests/TestApp/ChatTestView.swift Adds documentation-mode responses + placeholder-to-image replacement behavior.
Tests/GroveChatTests/ChatEntityImageCodingTests.swift Adds Codable round-trip tests for .generating and URL images.
Sources/GroveLLM/Models/LLMContextEntity.swift Introduces _ImageContent.generating and isGenerating helper.
Sources/GroveLLM/Models/LLMContext.swift Adds complete(assistantImage:) and removeGeneratingImages(for:).
Sources/GroveLLM/Helpers/LLMContext+Chat.swift Maps generating images into GroveChat as .generating placeholders.
Sources/GroveLLM/GroveLLM.docc/GroveLLM.md Documents generated-image placeholder behavior and adds screenshot.
Sources/GroveChat/Resources/Localizable.xcstrings Updates follow-up string and adds “Generating image” localization key.
Sources/GroveChat/Models/PlatformImage.swift Ensures .generating has no PlatformImage representation.
Sources/GroveChat/Models/ChatEntity.swift Adds ChatEntity.Content.Image.generating + Codable support.
Sources/GroveChat/Message View/ZoomableImageView.swift Prevents re-entrant fitting and avoids zoom/frame inflation issues.
Sources/GroveChat/Message View/SelectableMessageText.swift Renames follow-up action and adjusts action prominence (Textual).
Sources/GroveChat/Message View/PlainMessageView.swift Adds placeholder-to-image reveal transitions and blocks tapping placeholders.
Sources/GroveChat/Message View/GeneratingImageView.swift Adds animated dot placeholder view for generating images.
Sources/GroveChat/Message View/ChatImageViewer.swift Filters out generating placeholders and improves share-sheet behavior.
Sources/GroveChat/Message View/AttachedFilesView.swift Dismisses keyboard before presenting Quick Look.
Sources/GroveChat/Helpers/ProgressiveBlur.swift Removes progressive blur helper implementation.
Sources/GroveChat/Helpers/Keyboard.swift Adds reusable keyboard dismissal helper for viewer presentation.
Sources/GroveChat/GroveChat.docc/Resources/ChatView~dark.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/Resources/ChatView+VoiceInput~dark.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/Resources/ChatView+VoiceInput.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/Resources/ChatView+TextInput~dark.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/Resources/ChatView+TextInput.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/Resources/ChatView.png.license Removes legacy DocC image license sidecar.
Sources/GroveChat/GroveChat.docc/GroveChat.md Updates overview to new screenshots and documents generated pictures + follow-up.
Sources/GroveChat/ChatView+Export.swift Treats .generating like an image in text export description.
Sources/GroveChat/ChatView.swift Adds DocC inline image to ChatView header docs.
Review details
  • Files reviewed: 32/52 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/GroveChat/Helpers/Keyboard.swift
Comment thread Sources/GroveLLM/Helpers/LLMContext+Chat.swift Outdated
Comment thread Sources/GroveChat/GroveChat.docc/GroveChat.md
Comment thread Sources/GroveLLM/GroveLLM.docc/GroveLLM.md
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch from ffeba82 to 07dfa5b Compare September 7, 2026 18:55
@PSchmiedmayer PSchmiedmayer changed the title Hold the Chat's Place While the Assistant Draws and Picture the Chat and the Models Show a Placeholder While the Assistant Generates an Image Sep 7, 2026
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch from 07dfa5b to dad1538 Compare September 8, 2026 06:35
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch from dad1538 to 993472a Compare September 8, 2026 13:58
@github-project-automation github-project-automation Bot moved this to Todo in Grove Sep 8, 2026
@PSchmiedmayer PSchmiedmayer added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 8, 2026
@PSchmiedmayer PSchmiedmayer self-assigned this Sep 8, 2026
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch 2 times, most recently from 4910529 to b4ac639 Compare September 9, 2026 04:36
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch from b4ac639 to 9342bcc Compare September 9, 2026 04:42
@PSchmiedmayer
PSchmiedmayer force-pushed the chat-and-models-pictured branch from 9342bcc to 17e0c16 Compare September 9, 2026 04:46
@PSchmiedmayer PSchmiedmayer added this to the 0.3.0 milestone Sep 9, 2026
@PSchmiedmayer
PSchmiedmayer removed this pull request from stack #92 September 11, 2026 16:15
@PSchmiedmayer
PSchmiedmayer added this pull request to stack #98 September 11, 2026 16:15
@PSchmiedmayer PSchmiedmayer moved this from Todo to In Progress in Grove Sep 13, 2026

@lukaskollmer lukaskollmer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good. one small issue (and i'm not sure if this is an issue in Chat, or in LLM, or in the owning app) is that if you have a chat open that is long enough to fill the full available vertical screen space, and it starts generating an image and switches from the regular 3 dot typing indicator to the new image generation thing, it doesnt adjust the scroll position, and the user only sees the top part of the image generation indicator (as much as what the height of the regular typing indicator was, which does properly scroll the chat all the way down...

Comment thread Sources/GroveChat/GroveChat.docc/Resources/Composer.png
Comment thread Sources/GroveChat/Helpers/Keyboard.swift
@PSchmiedmayer

Copy link
Copy Markdown
Contributor Author

Good idea to adjust the auto-scrolling for image generation @lukaskollmer; feel free to push an update on top of the stack if you have a good idea how to implement this. We have been trying to disable auto-scroll when the messages come as one and don't work with streaming. The Stanford AI API Hub had some issues with that.

@lukaskollmer
lukaskollmer merged commit d03ce4a into main Sep 21, 2026
61 of 81 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Grove Sep 21, 2026
@lukaskollmer
lukaskollmer deleted the chat-and-models-pictured branch September 21, 2026 14:48
lukaskollmer pushed a commit that referenced this pull request Sep 21, 2026
### ♻️ Current situation & Problem

Onboarding, account setup, consent and the questionnaire each built
their own page: their own title, their own footer, their own idea of
what a missing answer looks like. Validation drew a red box inside the
grey card it sat on, the questionnaire and consent tinted their cards
each in their own way, and a page that had to be read to the end looked
nothing like a page with a button. The documentation showed screenshots
from different eras, and the walks that regenerate them ran inside the
CI suites.

### ⚙️ Release Notes

<table><tr><td align="center"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Welcome~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Welcome.png"
width="200" alt="Page with floating actions"></picture></td><td
align="center"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/ImageHeader~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/ImageHeader.png"
width="200" alt="Header with a symbol"></picture></td><td
align="center"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveOnboarding/GroveOnboarding.docc/Resources/ScrolledTitle~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveOnboarding/GroveOnboarding.docc/Resources/ScrolledTitle.png"
width="200" alt="Title risen into the bar"></picture></td><td
align="center"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Validation~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Validation.png"
width="200" alt="Blocking feedback"></picture></td><td
align="center"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveLLMOpenAI/GroveLLMOpenAI.docc/Resources/APITokenStep~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveLLMOpenAI/GroveLLMOpenAI.docc/Resources/APITokenStep.png"
width="200" alt="LLM setup step"></picture></td><td
align="center"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Tiles~dark.png"><img
src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Tiles.png"
width="200" alt="Tiles"></picture></td></tr><tr><td
align="center"><sub>Page with floating actions</sub></td><td
align="center"><sub>Header with a symbol</sub></td><td
align="center"><sub>Title risen into the bar</sub></td><td
align="center"><sub>Blocking feedback</sub></td><td
align="center"><sub>LLM setup step</sub></td><td
align="center"><sub>Tiles</sub></td></tr></table>

- GroveViews gets the page scaffold every full-page step uses:
`PageView` with a header, scrolling content and a footer that floats
over it, `PageHeader` with an optional symbol and a `spacing` option for
a header under a navigation bar, and `PageActions` with
`ActionButtonRole` and `actionButtonStyle(_:)`. The pieces are modifiers
too: `floatingActions`, `fadesIntoBottomEdge`, `risesIntoNavigationBar`
with `acceptsRisingTitle` (measured in global coordinates, so it also
works on a form that recycles its rows), and `softScrollEdge` for lists
under a bar. GroveOnboarding keeps `OnboardingInformationView` and
`SequentialOnboardingView`; `OnboardingView` is an alias of `PageView`
with the areas initialiser. The LLM setup steps move over here; account,
consent and the questionnaire follow in #88, #90 and #89.
- One language for something that still needs the participant:
`BlockingMessage`, and `reportsBlocking` on the control with
`highlightsBlockingContent` on its row or card, so the red is painted in
the container's own shape. `VerifiableTextField` uses it here; the
questionnaire and consent cards adopt it in #89 and #90.
- `Scripts/documentation-screenshots.sh` captures through RocketSim and
a `snapshot` directive installs a snapshot-test reference for views only
a snapshot renders. The walks skip themselves unless the script runs
them. Every catalogue except chat, LLM, account, consent and the
questionnaire is regenerated here, with inline images on the public
views.
- Textual 0.6.4, `ProgressiveBlur` moves from the chat to GroveViews,
and lists under a bar get the soft edge.
- The questionnaire navigator taps a row only once it is reachable below
the bar, and the consent test helper drags a picker back out from under
it: with a floating footer both used to tap the bar instead.
- `ListHeader` stays until #88 removes its last users, the consent view
only picks up the new names, the chat keeps its own blur until #91, and
three images the README still shows leave with #90.

### 📚 Documentation

GroveViews' page gets a Pages and a Blocking Feedback topic,
GroveOnboarding describes onboarding on top of the scaffold, and the
Documentation Guide covers the script's directives.

### ✅ Testing

UI suites for Views, Onboarding, Chat, LLM, Scheduler, Contact, License,
AccessGuard, Devices and HealthKit on an iPhone 17 Pro with iOS 26.5,
the iOS and macOS unit legs, the floor build and the documentation
build. The chat, LLM, account, consent and questionnaire suites were run
on the full stack.

### Code of Conduct & Contributing Guidelines

By creating and submitting this pull request, you agree to follow our
[Code of
Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants