Commit Graph

4746 Commits

Author SHA1 Message Date
Georges-Antoine Assi
111fc969be run fmt 2026-06-22 19:40:56 -04:00
Claude
d7c5ab7e06 feat(v2): RBox3D faces right, self-spins, and coasts on flick
- Default orientation now faces right (initialYaw flipped).
- Idle auto-spin actually resumes: the time-based check moved into the RAF
  loop, since a computed reading performance.now() cached and never restarted
  after the first interaction. Quiet window is 2s.
- Flick momentum without a JS decay loop: the release velocity is handed to
  the box as one extra rotation and a CSS ease-out curve coasts it to a stop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 23:23:37 +00:00
Claude
1b02bf6a3e fix(v2): RBox3D adopts box-2D dimensions even when cached
Read the front (box-2D) and spine image natural ratios on mount, not only on
the load event — a cached cover decodes before the listener binds, which left
the box stuck at the default ratio instead of the real box-2D proportions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 22:17:22 +00:00
Claude
ab2b61783b fix(v2): RBox3D rotates on drag instead of selecting faces
Add user-select: none to the box and pointer-events: none to the faces so a
drag always lands on the root (which owns the rotation listeners) and never
starts a text / image selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 22:12:48 +00:00
Georges-Antoine Assi
d38be187f3 Merge branch 'master' into claude/youthful-wozniak-ocjhc0 2026-06-22 18:04:37 -04:00
Georges-Antoine Assi
c4bfbac872 Merge branch 'master' into v2-ui-tweaks-gg 2026-06-22 17:45:59 -04:00
Claude
77980a79ec feat(v2): interactive 3D box art on game detail hero
Adds an RBox3D primitive that builds a rotatable, fake-3D game box from
three flat ScreenScraper scans (front, back, spine) using CSS 3D
transforms. Box proportions derive from the images themselves; it rotates
via pointer drag, arrow keys / gamepad D-pad, and the right analog stick,
drifts gently when idle, and honours prefers-reduced-motion.

The game detail hero (CoverColumn) upgrades to the spinning box when the
"3D box" boxart style is selected and the rom has the full set of faces,
falling back to the flat cover otherwise.

Backend: persist the box-2D-side (spine) scan locally, mirroring the
existing box-2D-back handling — new BOX2D_SIDE media type + box2d_side_path
on ss_metadata, opt-in via scan.media.

- RBox3D primitive + Storybook story (controls + keyboard-rotation play())
- useBoxFaces composable resolving the three faces + a `complete` gate
- box3d-alt i18n key across all locales
- backend BOX2D_SIDE persistence + tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019itLXRfJXGGbhPY3JyqnuN
2026-06-22 21:45:39 +00:00
Georges-Antoine Assi
af4b057894 feat(v2): screenshot-forward covers + cover-art PIP for continue-playing & activity
Show a "where you left off" screenshot on the Home continue-playing rail and
the live-activity board, with a small cover-art thumbnail (PIP) in the corner
so the game stays identifiable. Both render at the image's natural aspect.

Backend:
- New shared util `continue_playing_screenshot(rom, latest_save)` resolving the
  image in priority order: latest save's screenshot, then title screen, then
  first gameplay screenshot (None → frontend falls back to cover art).
- `SimpleRomSchema.screenshot_path` populated only on the `last_played` query;
  `get_latest_saves_for_roms` batch handler (+ tests).
- ActivityEntry / ActivityEntrySchema gain `screenshot_path`, computed from the
  session player's latest save in both the socket and REST heartbeat paths.

Frontend:
- New shared `CoverArtPip.vue` (bottom-right 2D cover thumbnail), reused by
  GameCard and ActivityCard.
- Home continue-playing rail uses `screenshot_path` + PIP, natural aspect (no
  forced hero/style).
- Activity board: screenshot-forward cover + PIP, and a wrapping flex layout so
  cards share a uniform height with natural-ratio widths (gallery-card
  behavior).
- GameCover only keys the measured ratio by rom id for the rom's own cover, so
  a `coverSrc` override (screenshot) never pollutes the gallery's ratio cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:40:56 -04:00
Georges-Antoine Assi
e471efb987 feat(v2): morph gallery cover into the player on Play
Clicking Play at the center of a gallery cover now runs the shared-element
view transition into the /ejs (EmulatorJS) or /ruffle hero, matching the
card → details morph.

