Skip to content

Final wiring of all things highlights API and auth-guard, with fix for highlight not deleting immediately (visually)#294

Merged
cameronapak merged 33 commits into
mainfrom
claude/ype-1034-pr5-hardening
Jul 24, 2026
Merged

Final wiring of all things highlights API and auth-guard, with fix for highlight not deleting immediately (visually)#294
cameronapak merged 33 commits into
mainfrom
claude/ype-1034-pr5-hardening

Conversation

@cameronapak

@cameronapak cameronapak commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

PLZ WATCH VIDEO ▶️

Summary

Server-backed BibleReader highlights go live: the dark-launch flag flips on after a final round of auth hardening, a fix for successful deletes being misread as failures, and Swift-parity UX corrections. Last PR of the YPE-1034 stack (#283#285#286 → this one); the consolidated changelog announces the launch and the removal of the accidentally shipped localStorage-only highlights.

Changes

  1. Highlights are enabled by default (HIGHLIGHTS_LIVE on) — signed-in readers' highlights persist to their YouVersion account; six stacked changesets are consolidated into one launch changelog.

  2. Removing a highlight no longer flashes back before disappearing — the core ApiClient treated empty-body 2xx JSON responses (the real DELETE shape) as failures, so the machine reverted the successful removal; regression tests now drive the real client through a stubbed fetch.

  3. A data-exchange grant only applies to the user who initiated it — initiator recorded at redirect, read-and-cleared at callback, any mismatch fails closed; signIn also clears stale permission stashes so an abandoned flow can't leak grants across users.

  4. A resumed pending write that fails 401/403 re-prompts for permission instead of silently reverting the reader's tap.

  5. Sign-in matches the Swift SDK: appName / signInPromptMessage props, Platform wordmark, comma-joined requested_permissions wire format, and a permission cache seeded from token scope so a highlight tap doesn't re-prompt right after OAuth.

  6. Dark mode matches the Swift SDK: highlight fills render at 0.3 opacity (1.0 in light mode), verse numbers paint white over dark-mode fills, and popover swatches preview the dimmed color a highlight will actually apply; fills also get subtly rounded corners with per-line-fragment rounding at wraps (deliberate web-native divergence from Swift's square fills); the active swatch shows a checkmark instead of an X.

  7. Hardening and cleanup: xstate pinned to exactly 5.32.4 (supply-chain cooldown), review findings across all three packages (color row gated on interactivity, stable serverColors/refetch identities, shared useApiClient), the logo's accessible label is a required localized prop, the pending-permissions stash stores string[], and personal workflow notes are stripped from AGENTS.md.

  8. One-shot sign-in grants survive the server's URL encoding: parseGrantedPermissions now also accepts bracket-array keys (granted_permissions[], indexed forms) that the auth server emits, on both the sign-in callback and the data-exchange return — previously the echoed grant was silently discarded and the permission dialog re-prompted after a successful sign-in.

  9. StrictMode-proof auth: the OAuth code exchange and the token refresh are each shared across concurrent invocations, so a duplicate's 4xx can no longer clear the session or the just-seeded grants (previously the demo's <StrictMode> double-invoke could wipe a fresh sign-in or grant cache).

  10. One-shot sign-in survives a silent server: a live instrumented capture (2026-07-23) showed the web callback carries no granted_permissions echo on either hop and the token scope is profile openid only — the SDK had zero evidence of the consent the user just gave. The permissions requested at signIn are now stashed (state-bound, fail-closed) and optimistically seeded as granted on a successful exchange; an over-optimistic seed self-corrects through the existing 401/403 → permission re-prompt path. The durable fix is server-side (echo the grant on the web callback) and is being filed with Platform.

  11. Verse-number labels and footnote icons inherit the verse text color over highlight fills in both themes, so saturated light-mode fills no longer clash with muted gray glyphs (deliberate divergence from Swift, which only recolors the label in dark mode).

  12. Reviewer simplification pass applied in full (12 comments, −286 lines: shared highlight test fixtures, dead helper/config removal, duplicate test deleted) and getHttpStatus now recognizes plain {status} objects, not just Error instances.

  13. Mobile: the verse-action popover caps at the viewport width (with live tracking across mid-open resizes) and the swatch row scrolls horizontally with edge fades that appear only where content is hidden; Copy/Share stay pinned. Verified live at 300px.

  14. Fill opacity churn, net zero: a same-day design decision briefly moved light-mode fills to 30% opacity and was then walked back — final state is the original 1.0 light / 0.3 dark (Swift parity). Kept in history as commits cf6a291/959f2ca and revert 7b91105.

Start here: change 3 — auth-boundary logic; the fail-closed path is the part worth adversarial review.

Test plan

  • pnpm build && pnpm typecheck && pnpm lint && pnpm test: all green (1014 tests — core 369 / hooks 289 / ui 356)
  • Vapor regression suite fails on the reverted fix, passes with it (fetch-stub end-to-end through the real client, machine, and adapter)
  • Storybook integration tests run in CI

Needs manual check: one live confirmation in the vite demo — remove a highlight while signed in and confirm no flash. The one-shot flow's root cause was pinned down via a breadcrumb-instrumented live run (server returns no grant evidence; see change 10) and the fix is regression-tested against that exact captured shape. Live-verified 2026-07-23: signed-out tap → sign-in → OAuth → highlight applies with no second dialog.

Greptile Summary

This is the final PR in the YPE-1034 stack, flipping server-backed BibleReader highlights to live. It ships a substantial auth hardening pass alongside the feature launch: the empty-body DELETE flash fix, StrictMode-safe code-exchange and token-refresh deduplication, initiator-bound data-exchange grant safety, bracket-array permission parsing, and optimistic permission seeding for the web flow's missing server grant echo.

  • Auth boundary (change 3 + 9): the data-exchange initiator is recorded before the redirect and cleared on return; any user mismatch or missing initiator fails closed. Code-exchange and token-refresh deduplication prevent StrictMode double-invocations from clearing tokens the winning run just seeded.
  • DELETE flash fix (change 2): ApiClient now reads the response body as text first and treats an empty body as "no data", so a 200 application/json with no payload no longer throws and surfaces a successful delete as a failure.
  • Permission seeding (changes 8 + 10): parseGrantedPermissions accepts bracket-array keys (granted_permissions[], granted_permissions[0]), and requested permissions are optimistically seeded on callback (state-bound, self-correcting via 401/403 → re-prompt) to work around the server returning no grant echo on the web flow.

Confidence Score: 4/5

Safe to merge except for the light-mode fill opacity constant, which doesn't match the product decision documented in the PR description.

The auth hardening, DELETE flash fix, and StrictMode deduplication are all well-designed and regression-tested. The one concrete gap is that HIGHLIGHT_FILL_OPACITY_LIGHT is still 1.0 even though the PR description's product decision (item 14) explicitly changed light mode to 30% — matching dark mode — and says swatches should preview the dimmed fill in both modes. The ADR update was written before that decision landed and still documents the pre-decision split. Everything else in the 63-file change looks correct.

packages/ui/src/components/verse.tsx (fill opacity constant) and packages/ui/src/components/verse-action-popover.tsx (swatch preview in light mode)

Important Files Changed

Filename Overview
packages/core/src/Users.ts Major auth hardening: StrictMode-safe code-exchange deduplication, state-bound permission stashes, optimistic permission seeding with self-correcting 401/403 path, and stale-stash cleanup at signIn start. Well-structured and test-covered.
packages/core/src/data-exchange.ts Initiator-bound grant safety: initiator is recorded at redirect start, read-and-cleared on return, and any mismatch (different user, missing initiator) degrades to failure. Security design is solid and regression-tested.
packages/core/src/client.ts Adds getHttpStatus with duck-type check (accepts any object with a numeric status property, not just Error instances), and fixes empty-body 2xx handling so DELETE responses no longer surface as JSON parse failures.
packages/core/src/permissions.ts Adds bracket-array key support (granted_permissions[], granted_permissions[0]) via regex to handle PHP/Rails server encoding; extracts parsePermissionList for token-scope parsing. Correctly handles all observed server formats.
packages/ui/src/components/bible-reader-highlights-machine.ts Removes the reprompt field from WriteOp — all apply-kind writes (user-initiated and resume-applied) now re-prompt on 401/403, fixing silent drop of resumed taps. Adds signedOut guard to auto-dismiss the permission dialog on sign-out.
packages/ui/src/components/verse.tsx Introduces theme-aware fill opacity constants (1.0 light / 0.3 dark), but HIGHLIGHT_FILL_OPACITY_LIGHT = 1.0 contradicts the PR description's stated product decision that both modes should render at 30%.
packages/core/src/YouVersionPlatformConfiguration.ts Adds dataExchangeInitiator storage (save/get/clear) and wires clearDataExchangeInitiator into clearAuthTokens so a sign-out always drops a pending initiator.
packages/hooks/src/internal/useApiClient.ts New shared hook that centralizes ApiClient construction for all per-service hooks, reducing duplication. Optional mode ({ optional: true }) returns null instead of throwing for auth-tolerant callers.
packages/hooks/src/useApiData.ts Adopts the useEvent-style fetchFnRef pattern to keep fetchFn out of fetchData's deps, stabilizing refetch identity across renders without stale-closure risk.
packages/ui/src/components/verse-action-popover.tsx Adds horizontal scroll with per-edge fade masks (pure computeScrollFade function, ResizeObserver-tracked), viewport-width cap, corrected auto-focus on pointer-open, and theme-aware swatch previews.
packages/ui/src/components/use-bible-reader-highlights.ts Adds highlightsInteractive flag and a reference-stable serverColors memoization (content-equality comparison via ref) to prevent spurious verse-style re-sweeps on no-op refetches.
packages/hooks/src/useHighlightAuthActions.ts Saves the data-exchange initiator before the async token mint and clears it on error, ensuring any mid-await user change can't stamp the new session as the original initiator.

Reviews (14): Last reviewed commit: "revert(ui): restore full-strength light-..." | Re-trigger Greptile

@changeset-bot

changeset-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7b91105

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-core Minor
@youversion/platform-react-hooks Minor
@youversion/platform-react-ui Minor
vite-react Patch

Not sure what this means? Click here to learn what changesets are.

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

Comment thread packages/ui/src/components/bible-reader-highlights-machine.ts
@cameronapak
cameronapak force-pushed the claude/ype-1034-pr3-recent-colors branch from 2f47c5f to 8a6ae88 Compare July 21, 2026 19:46
cameronapak and others added 6 commits July 21, 2026 14:54
A pending highlight resumed after sign-in/data-exchange now routes write
failures through the same handling as a user-initiated apply: 401/403
invalidates the permission cache, re-stashes the pending highlight (in its
own scope), and re-opens the permission dialog instead of silently dropping
the user's tap. No unattended redirect loop: the dialog requires a click.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Correctness:
- Gate the color row on auth-provider presence (highlightsInteractive) so
  copy/share-only integrations don't render inert swatches
- Exit permissionDialog to idle and clear pending when auth flips signed-out

Efficiency:
- Keep serverColors reference-stable across equal refetches (no chapter-wide
  verse re-sweep per write)
- Stabilize useApiData refetch identity by holding fetchFn in a ref

Reuse/simplification:
- Export getHttpStatus from core; machine consumes it instead of probing the
  ad-hoc error shape
- Extract resolveAuthToken (core) to de-dupe getAuthToken in highlights and
  data-exchange clients
- Add internal useApiClient hook; refactor five hooks onto it
- Drop derivable WriteOp.reprompt; share scopesEqual from the machine

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xstate@5.32.5 was published 2026-07-14, inside the 3-day minimumReleaseAge
window, so every CI job failed at pnpm install. Pin to 5.32.4 (published
2026-07-02) and rebuild the lockfile from a fresh resolution, per the
policy's own guidance. Bump back once 5.32.5 ages out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The callback saved granted_permissions for whoever was in localStorage
when the return page loaded — if user B signed in on another tab
mid-redirect, B inherited A's grant and hasPermission('highlights')
skipped B's consent (review finding).

The redirect leg now records the initiating user's id; the callback
reads-and-clears it and only saves the grant when it matches the current
user. Mismatch or missing initiator fails closed: grant discarded, result
downgraded to failure, URL still cleaned — the flow re-prompts instead of
proceeding as granted. Sign-out clears the initiator. The combined
sign-in+permissions path is unaffected: it returns through the sign-in
callback, which binds the grant to the user from the same ID token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cameronapak
cameronapak force-pushed the claude/ype-1034-pr5-hardening branch from 98f5693 to 905e592 Compare July 21, 2026 20:01
@cameronapak
cameronapak changed the base branch from claude/ype-1034-pr3-recent-colors to main July 21, 2026 20:01
…ing on 5xx

Save the data-exchange initiator before minting the token, and stop clearing the pending stash on apply network/5xx so a sibling permission-lost intent survives.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cameronapak
cameronapak changed the base branch from main to claude/ype-1034-pr3-recent-colors July 22, 2026 16:05
@cameronapak
cameronapak changed the base branch from claude/ype-1034-pr3-recent-colors to main July 22, 2026 16:05
cameronapak and others added 2 commits July 22, 2026 11:08
Keep controlled mode always interactive (YPE-3705) while self-contained
still requires HIGHLIGHTS_LIVE and an auth provider.

Co-authored-by: Cursor <cursoragent@cursor.com>
Take aria-hidden from main; keep this branch's checkmark-tap remove test.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cameronapak cameronapak changed the title Harden highlights: grant binding, 401/403 re-prompt, xstate pin (4/4) WIP (almost ready): Harden highlights: grant binding, 401/403 re-prompt, xstate pin (4/4) Jul 22, 2026
cameronapak and others added 7 commits July 22, 2026 12:31
Stash granted_permissions across the web OAuth hop and seed from token
scope; sync appName/signInPromptMessage onto the UI-bundled core so the
sign-in dialog pitch reaches consumers of the UI package.

Co-authored-by: Cursor <cursoragent@cursor.com>
An abandoned sign-in flow (consent granted, tab closed before the final
code round-trip) left the pre-code granted_permissions stash in
localStorage. A later sign-in by a different user would union that stale
stash into its own grant set and seed it into the optimistic permission
cache under the wrong user. Clear the stash when starting a new flow; it
is only ever produced later during the callback pre-code hop and never
needs to survive a signIn. Mirrors the data-exchange initiator binding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prevent cross-flow permission cache seeding and leftover data-exchange
initiator binding after mint failure or a new sign-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
The real DELETE /v1/highlights/{id} returns 200 application/json with an
empty body. ApiClient called response.json() on it, which throws, so
every successful delete surfaced as a failed write. The highlights
machine then (correctly, for a real failure) reverted its remove
overlay, repainting the stale highlight until the refetch caught up —
the "removed highlight flashes back" bug.

Read the body as text and treat empty as undefined. Regression coverage
stubs global.fetch and drives the real client + machine + adapter, the
only test shape that can catch response-parsing bugs (client-method
mocks cannot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flips the YPE-1034 dark-launch flag. The vapor delete-flash bug is fixed
and regression-tested; the optimistic-overlay staleness trade-off
(local overlay wins over server truth until a refetch reflects the
write) is signed off as acceptable for launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Highlight fills now render at 1.0 opacity in light mode and 0.3 in dark
mode (Swift BibleTextView+Rendering parity), replacing the flat 0.35.
Highlighted verse number labels paint white in dark mode so they stay
legible over the fill; both fill and label styles clear on removal.
ADR YPE-642 corrected to record the as-built values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The logo component hardcoded an English aria-label default, which
bypassed localization. The label is now a required prop; the sign-in
dialog already passes the localized string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cameronapak and others added 3 commits July 22, 2026 15:56
The stash payload is already JSON; comma-joining the permissions into a
string was a needless round-trip. Unknown shapes fail closed (the stash
only lives across a single in-flight OAuth redirect).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The learned-preferences blocks were one developer's session notes, not
repo guidance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six stacked-PR changesets merge into highlights-server-backed.md: one
consumer-facing entry announcing server-backed highlights as live, with
an overt callout that earlier releases accidentally shipped a demo-only
localStorage implementation. Controlled-mode and aria-label entries stay
separate; the aria-label entry now covers the required-label change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cameronapak cameronapak changed the title WIP (almost ready): Harden highlights: grant binding, 401/403 re-prompt, xstate pin (4/4) Server-backed highlights go live: auth hardening + delete-flash fix Jul 22, 2026
@cameronapak cameronapak self-assigned this Jul 22, 2026
cameronapak and others added 4 commits July 22, 2026 16:17
Highlight fills get 4px rounded corners with box-decoration-break:
clone, so each wrapped line fragment carries its own rounded ends and
2px inline padding instead of a square cutoff at the wrap. The padding
and rounding are static on every verse span, so applying or removing a
highlight only changes the background color and never reflows text.
Verse-action popover swatches now preview the real fill in dark mode
(same 0.3 alpha as applied highlights) with a legible checkmark and
stroke. Deliberate web-native divergence from Swift's square fills,
recorded in ADR YPE-642.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The signInPromptMessage prop stays in the SDK (Swift parity, hidden
when unset); the demo just should not showcase it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opening the verse-action popover moved focus onto the first swatch, and
because the trigger is a click on non-focusable verse text, browsers
treat Radix's deferred programmatic focus as keyboard-like, so the
focus-visible ring flashed on every mouse/touch open. Initial focus now
lands on the popover content itself (tabIndex -1 via onOpenAutoFocus);
Tab moves to the first swatch with the ring visible. Verified live in
the demo for both modalities.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parseGrantedPermissions only read the bare granted_permissions key, but
the auth server encodes permission lists with bracket-array notation
(observed as requested_permissions[]=highlights in its consent
redirect). A same-shape echo on the sign-in or data-exchange callback
was silently discarded, so a one-shot sign-in grant never persisted and
the permission dialog re-prompted. The parser now also accepts
granted_permissions[] and indexed granted_permissions[n]; bare-key
parsing, comma splitting, and de-dup are unchanged, and the state-bound
stash and cross-user clearing are untouched.

Callback-level regression tests cover both the final-callback echo and
the pre-code stash hop in bracket form.

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

@jaredhightower-youversion jaredhightower-youversion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Simplification pass — a few candidates to delete or shrink; all non-blocking.

Comment thread packages/core/src/permissions.ts Outdated
Comment thread packages/core/src/SignInWithYouVersionPKCE.ts Outdated
Comment thread packages/ui/src/assets/youversion-platform-logo/Contents.json Outdated
Comment thread packages/core/src/__tests__/Users.test.ts Outdated
Comment thread packages/core/src/__tests__/Users.test.ts Outdated
Comment thread packages/ui/src/components/verse.test.tsx Outdated
Comment thread packages/ui/src/components/YouVersionProvider.test.tsx Outdated
Comment thread packages/ui/src/components/use-bible-reader-highlights.dom-vapor.test.tsx Outdated
Comment thread packages/ui/src/components/use-bible-reader-highlights.dom-vapor.test.tsx Outdated
Comment thread packages/ui/src/components/use-bible-reader-highlights.strictmode-vapor.test.tsx Outdated
cameronapak and others added 10 commits July 23, 2026 12:35
…pe grants

Under React StrictMode the auth init effect double-invokes and both runs
call handleAuthCallback with the same single-use code still in the URL. The
duplicate exchange 400s and its catch path calls clearAuthTokens (which also
clears the granted-permissions cache the winning run just seeded), producing a
spurious "highlights permission needed" dialog after a successful sign-in.

- Dedupe the code-for-token exchange by authorization code via a module-scoped
  map, so repeated/concurrent callbacks share one exchange (one token request,
  no destructive catch path). Failed exchanges are shared too and not retried.
- Widen getHttpStatus to recognize any non-null object with a numeric status
  (subsumes the Error case) so a thrown plain {status: 401} is honored.
- Regression tests: core dedupe (single fetch, grants survive), StrictMode
  provider tolerance, and getHttpStatus plain-object/Error/unrelated cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- core: inline granted_permissions regex; drop dead .filter(Boolean)
- core: makeTokens/setupCallbackFlow/stubSignInCrypto helpers in Users.test
- ui: delete unused Xcode asset-catalog logo folder; fix doc reference
- ui: shared highlights-test-utils (collection/mockUserInfo/deferred/Providers)
- ui: drop duplicate checkmark-clear test; simplify css read, waitFor, refs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…'t wipe the session

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omits the grant echo

On the web sign-in flow, a user can request `highlights`, grant consent, and
still receive ZERO grant evidence back to the SPA. Captured live 2026-07-23:
the pre-code hop carried only `state`, the code hop `scope="profile openid"`,
and the token body `scope: "profile openid"`. All three existing grant sources
in exchangeCodeForTokens (URL echo, state-bound pre-code stash, token scope)
come back empty, so saveGrantedPermissions never runs and the highlights
machine re-prompts for permission immediately after sign-in.

Bridge the server contract gap client-side: signIn now stashes the REQUESTED
data-exchange permissions bound to the OAuth `state`, and the callback reads,
clears, and unions them into the granted set (same fail-closed state check as
the pre-code stash, still filtered by OIDC_SCOPES). The seed is optimistic and
self-correcting: a 401/403 on the first write drops the permission and the
machine's PERMISSION_LOST path re-prompts. The Set union means a future server
echo never double-counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rack viewport for the width cap

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…walked back 30%-both-modes)

Design direction reverted same-day: fills return to 1.0 opacity in light
mode and 0.3 in dark mode (Swift parity), undoing cf6a291 and its comment
touch-up 959f2ca.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cameronapak cameronapak changed the title Server-backed highlights go live: auth hardening + delete-flash fix Final wiring of all things highlights API and auth-guard, with fix for highlight not deleting immediately (visually) Jul 23, 2026

@jaredhightower-youversion jaredhightower-youversion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!! Awesome job on this game. It's been a journey, but we've gotten to this point. I did watch the video. The small little nuances with the thing getting cut off: not sure if you were able to resolve that. If not, no worries. A small follow-up PR should be able to resolve that.

@Dustin-Kelley
Dustin-Kelley self-requested a review July 24, 2026 14:22

@Dustin-Kelley Dustin-Kelley left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔥

@cameronapak
cameronapak merged commit 71e4c1a into main Jul 24, 2026
8 checks passed
@cameronapak
cameronapak deleted the claude/ype-1034-pr5-hardening branch July 24, 2026 14:29
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.

3 participants