Skip to content

feat(pixel-loader): productionize swc-pixel-loader - #6605

Open
aramos-adobe wants to merge 25 commits into
mainfrom
aramos-adobe/feat-pixel-loader-swc-2477
Open

feat(pixel-loader): productionize swc-pixel-loader#6605
aramos-adobe wants to merge 25 commits into
mainfrom
aramos-adobe/feat-pixel-loader-swc-2477

Conversation

@aramos-adobe

@aramos-adobe aramos-adobe commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer context: productionization summary

This PR stems fromjnjosh/pixel-loader (the prototype) and hardens swc-pixel-loader for production. Changes on top of that base:

  • Removed prototype framing across the component JSDoc, data.ts, animation.ts, geometry.ts, the stories meta, and the MDX (no more "prototype shell" language or internal design-spec section references).
  • size is no longer a public attribute. Sizing is consumer-controlled via the --swc-pixel-loader-size CSS custom property (default 56px). Design has no size model yet; a typed s/m/l enum can be layered on non-breakingly later on top of the same property.
  • Fixed a paused / reduced-motion bug: the preset ticker kept cycling icons while paused or under reduced motion. It now freezes on one icon (_isStatic gate + re-sync on paused).
  • Reduced-motion is now live: a matchMedia change listener re-evaluates when the OS setting toggles while the loader is mounted.
  • Documented both CSS custom properties (--swc-pixel-loader-size, --swc-pixel-loader-color) via @cssprop so they surface in the API table, plus a Sizing note in the MDX.
  • Accessibility: replaced the placeholder a11y spec with a real toMatchAriaSnapshot for the indeterminate progressbar.
  • Tests: added unit coverage for defaults, icon reflection, preset overriding icon, the progressbar/aria-label wiring, the paused static render (ticker regression guard), and the reduced-motion live toggle. 5/5 passing.

Still open (not in this PR): dedicated behavior/preset stories, and design sign-off on the final icon set / preset names / eventual size scale.


Description

Productionizes the prototype swc-pixel-loader from the jnjosh/pixel-loader base branch. This PR stacks on that branch and covers the engineering hardening needed before the Prompt field generating-state branding can depend on the loader.

Changes:

  • Removed all "prototype" framing. Reworded the component JSDoc, data.ts, animation.ts, geometry.ts, the stories meta, and the per-unit MDX so nothing reads as a "prototype shell" or cites internal design-spec section numbers a reader cannot access.
  • Made size non-public. Dropped the freeform size="56px" attribute. Sizing is now driven entirely by the --swc-pixel-loader-size custom property, which consumers override in their own CSS (for example, response-status will set it in a later project). Design does not yet have a size model, so a typed s/m/l enum would mean inventing values; keeping size off the public attribute surface avoids shipping an attribute that a future size enum would collide with. The enum can be layered on non-breakingly later, on top of the same custom property.
  • Fixed the paused / reduced-motion ticker bug. The preset ticker (setInterval) previously kept running while paused or under prefers-reduced-motion, so a "frozen" preset still swapped icons on a timer. Added a single _isStatic source of truth, gated _syncTicker() on it, and re-synced the ticker when paused changes. A paused or reduced-motion loader now holds one icon with no running animations.
  • Replaced the placeholder a11y spec with a real toMatchAriaSnapshot assertion for the indeterminate progressbar and its accessible name.
  • Added unit tests covering defaults, icon reflection, preset overriding icon, the progressbar/aria-label wiring, and the paused static render (including a regression guard that a paused preset runs no animations). All four test stories pass.

Motivation and context

The pixel loader shipped on jnjosh/pixel-loader as an explicitly-marked prototype shell. It needs to be productionized before the Prompt field "generating" branding can consume it as the leading AI icon. This is part of the Conversational AI release and is tracked as a P0.

References

RSP Code
Demo

Related issue(s)

  • Productionizes the prototype from the jnjosh/pixel-loader branch.
  • fixes SWC-2477

Screenshots (if appropriate)

Screen.Recording.2026-08-14.at.6.01.25.PM.mov
Screenshot 2026-08-14 at 6 02 41 PM

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published. (Deferred: this PR targets the jnjosh/pixel-loader feature branch, not main; a changeset will be added when the stack targets main.)
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Icons and presets render and animate

    1. In Storybook, open Conversational AI / Pixel loader.
    2. In the Playground, cycle the icon control through several values and set preset to cc, dc, exp, analyze, and mega.
    3. Expect each icon/preset to assemble, hold, and disassemble; presets cycle one icon per loop.
  • Paused and reduced-motion render statically

    1. Open the States story (or set paused in the Playground).
    2. Expect the paused loader to hold the fully-settled icon with no animation.
    3. With a preset set and paused on, confirm the icon does not change over time (the ticker is suppressed).
    4. Enable OS "reduce motion" and reload; expect the loader to render the settled icon with no animation.
  • Size override via CSS custom property

    1. Wrap a loader with a style such as swc-pixel-loader { --swc-pixel-loader-size: 24px; }.
    2. Expect the loader to render at the overridden size (there is no size attribute).

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps.

  • Keyboard (required — document steps below)

    1. In Storybook, open Conversational AI / Pixel loader and Tab through the page.
    2. Confirm the loader itself is not a tab stop and introduces no focus trap.
    3. Expect focus order in surrounding examples to be unaffected (no regressions).
  • Screen reader (required — document steps below)

    1. With VoiceOver (or NVDA) active, navigate to the loader.
    2. Expect it to be announced as a progress indicator using its label as the accessible name (default "Loading"; set a specific label such as "Generating response").
    3. Confirm no aria-valuenow/percentage is announced (progress is indeterminate) and there are no duplicate or noisy announcements from the animation.