- useGameActions gains an optional `coverEl` resolver; `play()` wraps the
  navigation in `morphTransition` (cover → player hero, same `rom-cover-<id>`
  tag the player paints statically) and awaits the push so the snapshot is
  taken after the player renders. GameCard supplies its GameCover box.
- The player heroes only seeded `rom` from `currentRom` (set via GameDetails),
  so a direct gallery→play left `rom` null and the `v-if`-gated hero never
  rendered — nothing to morph into. Seed a lightweight `heroSeed` SimpleRom
  from the gallery store (new `galleryRoms.getRomById`) so the cover paints
  its morph tag immediately; `rom` fills in on mount. Play is disabled until
  the full payload loads.
- Enable hover-motion on both player heroes so the cover spin / hover video
  work there too.
- Arcade systems (arcade / neogeoaes / neogeomvs) skip the cartridge slot-in
  animation (new `isArcadeSystem`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:46:31 -04:00
Georges-Antoine Assi
4a49a762a8 fix(v2): port cover animations to v1's compose-by-property model
Rework useCoverAnimation to mirror v1's useGameAnimation, which composes
two motions on different CSS properties so they don't fight: the spin
owns `transform: rotate` (set per-frame) while the disc/cartridge slot-in
owns `margin-top` (eased in CSS with an overshoot). A single transform for
both couldn't ease the slide independently of the per-frame spin.

- CD launch spins at max while sliding the cover down into the drive;
  cartridge launch seats fully into its bay (1/3 height).
- Drop the cartridge hover animation entirely — matches v1, which only
  spins discs and plays the hover video on hover.
- Enable hover-motion on the EmulatorJS and Ruffle player heroes so the
  hover spin / video work there too.
- Fix a `.ame-cover__img` typo (missing "g") that was silently breaking
  the cover image base styles, and ease `margin` in CSS on `.game-cover__img`
  so the slide composes with the bloom reveal instead of overriding it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:19:03 -04:00
zurdi
e232e9e00e test(v2): align cover-ratio debounce test with 350ms re-pack delay
The re-pack debounce in useGalleryCoverRatios was raised from 150ms to
350ms (c5d844e1) to avoid a re-pack storm during fast scroll, but the
test still advanced fake timers by 150ms and expected the debounced
ratioVersion bump to have fired — so it asserted 1 and got 0. Advance
by the new 350ms delay at all three sites. Pre-existing failure on
master, unrelated to this branch's changes; bundled here to unblock CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:09:13 +00:00
zurdi
8c20fd6547 feat(logging): enhance logging module with dynamic module name resolution and add module filtering options 2026-06-22 15:51:22 +00:00
Georges-Antoine Assi
7b8b35fbc1 fix miximage view preview 2026-06-22 10:06:03 -04:00
Georges-Antoine Assi
e10a715cf3 improve hotpath 2026-06-22 08:07:24 -04:00
Georges-Antoine Assi
76417cb4b1 Merge branch 'master' into v2-ui-tweaks-gg 2026-06-22 07:49:39 -04:00
Georges-Antoine Assi
367626c21e some UI tweaks 2026-06-22 07:48:52 -04:00
zurdi
c5d844e118 feat(v2): improve gallery responsiveness and optimize game actions handling 2026-06-22 11:36:39 +00:00
zurdi
8a4d286cfa feat(activity): add total sessions label to activity localization files
- Added "total-sessions" key to various language JSON files for activity localization.
- Updated ActivityCard component to reflect changes in the activity view.
- Enhanced Activity view to display a live session counter with a tooltip.
- Introduced virtual scroll debugging to monitor performance in the gallery.
- Adjusted layout styles for better responsiveness and visual consistency across components.
2026-06-22 09:23:39 +00:00
Georges-Antoine Assi
e7d383b285 style(v2): right-align list-view cover in its column
Cover hugs the title side of its fixed-width column (and the skeleton
matches), so the empty space sits on the left and the cover reads as
attached to its title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:43:59 -04:00
Georges-Antoine Assi
41ec6f4de6 fix(v2): give list-view cover its own column so titles align
With natural-aspect covers, the list-row cover lived inside the title cell
and its variable width pushed each row's title/meta to a different x. Add a
dedicated fixed-width (64px) cover column to the shared list grid template —
big enough for any portrait→square game cover — so the title column starts
at the same x on every row. Header and skeleton pick it up via the shared
column config; the cover renders left-aligned at its natural aspect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:33:09 -04:00
Georges-Antoine Assi
44016129b5 feat(v2): list-view covers respect natural aspect; scale placeholder title
- Gallery list view: drop the `fixed` opt-out on its GameCard so the
  row thumbnail renders at the cover's natural aspect (fixed height,
  natural width) like everywhere else. Removes the now-unused `fixed`
  prop / class / CSS branch from GameCard.
