Skip to content

Motion & B8 performance: jank scoreboard, transition gating, immersive hero, nav-drawer perf - #4

Open
aa-hh wants to merge 29 commits into
mainfrom
motion-aliveness
Open

Motion & B8 performance: jank scoreboard, transition gating, immersive hero, nav-drawer perf#4
aa-hh wants to merge 29 commits into
mainfrom
motion-aliveness

Conversation

@aa-hh

@aa-hh aa-hh commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

A full pass on making the LG B8 (webOS 4 / Chromium 53) feel snappy with real motion, validated against on-device frame data rather than feel — plus a new layered immersive home hero.

Instrumentation (foundation for everything else):

  • jank:navigation/jank:sidebar/jank:rail-scroll — rAF frame-gap scoreboards per interaction (frames/dropped/frozen/worstMs/busyMs)
  • Split animationTiming into startDelayMs (main-thread congestion) vs runMs (actual animation performance)
  • Fixed webOS4 motion-tier misclassification (B8's firmware versionMajor mirrors LG's build number, not the webOS platform version — now trusts sdkVersion via luna)

Transition choreography ("one thing at a time"):

  • transitionGate.js: protected window per navigation + D-pad rail glide; poster decode, backdrop loads, and metadata prefetch all queue behind it instead of competing with the enter fade
  • Heavy passes chunked (home: one row per macrotask; detail: episodes in 250ms batches) instead of one synchronous freeze
  • Paced idle-queue drain (~3 frames apart) so decodes can't batch into one raster frame; no-op drops (stale/hidden work) chain unpaced so they don't tax the queue

Detail screen backdrop — killed a 1200ms decode:

  • The old "ultrablur" backdrop fetched 4 corner colors from Plex, then downloaded a server-rendered JPEG of the same gradient + noise dithering (1212ms synchronous decode on the B8) for zero visible gain over CSS. Replaced with colorWash.js: native radial-gradients + a tiny tiled noise PNG, built entirely from the colors already fetched — same look, ~0ms.
  • Colors fetch now starts immediately on entry (network work overlaps the fade instead of queuing behind it)

Layered immersive home hero:

  • Sparse/empty homes (Jellyfin, new users) no longer look like flat dark nothing behind a small corner thumbnail — the whole screen now tints to the focused title's palette (ambient wash), crossfading between titles
  • Colors sourced from the same Plex endpoint the detail backdrop uses (proven on-device); a client-side canvas sampler was attempted but hit a file:// cross-origin wall on the B8 and is parked for a future CORS-validated revival
  • A soft full-bleed art layer was built and shipped disabled (il--no-bleed) — read as a duplicated poster echo rather than ambient glow; the raw 720→1920 upscale isn't soft enough. Re-enable only with a real blur source.
  • Fixed an edge-melt regression along the way: the crisp corner box used to fade into a painted flat color, which only worked when the surroundings were guaranteed flat — now the image itself fades via a CSS mask, so it's correct regardless of the ambient tint underneath

Nav drawer perf:

  • Removed the width transition (a layout property, measured 323–723ms of dropped/frozen frames per expand/collapse) — width now snaps in one reflow, and the drawer's felt motion is a compositor-only label/brand opacity+transform cascade instead

Process note: several pieces here (colorWash, palette sampler, nav-drawer snap+cascade) were built by parallel background agents against a shared plan doc (docs/plans/2026-07-04-b8-snappy-agent-plan.md), then reviewed and merged — two review passes caught a shipping-breaking CSS bug (a background-shorthand value that would have silently blanked the backdrop) and a broken stacking context (positive z-index veiling all home content) before they reached the device.

