Skip to content

Replace thread loading spinner with skeleton pattern#14906

Merged
tkheyfets merged 11 commits into
mainfrom
feature/thread-skeleton-loading
Mar 10, 2026
Merged

Replace thread loading spinner with skeleton pattern#14906
tkheyfets merged 11 commits into
mainfrom
feature/thread-skeleton-loading

Conversation

@tkheyfets
Copy link
Copy Markdown
Contributor

@tkheyfets tkheyfets commented Mar 10, 2026

Summary

Replace the spinning loading indicators in the chat area with content-aware skeleton placeholders that mimic the chat message layout, providing better perceived-performance UX when threads are loading.

Changes

  • Added ChatLoadingSkeleton component with alternating assistant/user skeleton message bubbles using VSkeletonBone + shimmer animation
  • Replaced ProgressView() spinner in ChatView (history loading state) with ChatLoadingSkeleton()
  • Replaced VLoadingIndicator in DaemonLoadingChatSkeleton with ChatLoadingSkeleton()
  • Replaced VLoadingIndicator + "Getting ready..." in ChatBootstrapLoadingView with ChatLoadingSkeleton()
  • Added VoiceOver accessibility labels for all skeleton loading states
  • Uses adaptive maxWidth sizing so skeletons scale with narrow containers

Milestone PRs (merged into feature branch)

Project issue

Closes #14888

Test plan

  • Open the app and click on a thread — verify skeleton appears instead of spinner while messages load
  • Kill the daemon and relaunch — verify skeleton appears in chat area during daemon connection
  • Test first-launch bootstrap — verify skeleton with fade-in instead of spinner + "Getting ready..."
  • Open a side panel to narrow the chat area — verify skeletons scale down without clipping
  • Enable VoiceOver — verify "Loading chat history" / "Getting ready" labels are announced

Generated with Claude Code


Open with Devin

tkheyfets and others added 2 commits March 10, 2026 16:26
* Add ChatLoadingSkeleton component with skeleton message bubbles

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove unused avatarReserve property and add #if DEBUG guard to preview

* Use adaptive maxWidth for skeleton bones to handle narrow containers

* Use VRadius.pill instead of hardcoded radius for avatar circle

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Replace loading spinners with ChatLoadingSkeleton in chat area

Co-Authored-By: Claude <noreply@anthropic.com>

* Update stale docstring on DaemonLoadingChatSkeleton

* Add accessibility labels for VoiceOver on skeleton loading states

---------

Co-authored-by: Claude <noreply@anthropic.com>
@tkheyfets tkheyfets self-assigned this Mar 10, 2026
@tkheyfets
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6339738044

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
}
}
.accessibilityHidden(true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove unconditional accessibility hiding from skeleton

ChatLoadingSkeleton applies .accessibilityHidden(true) at its root, which removes the view from the accessibility tree in every context. In this patch, both ChatView loading states rely on wrapping this skeleton with explicit labels ("Loading chat history" / "Getting ready"), so VoiceOver users can end up with no announced loading status after the spinner/text was removed. Keep the skeleton hidden only in contexts that are truly decorative (for example, the daemon overlay) and allow labeled loading states to remain accessible.

Useful? React with 👍 / 👎.

The skeleton itself should not unconditionally hide from VoiceOver.
Each usage site already controls accessibility appropriately:
- DaemonLoadingOverlay hides the ZStack (decorative)
- ChatView loading states use accessibilityElement + accessibilityLabel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tkheyfets
Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

tkheyfets and others added 8 commits March 10, 2026 18:06
- User message: right-aligned bubble with two short text lines, matching
  real ChatBubble user styling (fill, padding, corner radius)
- Assistant message: left-aligned with 28pt avatar placeholder and six
  varying-width text lines, using the same overlay/offset pattern as
  real ChatBubble (avatar at -(28+sm), content padded .leading 36pt)
- Spacing uses VSpacing.md between messages (matching LazyVStack)
- Constrained to chatColumnMaxWidth for proper centering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Start from top of chat area instead of vertically centered
- Use real assistant avatar (AvatarAppearanceManager) instead of
  skeleton bone for the avatar circle
- Add subtle bubble background to assistant message block
- Add Spacer at bottom of skeleton to push content to top

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use darker bone fill (surfaceBorder 0.7) instead of default 0.5
- Use surfaceBorder as shimmer highlight instead of bright surface color
- Widen user message bones (280/200pt) for more realistic proportions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User skeleton bubble now spans the same width as the assistant block
(680pt) instead of shrink-wrapping around short bones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
surfaceBorder is Moss._100 in light mode — same as background, making
bones invisible. Switch to textMuted (Stone._600 light / Moss._500 dark)
which contrasts well against the background in both color schemes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User messages are typically shorter than assistant responses.
The skeleton now reflects this with a 65% width bubble.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Real ChatBubble uses Color.clear for assistant messages and the skeleton
doesn't need bubble chrome — just bare bones matching the layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User messages have a visible bubble in both dark and light modes
(Moss._950 / Moss._200). Skeleton now uses the real userBubble color
with proper padding and corner radius matching ChatBubble.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tkheyfets tkheyfets merged commit 7e941e5 into main Mar 10, 2026
1 check passed
@tkheyfets tkheyfets deleted the feature/thread-skeleton-loading branch March 10, 2026 22:33
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.

Replace thread loading spinner with skeleton pattern

1 participant