- CoverPlaceholder: the title now scales with the cover box (container
  query units, clamp(8px, 8cqmin, 18px), em padding) instead of a fixed
  12px — readable on a tiny list thumb, proportionate on the detail hero.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:09:30 -04:00
Georges-Antoine Assi
cdb1f17902 feat(v2): add section headings to HLTB strip and screenshots in overview
The overview's screenshots and How-Long-To-Beat blocks rendered with no
label, unlike the related-games sections. Wrap both in the same labelled
section pattern (renamed `overview-tab__related-*` → `__section-*` since it
now covers non-related blocks too). The HLTB heading is gated on a
`hasHltb` computed so it doesn't show when the strip is empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:11:33 -04:00
Georges-Antoine Assi
14a7b40475 details cleanup 2026-06-21 20:10:07 -04:00
Georges-Antoine Assi
72fb96ec4c fix(v2): consistent gap between cover and fields in the edit dialog
The edit dialog's hero used a fixed 240px cover column with the cover
centered, so a natural-width cover left variable leftover space — making
the gap to the fields vary by cover shape. Size the column to the cover
(`auto`) so the gap is exactly the grid gap for any cover, and give it a
touch more room (24px).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:57:00 -04:00
Georges-Antoine Assi
c8362ed22e feat(v2): use natural cover aspect ratio across remaining surfaces
Extend natural-aspect cover rendering beyond the gallery to the rest of
the app (continue-playing stays a fixed 16:9 hero, by request).

- GameCard size tiers now render fixed-height / natural-width like the
  default card (size just picks the height); covers EditRomDialog (lg)
  and ManageCollectionsDialog (xs) for free. Adds a `fixed` opt-out for
  dense aligned tables.
- GameListRow (compact list view) opts into `fixed` so its cover column
  stays uniform for row alignment.
- Compact fixed-column thumbnails (ScanPlatformRow, DeleteRomDialog,
  RefreshMetadataDialog, ActivityCard) stop cropping (object-fit
  cover → contain): the whole cover shows at its true aspect while the
  slot stays uniform.

Deliberately unchanged: collection mosaics (composite collage art),
save/state screenshots (16:9, not covers), and the provider cover-picker
comparison grids (uniform tiles aid side-by-side selection).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:46:01 -04:00
Georges-Antoine Assi
74ef594609 feat(v2): render the Random Pick cover at its native aspect ratio
Swap the hand-rolled RImg + useCoverArt cover resolution in
RandomPickWidget for the shared GameCover, which measures the image's
natural ratio. The thumbnail now renders at a fixed 70px height with
natural width (no crop), matching the gallery, and the widget's cover
plumbing (coverSrc / coverContain / placeholder fallback) collapses into
GameCover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:24:05 -04:00
Georges-Antoine Assi
662dc89a24 Merge branch 'master' into aspect-ratio-no-no 2026-06-21 19:20:09 -04:00
Georges-Antoine Assi
d1a9dbb4fb refactor(v2): extract gallery cover-ratio measurement into a composable
Move the measured-ratio map, debounced ratioVersion, onCardRatio handler,
and ratioAt resolver out of GalleryShell into useGalleryCoverRatios. The
composable owns its debounce-timer cleanup (onBeforeUnmount), so the shell
no longer tracks ratioBumpTimer. Behaviour is unchanged; adds a unit test
for the dedup / debounce / position→rom→ratio mapping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:10:36 -04:00
Georges-Antoine Assi
84ccba6996 Merge pull request #3308 from tmgast/feature/device-flow-authorization
Add device authorization flow for TV-app-style pairing (RFC 8628)
2026-06-21 19:07:08 -04:00
Georges-Antoine Assi
d5f30d1fab docs(v2): collapse verbose comment blocks from the aspect-ratio work
Tighten the multi-line comments added across GameCover/GameCard/
GalleryShell/useGalleryVirtualItems/useResponsiveColumns and the
packFlowRows test to one or two lines each, keeping the load-bearing
rationale and dropping the prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:06:23 -04:00
Georges-Antoine Assi
8f25796201 refactor(v2): drop unused rowIndex from gallery row item
Since rows became variable-length contiguous runs, rowIndex was hardcoded
to 0 at both build sites and read nowhere. Remove it so a future reader
doesn't mistake 0 for a meaningful index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:58:17 -04:00
Georges-Antoine Assi
fd7d40aa1a docs(v2): note the gallery ratio cache is intentionally unbounded
ratioByRomId is keyed by rom id so a re-visited platform/collection
re-packs instantly without re-waiting on image loads. It's never pruned,
but each entry is two numbers (a few hundred KB even at tens of thousands
of distinct ROMs), so an LRU ceiling isn't worth it. Document the choice
so it reads as deliberate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:55:33 -04:00
Georges-Antoine Assi
e490706028 fix(v2): pin gallery cards to flex-shrink: 0 so covers never crop
The flow-packer sizes a row from cardHeight * ratio (floating point) and
the browser rounds rendered widths, so a "just fits" row can land a hair
over the container. With shrink enabled, fixed-height cards absorb that by
narrowing — cropping the cover via object-fit. Pin shrink to 0 on the
row's children: trades the rare sub-pixel crop for a hair of ragged
overflow, and keeps loading skeletons (default shrink: 1) at packed width.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:54:35 -04:00
Georges-Antoine Assi
4724361f6d refactor: remove per-platform cover aspect-ratio setting
The platform aspect_ratio setting is dropped from the UI and the API
(platform update body + response schema) — nothing consumed it for
rendering, and covers now size to their image's natural aspect.

- SettingsTab: remove the cover-style / aspect-ratio picker (and its
  now-dead helpers, CSS, and unused imports); collapse to a single column.
- update_platform: drop the `aspect_ratio` body field; PlatformSchema no
  longer returns it; utils/platforms stops seeding the default.
- Regenerate the affected frontend types (PlatformSchema, update body).

The DB column stays (out of the update/response scope; dropping it would
be a separate destructive migration) but is no longer read or written
through the API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:43:40 -04:00
Georges-Antoine Assi
389025942f changes from bot review 2026-06-21 17:42:25 -04:00
copilot-swe-agent[bot]
c5148222cc fix: gate canRemoveFromContinuePlaying on roms.user.write scope
Co-authored-by: zurdi15 <34356590+zurdi15@users.noreply.github.com>
2026-06-21 21:34:03 +00:00
Georges-Antoine Assi
e9ab144d39 feat(v2): render cover art at its natural aspect ratio
Stop forcing a fixed box ratio on gallery covers. Cards now share one
height and vary in width to match each cover image's natural aspect, with
no cropping.

- GameCover measures the rendered image's natural ratio on load and drives
  its own aspect-ratio from it (style ratio kept only as a pre-load seed);
  emits the measured ratio.
- GameCard default (gallery) card: fixed art height, natural width; size
  tiers and hero keep their fixed footprints. Forwards the ratio event.
- Gallery grid becomes flow-packed wrapping rows: useGalleryVirtualItems
  greedily packs same-height / natural-width cards per row (ragged right),
  measuring ratios client-side (cached by rom id, debounced re-pack). Row
  height stays uniform so RVirtualScroller, AlphaStrip, scroll restoration
  and grid-nav are unchanged.
- useResponsiveColumns additionally exposes usableWidth for width packing.
- Unit tests for packFlowRows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:33:28 -04:00
zurdi
58ecb681ec feat(v2): restore "remove from continue playing" action
The per-user "remove from Continue Playing" option existed in v1's
AdminMenu but was missing in v2. The API, store action
(removeFromContinuePlaying) and data shape were already shared; only
the v2 UI layer and action handler were absent.

- useGameActions: add removeFromContinuePlaying() (clears last_played,
  prunes the cached continue-playing list, snackbar feedback) plus a
  canRemoveFromContinuePlaying gate (only when the ROM has last_played).
- GameActionsList: surface the action as an RMenuItem in the more-menu.
- locales: add snackbar-removed-from-playing and
  snackbar-remove-from-playing-failed to all locales (translated for
  es/de/fr/it/pt/ru, English placeholder elsewhere).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:44:40 +00:00
Georges-Antoine Assi
765d2a29fe fix settings.json 2026-06-21 16:31:17 -04:00
Georges-Antoine Assi
77970f6379 Merge branch 'master' into feature/device-flow-authorization 2026-06-21 16:25:39 -04:00
Zurdi
dbc6139752 Merge pull request #3570 from rommapp/chore/extract-files-layout
refactor: unify upload UX across GameDetails subtabs
2026-06-21 21:21:35 +02:00
Zurdi
ee33bf1b71 Merge pull request #3569 from rommapp/feat/share-savestates
feat: share saves & states with other users
2026-06-21 21:18:34 +02:00
zurdi
7c82965825 docs: address copilot review comments on PR #3570
Refresh stale doc comments that didn't keep pace with the refactor:

- `global.css` r-v2-asset-fade — list AssetList / AssetStrip / FileRow
  / ScreenshotsTab / SoundtrackPanel as the consumers (was: just
  AssetList / AssetStrip).
- `SaveDataTab.vue` section-head comment — drop the "subtitle" mention
  (subtitle was removed in this PR).
- `FilesTab.vue` file header — section header now hosts Upload only;
  Download-all / Copy-link moved to the selection toolbar (select-all
  then act). Updated the file-level summary and the content-column
  bullet list to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 18:18:00 +00:00
zurdi
d3bc224900 refactor: unify upload UX across GameDetails subtabs
Move every upload affordance out of the sidebar's collapsible action
panel into a section header at the top of each panel, matching
ScreenshotsSubtab. Sidebars become navigation-only.

Touched subtabs:
- SaveDataTab (Saves + States) — Upload per Mine section header.
- MediaTab Manual — header hosts the entry selector + Upload only;
  Delete + Redownload move into the PDF viewer's toolbar via new
  PdfViewer `deletable` / `redownloadable` props.
- MediaTab Soundtrack — header with just Upload.
- MediaTab — reorder subtabs so Screenshots sits above Soundtrack.
- FilesTab — header with Upload only (Download all / Copy link
  dropped; equivalent via select-all + selection toolbar).
- ScreenshotsSubtab + SaveDataTab — drop the redundant "Private to
  you until you make them public" subtitle and i18n keys.

Add a distinct entrance animation for dense, data-row content:
`r-v2-asset-fade` (fade + 14px rise, no scale, no overshoot, 320ms,
24ms stagger). Applied to AssetList / AssetStrip rows, FileRow,
screenshot thumbnails and soundtrack track rows so they cascade in
on first paint, separate from the bouncier `r-v2-card-fade` reserved
for hero cards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 17:23:40 +00:00
Georges-Antoine Assi
49f2b1659c Merge pull request #3272 from rommapp/claude/user-game-activity-monitoring-Mqb1v
Add real-time activity tracking for active game sessions
2026-06-21 12:02:58 -04:00
zurdi
91640c36ec fix: address copilot review comments on PR #3569
* i18n `rom.cant-toggle-visibility` is interpolated with `{error}` in
  SaveDataTab but the strings dropped the placeholder, hiding the
  backend error detail from the snackbar. Add `{error}` to every locale
  (en_US source + es_ES translated, others English placeholder).
* `RSliderBtnGroup` badge visibility check did not handle the string
  `"0"` / `""` cases allowed by the `string | number | null` type.
  Extract a `showBadge` helper and use it in both the router-link and
  RBtn branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 15:55:23 +00:00
zurdi
1e902a966f style: align visibility toggle color across saves, states & screenshots
Notes already paints the lock button primary when private and muted when
public; bring screenshots and the new saves/states toggles to the same
binding so the three visibility surfaces read consistently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 15:45:39 +00:00
zurdi
47b198c61c style: apply eslint --fix to share-savestates refactor
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 15:22:43 +00:00
Georges-Antoine Assi
c6c7ce9ea6 cleanup 2026-06-21 11:13:30 -04:00
Georges-Antoine Assi
0460133992 Secure activity identity, cut Redis churn, remove v1 activity
Backend:
- Resolve the acting user from the authenticated socket session on
  connect instead of trusting the client-supplied user_id, so a client
  can no longer spoof a "now playing" session for another user. Only
  rom_id/device_id come from the payload.
- Emit activity:update/clear through the already-initialised socket
  server instead of opening (and leaking) a fresh AsyncRedisManager per
  REST heartbeat.
- Collapse get_all_active's per-key GET into a single MGET.
- Drop the pure pass-through _build_activity_entry helper.

Frontend:
- Remove all activity emits from the v1 EmulatorJS Player; the v2 shell
  is the single driver of the activity lifecycle.
- Remove activity from the v1 UI entirely (Activity view, ActivityBtn,
  ActivePlayers on game details, navigation, and the now-v2-only route).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:12:18 -04:00