Test plan

  • npm test green throughout (693 tests: eslint no-undef, Chrome53 CSS-compat lint, unit tests)
  • Validated on real B8 hardware after every phase via remote tv.log scoreboard (not simulator-only)
  • jank:sidebar worstMs 323–723 → confirmed improved after nav-drawer fix
  • jank:navigation (detail) worstMs 1212+ → confirmed improved after backdrop fix
  • jank:rail-scroll → confirmed improved after transition-gate fix
  • Final drive-through of the immersive hero fix (mask edge-melt) pending — deployed to simulator/B8, awaiting visual confirmation

🤖 Generated with Claude Code

aa-hh and others added 29 commits July 4, 2026 03:00
…oss-screen fade-through, caps-motion-rich tier)

- Add Material 3 easing/duration tokens to :root; migrate the deprecated M2
  curve cubic-bezier(0.4,0,0.2,1) + bare ease (sheets/drawers -> emphasized-
  decelerate, scrim/sidebar-width -> standard).
- Staggered screen-enter reveal: home-feed rows rise+fade as they mount
  (homeScreen.js applyRowEnterStagger + .row-section--enter), capped <=240ms.
- Cross-screen fade-through: fresh screen-host fades in (router.js), Back snaps.
- New caps-motion-rich tier (webOS5+/sim/dev): focused media-card lift +
  elevation shadow the B8 skips. app.js applyMotionCapabilityClass emits both.
- Registry: Motion foundation section (tokens, tiers, pattern map).

Chrome53/webOS4-safe: transform/opacity only, no JS/rAF springs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New src/perf/animationTiming.js: timeAnimation(el, label, data) times the
  ACTUAL animationend wall-clock duration (not the declared CSS duration), so
  B8 compositor/decode contention shows up as real elapsed time. Reports via
  both the in-memory perf-mark buffer and the remote tvLog sink.
- router.js: times the cross-screen fade (anim:screen-enter-fade); adds
  screen:contentPainted (waits for in-DOM <img>s to finish loading, bounded
  4s) to separate 'DOM built' from 'posters actually decoded'; mirrors
  route:render / screen:firstPaint to tvLog so they reach tv.log on-device.
- homeScreen.js: times the home-feed row-stagger cascade's completion via the
  last row's animationend (anim:row-stagger-complete).
- Registry: Motion instrumentation note.

All self-gate on existing flags (?perf=1 / plax_perf_enabled + debug/logSink),
so cost is near-zero when disabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
version-gate/motion-capability currently resolve major/osMajor=5 on hardware
the engine diagnostic (from Plex client identity, a separate source) reports
as webOS 4/Chrome 53 — same failure class as the historical
caps-motion-gate-bug (firmware build number mistaken for webOS platform
major). console.info is unreliable on-device, so mirror versionMajor/
platformVersionMajor/sdkVersion/version/firmwareVersion/platformVersion/
modelName to the tvLog sink to see exactly which field is wrong on next boot.

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

Remote diagnostics (device-info-raw in tv.log) from a real 2018 LG B8
(modelName OLED55B8LLA) proved deviceInfo().versionMajor itself reports 5 on
real hardware -- it mirrors LG's firmware build number (version:'05.50.70'),
not the actual webOS platform major. strictWebosMajor() previously trusted
versionMajor/platformVersionMajor exclusively, believing them distinct from
firmware fields -- wrong, and it silently turned caps-motion-rich ON for a B8.

- New src/platform/webosSdkVersion.js: dependency-free fetchSdkVersion +
  parseWebOSVersionMajor, shared by webos.js and versionGate.js without
  creating a circular module graph (moved out of webos.js, which versionGate
  would otherwise have had to import back into, closing a cycle through
  clientIdentity.js).
- versionGate.js: fetches sdkVersion via the same authoritative luna
  getSystemInfo call webos.js already trusts for playback capability
  detection (bounded 1200ms timeout), and stamps device.sdkVersionMajor before
  resolving the gate.
- app.js strictWebosMajor(): prefers device.sdkVersionMajor when present;
  falls back to the old versionMajor/platformVersionMajor scan only if the
  sdkVersion fetch truly failed (simulator/dev-browser edge cases).

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