@aramos-adobe
aramos-adobe requested a review from a team as a code owner August 11, 2026 20:10
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3904981

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@aramos-adobe aramos-adobe self-assigned this Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6605

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@aramos-adobe aramos-adobe changed the title feat(pixel-icons): adding pixel loader icons to convoAI feat(pixel-loader): productionize swc-pixel-loader Aug 11, 2026
@aramos-adobe aramos-adobe added the skip_vrt Skip VRT build; mark UI Tests green without running Chromatic label Aug 11, 2026
@aramos-adobe aramos-adobe added the Status:Ready for review PR ready for review or re-review. label Aug 11, 2026
@5t3ph 5t3ph self-assigned this Aug 12, 2026

@5t3ph 5t3ph left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work on this!

Two bugs, and some suggestions for further polish.

Comment thread 2nd-gen/packages/swc/patterns/conversational-ai/pixel-loader/animation.ts Outdated
Comment thread 2nd-gen/packages/swc/patterns/conversational-ai/pixel-loader/PixelLoader.ts Outdated
Comment thread 2nd-gen/packages/swc/patterns/conversational-ai/pixel-loader/PixelLoader.ts Outdated
Comment thread 2nd-gen/packages/swc/patterns/conversational-ai/pixel-loader/PixelLoader.ts Outdated
@aramos-adobe
aramos-adobe requested a review from 5t3ph August 12, 2026 19:35
@aramos-adobe aramos-adobe added the High priority PR review PR is a high priority and should be reviewed ASAP label Aug 14, 2026

@5t3ph 5t3ph left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Latest animation looks nice!

See updated comment reply re: reduced motion.

Also, the icons are squared off again, but the demo you linked still shows the rounded appearance.

@aramos-adobe
aramos-adobe requested a review from 5t3ph August 17, 2026 18:48
@aramos-adobe
aramos-adobe changed the base branch from jnjosh/pixel-loader to main August 17, 2026 18:59

@5t3ph 5t3ph left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work on this! :shipit:

@miwha-adobe miwha-adobe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we include dev warnings + tests for the icon / preset fallback? Right now an invalid value silently renders aiLogo / drops the preset with no warning. We have shared helpers in core/utils/dev-validation.ts (validateEnum / warnIf) that should make this pretty straightforward.

import type { Cell } from './data.js';

const FPS = 30;
const ms2f = (ms: number): number => (ms * FPS) / 1000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: This variable name was not super clear to me. Maybe something like msToFrames

@aramos-adobe

Copy link
Copy Markdown
Contributor Author

@miwha-adobe How's this?

preset is optional. If someone enters an invalid preset name it'll just render the aiLogo as a single icon and no preset

Screenshot 2026-08-17 at 5 16 04 PM Screenshot 2026-08-17 at 5 15 18 PM

@rubencarvalho rubencarvalho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work! I think it would make sense making this internal, though. We don't expect (nor want!) consumers to reach out to the pixel loader directly. We will compose it in our Prompt field and eventually Response status.

// Cached shadow-root lookups. The container and cell elements are stable
// between renders, so query them once and reuse across `_playCells` /
// `_finishThenSwap` calls (including the render-less reduced-motion and pause
// paths). Invalidated in `updated` since a new icon/preset re-renders cells.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i could confirm there is a small bug here: on reconnect this only re-arms the ticker, but disconnectedCallback cancelled the cell animations via _cancelAnimations(). Since no property changes, Lit doesn't re-render and _playCells() never runs again. if we have a single-icon loader (no preset) that gets moved in the DOM (e.g. if we use something like appendChild, tab switch, maybe virtualization) it comes back frozen (i created a reproducible and confirmed it). if we add a this._playCells() here to mirror _cancelAnimations() on disconnect, it should fix it.

@state()
private _displayedIcon: PixelLoaderIconName = 'aiLogo';

/** Guards stale finish-then-swap completions against a newer change. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this _syncTicker() runs on mount, then the first updated() sees preset in changedProperties and immediately clears + recreates the interval. So we tear down/rebuild the timer twice on every mount.

? this.preset
: undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmmm, is this JSDoc here correct? shouldn't it be on _finishThenSwap() (below?)

aramos-adobe and others added 7 commits August 18, 2026 15:23
Match the React Spectrum loader corner rule: a cell corner rounds only when its
horizontal, vertical, and diagonal neighbors are all empty. Previously a corner
rounded whenever the two orthogonal neighbors were empty, so diagonally touching
cells rounded corners that should stay square.

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

@rubencarvalho rubencarvalho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚢

}

if (changed.has('preset')) {
this._presetIndex = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a bit of an edge/corner case but this reset runs in updated(), i.e. after render() has already drawn cells using the old _presetIndex. If you swap from one preset to another after it's auto-cycled (say preset A is at _presetIndex=3, then preset='B' is set), render() draws B[3]'s cells, then this resets to 0 and the synchronous _playCells() below queries the B[3] DOM cells but reads B[0]'s cell list. in practice, it's just that the wrong glyph animates for a frame, and if the counts differ the surplus cells flash opaque.
tl;dr is that moving this reset up into willUpdate (before render) should fix it

@miwha-adobe miwha-adobe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component:ConversationalAI High priority PR review PR is a high priority and should be reviewed ASAP skip_vrt Skip VRT build; mark UI Tests green without running Chromatic Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants