feat(sdk): loading state = scene image + frosted card + circular loader#64
Open
LEVI-RIVKIN wants to merge 1 commit into
Open
feat(sdk): loading state = scene image + frosted card + circular loader#64LEVI-RIVKIN wants to merge 1 commit into
LEVI-RIVKIN wants to merge 1 commit into
Conversation
Contributor
📦 Snapshot ReleasePublished snapshot packages for this PR: pnpm add @perspective-ai/sdk@1.14.0-pr-64-20260708153147
pnpm add @perspective-ai/sdk-react@1.14.0-pr-64-20260708153147Or use the npm tag (always points to the latest snapshot from this PR): pnpm add @perspective-ai/sdk@pr-64
pnpm add @perspective-ai/sdk-react@pr-64 |
There was a problem hiding this comment.
Pull request overview
This PR updates the SDK’s embed loading skeleton to be a neutral, container-query–responsive overlay that reflects only the iframe’s outer “shape” (boxed on wide slots, full-bleed on narrow slots), with a calmer single-bar shimmer animation and updated default background colors aligned to the interview app’s surface tokens.
Changes:
- Replaced the detailed, UI-mirroring skeleton DOM with a minimal structure (content/surface + single shimmer bar) and moved layout/animation to a shared injected stylesheet driven by container queries.
- Updated default light/dark background and shimmer/border translucency values to match the app’s interview surface and reduce visual flash at handoff.
- Rewrote unit tests to assert the new structure, shared-style injection, container-query usage, and reduced-motion behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/sdk/src/loading.ts | Reworks skeleton implementation to a generic, container-query–responsive overlay with a shared injected stylesheet and per-instance CSS variables. |
| packages/sdk/src/loading.test.ts | Updates tests to validate the new DOM structure, stylesheet injection, and animation/scoping behavior. |
| .changeset/generic-loading-skeleton.md | Adds a patch changeset describing the new neutral skeleton behavior and compatibility notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1c8f3c9 to
3e4fbcb
Compare
7d90c82 to
ab32b62
Compare
vinaypuppal
approved these changes
Jul 7, 2026
ca44f6e to
0966116
Compare
9b7824e to
0dd030f
Compare
0dd030f to
a49fb6c
Compare
Replaces the loading skeleton with the interview's real chrome, outside-in:
the research's scene image, a frosted translucent card over it (55% panel +
backdrop blur, matching the app's card treatment), and a 40px circular
loader centered in the card (conic-gradient arc with a fading tail over a
faint track, round cap at the leading edge).
- Loader tint follows the SDK's usual precedence (same as the float
launcher): local brand.primary → API embed config primaryColor /
darkPrimaryColor → Perspective default (#7c3aed / #a78bfa). LoadingOptions
gains `apiConfig`; all five embeds pass _apiConfig through. Browsers
without color-mix() keep an arc-only fallback.
- Scene image comes from {host}/interview/{researchId}/scene-image and is
applied only after it actually loads, layered over the app's default
surface color (#f5f2f0 light / #15171e dark, or brand.bg) — a research
without a scene (404), slow network, or blocked request degrades to a
clean solid color. prefetchSceneImage() warms it on intent signals
(float-bubble hover/focus, popup/slider trigger elements).
- Container-query responsive: the overlay lives in the host page DOM next to
the cross-origin iframe, so layout keys off the consumer's slot, not the
viewport. At the app's own 672px breakpoint the card switches between a
centered box (height-relative percentage inset) and full-bleed.
- The loader always animates — essential loading motion; a frozen spinner
reads as hung — so there is no prefers-reduced-motion override.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kJptBdnAQ2VfzrE9ob1Wb
a49fb6c to
fe18680
Compare
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.
Summary
Replaces the embed loading skeleton with the interview's real chrome, rendered outside-in:
{host}/interview/{researchId}/scene-image) behindbackdrop-filter: blur(22px), matching the app's card treatment), withDetails
brand.primaryoverride → API embed config (primaryColor/darkPrimaryColor) → Perspective default (#7c3aed/#a78bfa).LoadingOptionsgainsapiConfig; all five embeds pass_apiConfigthrough. Empty strings fall through to the next source; browsers withoutcolor-mix()keep an arc-only fallback (the plainconic-gradientis declared first).#f5f2f0light /#15171edark, or a custombrand.bg) — a research without a scene (404), slow network, or blocked request degrades to a clean solid color.prefetchSceneImage()warms it on intent signals (float-bubble hover/focus, popup/slider trigger elements), backed by a bounded (capped) dedupe set.container-type: size(both axes) — not the viewport. The card's sizing mirrors the interview app's own container queries: full-bleed below 672px width; a centeredmax-width: 672pxbox above it, with vertical padding stepping 4px → 48px → 80px at the app's 448px / 768px container-height breakpoints. Verified pixel-for-pixel against the live iframe across the full width×height grid, so the card doesn't jump at handoff. Breakpoints are in px, not the app's rem: rem would resolve against the host page's (uncontrolled) root font-size, whereas the card must match the iframe's fixed pixels.visual-ready, it snaps toopacity: 1beneath the overlay (no transition) while the overlay fades out on top and is removed — so combined coverage never dips below 100% (a cross-fade would flash mid-fade).prefers-reduced-motionoverride, which OS-level animation toggles also flip).Testing
packages/sdkunit suite: 426 passed (structure, scene URL building + normalization + 404 path, panel luminance palettes, loader tint precedence incl. empty-string fallthrough, container-query breakpoints, prefetch dedupe + bounded eviction)..perspective-loadingroot class (preserved) detaching at handoff.Requires (app-side, graceful until then)
GET {host}/interview/{slug}/scene-image— 302 to the versioned CDN asset (short-TTL redirect, immutable asset), 404 when the research has no scene. Until it ships, the loading state simply shows the solid background + card + loader.🤖 Generated with Claude Code