Real-device measurement (anim:screen-enter-fade in tv.log) showed a B8
consistently taking 372-408ms wall-clock for a fade declared at 200ms — even
on profile-picker (24ms build, 0 images), leaving ~150-200ms unaccounted for
by build time + declared duration. That gap matches Chromium 53's cost to
synchronously allocate a new compositor layer the first time an element's
animation kicks in.

- app.css: will-change: opacity on .screen-host--enter, pre-promoting the
  layer before the animation starts instead of during it.
- router.js: strips the screen-host--enter class on animationend so a
  retained host (which can live the rest of the session) doesn't keep an
  unnecessary compositor layer promoted indefinitely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n + jank scoreboard

Real-B8 finding: the enter fade (declared 200ms) measured 372-1695ms wall-
clock, scaling with screen weight — the animations were compositor-fine; the
MAIN THREAD was congested by work each screen build kicked off alongside them
(poster/hero JPEG decode, JSON prefetch), which is also remote-input dead
time. Fix is scheduling, not styling: one thing at a time.

- New src/ui/transitionGate.js: beginTransition/endTransition/onIdle. Router
  opens a 400ms protected window per fresh navigation (closed early on fade
  animationend); homeScreen opens a 550ms window per row cascade (closed on
  the last row's animationend).
- posterImages.js: single choke point in bindPosterImage defers ALL poster
  binds to the window's close — placed after the already-bound/already-loading
  guards (retained posters still reveal synchronously) and before any slot
  bookkeeping (a deferred bind can never hold or leak a concurrency slot).
- detailScreen.js: ultrablur backdrop fetch+decode deferred behind the gate
  (bgTok keeps the deferred run harmless after re-navigation).
- homeScreen.js: metadata/library prefetch deferred behind the cascade.
- Elevation for the B8 baseline: card focus now lifts translateY(-4px) and
  casts a shadow pre-rastered ONCE on an empty ::after, with focus animating
  only its OPACITY — never box-shadow (Chrome 53 re-rasters animated blur
  every frame). Supersedes the rich-tier-only static lift-shadow.
- Instrumentation: animationTiming now splits startDelayMs (creation →
  animationstart = congestion/input-dead-time proxy) from runMs; new
  src/perf/frameJank.js samples rAF gaps for 1.5s per navigation and reports
  {frames, dropped, frozen, worstMs, busyMs} as jank:navigation — the
  scoreboard every future motion change gets judged against.
- Registry: Motion section updated (choreography, elevation pattern, jank
  scoreboard); media-card focus spec updated.

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

First on-device scoreboard run showed the gate fixed WHEN, not HOW MUCH:
home froze ~1.1s building 3 rows synchronously (3 rAF frames in 1.5s window,
fade startDelayMs 1166) and detail hit a 1950ms worst gap after the gate
closed. This round spreads the work itself:

- homeScreen: render ONE row per macrotask — first row renders synchronously,
  the rest self-schedule through the existing append path (render-token
  aborts on refresh). Frames and remote input interleave between rows; the
  row-enter cascade now comes from real mount spacing. Every pass extends the
  transition window; only the final pass hooks the early close so an earlier
  row's animationend can't drain the poster queue mid-cascade.
- detailScreen: hydrate episode thumbs 6-per-250ms instead of a whole season
  in one burst (decode is synchronous-with-raster on Chrome 53); guards abort
  on season switch. Breadcrumbs (detail:backdrop-swap,
  detail:episodes-appended) bracket the suspects on the tv.log timeline.
- frameJank: worstEndedAgoMs places the worst gap's end on the log timeline
  for attribution against those breadcrumbs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in the Android TV nav-drawer sizing/scrim/brand fixes (commits
5fb6694 + 6703222). Conflicts resolved by keeping our one-row-per-macrotask
home render + pre-rastered ::after shadow while adding main's scrim CSS,
improved modal timing (emphasized-decelerate/short4), and fuller Foundations
section in the design registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The new Android-TV nav drawer animates `width` (a layout property) over
180ms and reveals 8 labels (display:none→block) on focusin — the one motion
in the app that deliberately breaks the transform/opacity-only rule. It was
uninstrumented, so we had no on-device frame data to confirm the "absolute
overlay = cheap reflow" assumption in the CSS holds on the B8.

Fire sampleFrames('jank:sidebar', {dir}) from syncExpanded on a real
expanded-state change (700ms window). Self-gates like every other sampler
(no-op unless perf/debug is on); a navigation right after supersedes it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
We measured focus-commit LATENCY (input:keydown/input:focusCommitted) but
not the SMOOTHNESS of the glide that follows — smoothScrollCarousel/Vertical
write scrollLeft/scrollTop every frame for 150ms (a per-frame layout on
Chrome 53), and whether that holds 60fps is the "does moving along the rail
feel smooth" question the latency marks can't answer.

Fire sampleFrames('jank:rail-scroll', {axis}) from a throttled sampleGlide()
at each glide start (500ms window, one per burst so a held arrow reports one
number not dozens). Dual-gated like the other samplers, so unlike the
perfMark-only input:* marks it reaches tv.log in a plain debug session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ro, drawer snap

Self-contained execution spec for sequential Opus/Sonnet agents: ground rules
(worktree isolation, no-stash, chrome53 constraints, registry protocol, no
deploys), measured B8 baselines + targets per channel, and per-phase specs
including the canvas-taint landmine (file:// → blob XHR) and the one-decode-
three-uses hero architecture. Parent session validates on-device between phases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…er-frame reflow

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

The detail-screen backdrop fetched 4 corner colors from PMS, painted a CSS
gradient, then "upgraded" to a server-rendered 1280x720 JPEG that was just
that same gradient plus noise dithering — its synchronous decode measured
1212ms on a real B8. Both ingredients (corner-color blending, noise
dithering) are reproducible natively via CSS: src/ui/colorWash.js builds 4
corner-anchored radial-gradient layers plus a tiled noise-PNG layer
(generated once by scripts/gen-noise-png.cjs, hand-built PNG chunks, no new
dep) and detailScreen.js paints them the instant PMS colors arrive, deleting
the new Image()/decode/swap branch entirely. ultrablur.js drops the
now-unused image-URL builders; loadUltraBlurBackdrop keeps returning
{colors} only (grep-confirmed no other callers depended on imageUrl). The
detail:backdrop-swap breadcrumb still fires at wash-apply time so the
jank-attribution timeline is unbroken.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
#	docs/design-system/component-registry.md
…circle

Two defects caught reviewing the Wave-1 agent branches against spec:

1. colorWash NOISE_TILE_URL baked ` repeat` into the constant — that's
   background-SHORTHAND grammar; inside background-image it invalidates the
   ENTIRE declaration, so the detail backdrop would have silently painted
   nothing (flat corner color only). Stripped; tiling was already correctly
   handled by detailScreen's per-layer background-repeat. Test hardened to
   anchor the constant at both ends so this class of bug is caught.

2. The drawer's take-3 fixed-256px-item geometry painted the collapsed
   .active pill (item background, 999px stadium) as a flat-clipped bar at
   the 96px rail edge — not the registry's 72×72 circle. Collapsed items now
   SNAP to the 72px content box (no transition; same single reflow frame as
   the host snap, zero per-frame layout) restoring the circle. Icon x stays
   28px in both states. Registry pass-3 claims corrected to pass 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ero swap

Wave-1 drive session showed detail STILL freezing 1.2-1.7s LATE
(worstEndedAgoMs:0) despite the wash killing the JPEG: the FIFO idle queue
drained home's now-hidden poster binds AHEAD of detail's own images, and the
~4ms setTimeout(0) chain let their decodes batch into one raster frame.

- posterImages: deferred binds re-check at drain time, drop when the img's
  host is hidden (offsetParent null); recovered via onResume re-prime
- homeScreen: onResume primes visible posters; onSuspend cancels the pending/
  in-flight 720px hero art swap so it can't decode behind detail
- transitionGate: drain ticks paced 48ms (~3 frames) apart so the compositor
  gets frames between decodes; gate tests widened for the new tick
- focus: NAV_SCROLL_MS 150ms -> 120ms (user-felt "rail movement could be
  faster")

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…breadcrumb

Round-2 data: jank fixed (detail worstMs 1677->327) but the backdrop swap
measured 6s after entry — the user's "incredibly slow to load" is CONTENT
latency, not jank. Two mechanical causes:

- every DROPPED stale bind still cost a full 48ms drain tick; dozens per
  scroll burst = seconds of queue delay ahead of detail's own deferred work.
  Drain callbacks may now return false ("no work done") to chain unpaced;
  real work keeps the ~3-frame spacing
- already-queued poster load jobs kept pumping after navigation, burning the
  6-per-host connections against detail's metadata fetch; the pump now drops
  hidden imgs (recovered by onResume re-prime, same as deferred binds)

New detail:content-rendered {sinceBuildMs} breadcrumb measures skeleton-to-
content so content latency is attributable separately from jank. Cast & crew
kept — the data does not implicate it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
detail:content-rendered measured 431ms (content path fixed) but the wash
still landed 2.6s after entry: gate-wait (~0.8s) + PMS's on-demand
/services/ultrablur/colors analysis (~1.8s), serialized. The onIdle wrapper
around the FETCH was a JPEG-decode-era leftover — the fetch is network work
and must overlap the enter fade, not queue behind it. Only the paint still
respects the gate (one 1080p gradient raster mid-fade could hitch the B8).

Cache-hit colors now paint at fade end; cache-miss floor is the PMS round
trip alone. Phase 3's client-side palette sampling kills that dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p subject

Three full-screen layers behind the home content (ambient corner-wash <
soft full-bleed art < crisp top-right box), cheapest first, color-leads.
ONE art fetch + ONE decode per focus-settle reused three ways via new
src/ui/palette.js (blob-XHR → same-origin objectURL → 8x8 canvas corner-
average, dodging the file:// canvas-taint). LRU (max 12, revoke-on-evict/
destroy). Bleed is html.caps-motion-gated + il--no-bleed kill-switchable.
Opacity-only crossfades; onSuspend token-guard keeps nothing decoding behind
a covering screen. New home:hero-swap {cached} breadcrumb. Backend-agnostic.

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

Two defects caught reviewing the Phase 3 branch against spec:

1. Stacking: the bleed + scrim shipped at z-index:1 with no stacking context
   and no feed z — per CSS2.1 App. E, positive-z positioned layers paint
   ABOVE non-positioned inline content, so the 0.45-opacity art + 0.55-0.85
   scrim would have veiled the entire home UI the moment a hero loaded.
   .home-layout is now an explicit stacking context (position:relative +
   z-index:0) and the layers use NEGATIVE z (-3/-2/-1): above the body's
   surface-dim, categorically under every piece of home content.

2. Kill-switch: the swap writes bleed/scrim opacity + background-image
   INLINE, which beats plain class rules — the il--no-bleed kill-switch and
   the html:not(.caps-motion) gate both silently lost. The suppress rules
   now carry !important, making the one-line disable real.

Registry anatomy corrected to match (the recorded z-order was the broken one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ro band

home:hero-swap fired every focus (code path fine) but the background never
changed: the immersive layers sit at negative z BEHIND the home content, and
.il-hero painted an OPAQUE surface-dim across the top 540px — occluding the
ambient wash + bleed exactly in the hero region. .il-hero is now transparent;
the ambient wash provides the neutral base it used to, and body surface-dim
shows through before first focus (cold-load look unchanged). The other home
wrappers were already transparent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e blob-XHR

"Focus does nothing" persisted after the z-index/transparent fixes because
the real failure was upstream: Phase 3 rewired the crisp corner box (and the
bleed) onto palette.js's cross-origin blob-XHR objectURL, which produces null
on the B8, blanking the WHOLE hero — including the box that worked fine before
Phase 3 via a plain <img>.

Decouple the visible layers from the fragile path:
- crisp box + bleed: plain art URL as CSS background, <img>-preloaded (display
  only, no CORS/canvas — the pre-Phase-3 mechanism)
- ambient wash colors: PMS /services/ultrablur/colors via loadUltraBlurBackdrop
  (same as the detail backdrop, proven on-device; home already prefetches it
  on focus so the settle is usually cache-warm)

palette.js + tests are parked (unused) for a future CORS-validated revival.
New home:hero-swap {colorsFrom} breadcrumb reports whether the wash resolved.

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

On-device verdict: the soft full-bleed layer reads as a duplicated poster
behind the crisp corner box (720->1920 upscale is not soft enough to register
as ambient glow). The immersive response is carried by the ambient color wash
+ crisp subject. Kill-switch was built for exactly this call; re-enable only
with a real blur source, never the raw upscale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…matching color

On-device screenshot showed the crisp corner box ending in a hard, unblended
edge. Root cause: the box's own edge-melt relied on painting a hardcoded
var(--surface-dim) overlay, assuming .il-hero's surrounding area was always
that same flat color -- true before this phase, false now that .il-hero is
transparent (to let the ambient wash show through). The painted color and
the true (ambient-tinted) background no longer match, producing a visible
seam exactly at the box boundary.

Fix: fade the backdrop IMAGE's own opacity via -webkit-mask-image (two
intersecting directional gradients, destination-in composite -- old WebKit,
Chrome53-safe) instead of painting a color. The scrim now only adds light
text-legibility darkening and fades to true transparency. Correct regardless
of active theme or ambient tint, since nothing is hardcoded to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…b field name

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ash + real seam/scrim fixes

Undoes the plan-implementation detour that made the hero/ambient seam and
sidebar scrim worse: drops the Plex-only loadUltraBlurBackdrop path in
homeScreen and reinstates the backend-agnostic loadAmbientColors contract
(Plex → ultrablur, Jellyfin → BlurHash corners) so both providers get a
color-matched wash again.

Also lands the actual seam/scrim fixes evidenced by on-device logging:
- Longer, edge-exact backdrop mask + softened bleed-scrim gradients so the
  hero photo never ends in a hard rectangle against the ambient wash.
- Ambient/bleed/bleed-scrim layers use negative safe-area inset instead of
  100vw + transform, avoiding the compositor stall that froze the sidebar
  expand on the B8.
- Collapsed nav rail goes transparent (translucent per-icon discs for
  contrast) so it no longer double-darkens under the ambient wash, with the
  ::before scrim fade gated to the rich motion tier only.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two on-device issues after the Cursor round:

1. Drawer scrim landed ~3s after expand: an opacity-0 layer is not kept
   rastered, so the 50vw multi-stop gradient (~1M px) rasterized FROM SCRATCH
   on first expand, queued behind ambient-wash raster work. The visual job was
   only ever the soft falloff at the drawer edge, so paint only that: a 140px
   feather strip at left:var(--nav-rail-w-expanded) (~8x fewer pixels,
   rasters within the expand frame). Half-screen dim dropped by design.

2. Hero art still met the ambient wash at a clear border: transparency-only
   melts hand dark photo pixels to an averaged wash — a hue/luminance cliff no
   alpha ramp fixes. New ilTintHeroScrim rebuilds .il-hero__scrim inline per
   swap from the item's own topRight palette color, rising over the same band
   the mask fades the photo out: ghosting hides under the tint and hue stays
   continuous into the wash's top-right radial. One string per swap, no new
   layers; static CSS scrim remains the pre-colors default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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