Release v1.3.0 - #480
Merged
Merged
Conversation
The ACK check sat inside the DONE branch, so it was only reachable for a
run that finished. A run that is killed or loses its host never writes
DONE: it ages into the STALE arm, which consulted nothing. That entry
reported forever and held rc=1 forever, and `touch <dir>/ACK` could not
clear it.
Both /b-work Step 0.3 and /b-close gate on this exit code and tell the
operator to stop and handle a non-zero result, so one unclearable entry
stops every later session with an already-handled line β until the check
becomes noise, and the next genuine failed close lands in a channel
nobody reads.
Read ACK above the DONE branch and apply it to the DONE and STALE arms.
The in-flight arm deliberately IGNORES it (AC3): that arm adds nothing to
rc, so an ACK could only hide a live run β and refusing instead would
rebuild this bug mirror-imaged, as an entry the documented remedy cannot
clear. Stated in the header comment, which claimed the contract this
code broke (AC5).
The STALE line now names its remedy, as the DONE line already did; its
absence is why the observed case was cleared by hand-writing a DONE file.
Tests pin all four {DONE, no DONE} x {ACK, no ACK} states plus the
in-flight carve-out, and that the mute is per-directory β silencing the
whole sweep would satisfy AC1 and break AC2.
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
journal-save-button sat 100% under the keyboard at extra-extra-extra-large (measured [24,415][279,473] against a full keyboard window of [0,407][375,667] on iPhone SE 3), so a large-text user could not reach the app's only save-time crisis scan at rest. The keyboard band is identical at every text size; only Save moved, pushed down by the heading and transcript growing above it. DEBUG-480 fixed the default size only and left 4pt of clearance β one Dynamic Type step from failing. It failed. Restructure: the review phase's action block leaves the ScrollView and becomes a plain flex sibling of it, inside an outer container whose paddingBottom is read from the LIVE keyboard frame. Clearance is now non-negative by construction rather than by arithmetic. Frame-derived deliberately, and this is the load-bearing choice. UIKit reports key layout + predictive bar + any inputAccessoryView as one union, so the inset IS the occluding edge rather than a proxy for it. DEBUG-506 is about to attach this app's crisis accessory for the first time and raise that edge ~72pt on this surface, which would take DEBUG-480's 4pt clearance negative at the DEFAULT text size. A clearance-derived fix is correct under exactly one of those two states; this one is correct under both, because the inset and the edge are the same number read once. Not useOverlayBottomInset(): its MAX with CRISIS_BUTTON_RESERVED_BAND reserves 176pt with no keyboard up β a quarter of an SE 3 viewport, in every phase, to duplicate protection the action row's paddingRight already provides. The band is the shape for a centred card; a bottom-anchored row dodges the crisis button horizontally. The keyboard subscription is extracted to useKeyboardFrameHeight rather than forked a third time, on the same module-level useSyncExternalStore shape as useKeyboardOccludesCrisisButton. Also here, because the restructure causes them: - the save error moves into the pinned footer and its scrollToEnd is retired. It can no longer push Save down, and left in place it would fight the scroll-to-top below. It gains accessibilityLiveRegion (WCAG 4.1.3) β the scroll was its only handling and that signal is visual. - the banner scrolls back to top on the crisisActive rising edge. In a full-screen ScrollView "first child" and "top of viewport" coincided; in a shrunken scroll region they do not, and the one path that can disclose while scrolled is a save that fails and discloses. - the transcript floor is expressed in scaled lines, not a fixed 180pt, which held ~7 lines at default and ~2 at AX5 on a screen whose task is proofreading. - journal-discard-button relocates out of the footer at fontScale >= 2.6 so it never crowds Save. Relocation, never deletion; Save never moves. Constraints from the crisis and accessibility planning passes, both of which withdrew positions once given the DEBUG-506 fact and each other's arithmetic: no second 988 control on this surface (that obligation is DEBUG-506's and is app-wide), keyboardDismissMode stays on-drag while DEBUG-506 is open, and journal-review-header stays inside the scroll region so its bubble-phase dismissal keeps working. Corrected in place: the automaticallyAdjustKeyboardInsets comment, which described that prop as the reachability mechanism. It insets scroll content and does not move contentOffset, so it never repositioned Save and cannot reach a pinned footer. Its accessory-inclusiveness claim was also never exercised β no build has produced a non-zero bar for it to absorb. Refs DEBUG-480, DEBUG-507, DEBUG-506 π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
β¦e URL Swap APP_STORE_SERVER_API_ORIGINS to the short forms Apple currently documents (api.storekit.apple.com / api.storekit-sandbox.apple.com), and move the two literals that pin them in the same commit. Future-proofing, not a live defect. Measured 2026-08-22: each pair still shares one Akamai CNAME (commercegateway / commercegateway-sandbox) and all four hosts return 401 unauthenticated, so the swap is cosmetic today. It guards against Apple retiring the legacy alias, which would fail every Apple verification at once -- verify-apple-receipt and grace-period-automation both resolve the host through the same resolveApiOrigin. The pinning test's failure on the constant change alone (162 passed / 1 failed) is the proof it was load-bearing; that exactly one test failed also confirms the other four references track the constant symbolically and needed no edit. Also asserts the distinctness the test's name has always promised but never checked, so a future edit cannot collapse the two hosts without failing. No fallback and no host-switching retry: the module header forbids it by name, because that is the deleted 21007 sandbox-fallback bug. Two hosts co-resolving is the reason no fallback is needed, not a reason to try both. resolveApiOrigin's no-default fail-closed throw is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
AC 4. profile-voice-reflection-xxxl.yaml stopped at the keyboard-up state because the next step was broken; DEBUG-516 fixed it, so the flow regains the contract it was always meant to pin: tap journal-save-button at its resting position, keyboard up, at the largest non-accessibility text size, and assert journal-saved-state. No scroll before the tap and no visibility assertion on the button β both are forbidden in this file and in journal-crisis-scan.yaml. A scroll weakens the contract from "reachable with the keyboard up" to "reachable after scrolling", and the iOS keyboard is a separate window so XCUITest scores a 100% covered button fully visible. The oracle stays journal-saved-state, which only handleSave's success branch produces. Also adds a drag-dismissal block, deliberately as a separate capture at the end. keyboardDismissMode='on-drag' is this surface's only keyboard dismissal (multiline field, so Return inserts a newline and there is no Done key), and while DEBUG-506 is open it is also the only route back to a state where the root crisis button is reachable. The restructure shrank the scroll region, so "the content still scrolls, therefore the drag still dismisses" stopped being free β if it ever stops being draggable the mitigation dies silently. It cannot share the block above, because `swipe` destroys the condition that one tests. Both files' recorded geometry is stamped as PRE-DEBUG-506 evidence rather than a threshold. The +4pt @ large / -37pt @ XXXL figures record where an in-ScrollView Save landed on one build, and two things move them: type size (which is DEBUG-516) and the keyboard's own chrome β an attached inputAccessoryView joins the union UIKeyboardFrameEndUserInfoKey reports, so DEBUG-506 raises the occluding edge ~72pt and would have taken the 4pt negative at the default text size. The fix no longer derives from either number. Refs DEBUG-480, DEBUG-507, DEBUG-506 π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
β¦journal-save-occluded-xxxl
β¦pi-hosts chore: MAINT-513 App Store Server API host constants lag Apple's documented base URL
β¦s terminal The block added in the previous commit ran after journal-saved-state and started a fresh capture. It failed on device with `Element not found: journal-record-button`: the `saved` phase is terminal by design and offers no affordance back to `idle`, which VoiceReflectionScreen's own DEBUG-504 comment states outright. Folded into a single capture, ordered drag -> re-raise -> save. That is stronger than the original shape, not a workaround: the save is now measured against a freshly-presented keyboard AND a scroll region the drag has already moved, so it is the pinned footer rather than a lucky content offset that has to keep Save reachable. Drags DOWN from the header rather than UP β at the top of the region that is an overscroll bounce, which still begins a drag (so on-drag fires) without carrying the transcript field off screen and stranding the steps below it. The comment records why a route back to `recording` must never be added to "fix" this: anything reaching that phase outside handleStart inherits the previous capture's draftId and silences its disclosure, which is the defect DEBUG-504 fixed. Refs DEBUG-504 π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
β¦ne generator Lands the measurement apparatus for free-text crisis detection. Scoped at batch approval: the harness, fixture schema and generator ship now; the LLM corpus generation is deferred (no ANTHROPIC_API_KEY in this environment, and no root package.json, so the SDK cannot be installed without breaking FEAT-287's pin). Four artifacts, all test-only -- textCrisisDetection.ts is not modified, so there is no runtime behaviour change and no safety-surface diff: - scripts/generate-crisis-corpus.js -- zero-dependency, repo-root, raw HTTP against the Messages API. Wired into nothing: no npm script, no CI job, no test. Refuses to run without a key rather than degrading to an empty corpus, and REFUSES an --out path inside the repo (unreviewed generated phrasing must not enter git history, and an untracked file under the worktree invalidates the e2e provenance fingerprint). Both refusals verified by running them. - crisisTextCorpus.v1.json -- seed corpus. JSON, not .ts, because jest's testMatch collects any .ts under a src __tests__ dir and would fail it as an empty suite. Four classes; KNOWN_OVER_FIRE exists so the deliberate "I don't want to die" over-trigger cannot be mislabelled MUST_NOT_FIRE to improve a rate. - textCrisisDetection.corpus.test.ts -- reports MUST-FIRE recall, MUST-NOT-FIRE false-positive rate and STT-MANGLED recall as three separate figures, and thresholds none of them (AC5: recorded, not enforced). It hard-fails only on corpus integrity and on an ANCHOR set -- one canonical phrase per shipped pattern -- which is the anti-narrowing lock. Verified by mutation: removing the no-point-living pattern turns three assertions red; reverted. - The dated review packet, which is the record, because the CI job that selects this file runs --silent and swallows the console figures. The seed's 12/12 recall is a HARNESS SELF-TEST, not a baseline -- every seed item is a string already reviewed and committed in textCrisisDetection.unit.test.ts. The packet says so in three places, and carries six VERIFIED MUST-FIRE misses as the first candidate batch: "killing myself", "ending it all", "cant go on", "suicide", "i want to end my life", "i wish i was dead" all MISS, and all are correctly-transcribed text -- which falsifies the module header's "zero-false- negative holds over correctly-transcribed text". Correcting that header is deliberately out of scope here (it would re-tier this to an attended close) and is the most important thing to carry forward. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
β¦a pre-existing abort MEASURED on the gate simulator, 3 of 3 runs: ExpoSpeechRecognizer.start -> prepareMicrophoneRecognition -> AVAudioEngine.inputNode -> AURemoteIO::Cleanup -> _ReportRPCTimeout -> abort(). A blocking RPC to the simulator's audio daemon, entered on the journal-record-button tap, SIGABRTing the app ~15s later wherever the flow has reached. NOT a DEBUG-516 regression: crash reports carrying this exact stack predate this branch (2026-08-21 02:14, 02:20, 02:22, 02:26; 2026-08-22 04:25, 04:31), and nothing in this item touches handleStart or the speech guard. What the added block did was lengthen the record -> assert window past ~15s, so the abort started landing inside the flow instead of after it. It surfaces as "App crashed or stopped while executing flow" attributed to whichever step was executing β in one run it surfaced instead as a bare "Element not found", so a green run is not evidence the audio path was healthy. The flow keeps AC 1 and AC 4 and nothing else, so the whole record -> save -> journal-saved-state sequence sits inside the window. The header records the bound, so nothing is inserted there later without re-measuring it. Recorded as a STATED coverage gap rather than pinned green by a weaker assertion: that keyboardDismissMode='on-drag' actually dismisses is now unverified on device. It matters because it is this surface's only keyboard dismissal and, while DEBUG-506 is open, the only route back to a state where the root crisis button is reachable. The abort itself needs its own item; it is a product-visible crash on the record path, not a harness defect. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
β¦-corpus chore: INFRA-512 adversarial crisis-corpus harness, fixture and offline generator
β¦b-close-status-ack-stale
check:ci-test-coverage failed on src/core/hooks/__tests__/useKeyboardFrameHeight.test.tsx: it matches no CI --testPathPattern, so it would run on nobody's pull request. Declared rather than wired, following the ledger's own convention. Its consumer's suite β VoiceReflectionScreen.behavioral.test.tsx β is already listed for exactly the same reason (co-located under src/**, reached by no pattern), and wiring the remainder is a deferred follow-up blocked on the assessmentStore load-dependent test recorded in wiring-them-is-deferred. Inventing a pattern for one file would change the CI contract for every PR in the repo, which is not this item's scope. NOT fixed by renaming toward a pattern β that is forbidden by the ledger's own do-not-fix-by-renaming entry, and coverage-by-filename is the defect INFRA-368 exists to document. The why entry records what the absence actually costs, so the next reader does not have to re-derive it: the behavioural contract IS gated (Phase 2.5 runs journal-crisis-scan on any features/journal change), so what is missing is the subscription-shape and snapshot pins, not the reachability contract. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
β¦journal-save-occluded-xxxl
Enumerated permission pins cannot catch the failure that rejected v1.2.0. app/package.json still declared expo-file-system ~56.0.7; the lockfile's RESOLVED version moved 56.0.7 -> 56.0.9, which added a PHPhotoLibrary call, and Apple requires NSPhotoLibraryUsageDescription whenever the symbol links. No diff in the repo showed it. This scans each package's own ios/ sources for purpose-string APIs and fails closed on any that is present but undeclared in app.json β inverting speechRecognitionPermissions.config.test.ts, which asserts a hand-written list. Exemptions are explicit and carry a reason. One today: expo-sensors, whose CMPedometer reference is unreachable (only useBugReportShake.ts consumes it, via Accelerometer/CMMotionManager, which needs no purpose string). Includes the guards a source-shape assertion needs to stay honest: a non-trivial-scan floor, a known-bad literal the matcher must still match, and a check that every exemption names a module that still ships native sources. Verified red by deleting the key: names 'NSPhotoLibraryUsageDescription (required by: expo-file-system)'. Runs in test:safety, so precommit and the CI Safety + privacy gates job. π€ Generated with [Claude Code](https://claude.com/claude-code)
eas build --no-wait exits 0 when EAS *accepts* the job, so the workflow's conclusion described scheduling, not the build. v1.2.0 errored in Install dependencies on an expired GITHUB_TOKEN while this job reported success β nothing on GitHub ever went red, and the failure was found only by reading eas build:list an hour after the release was tagged. Drops --no-wait so the job carries the build's verdict, and raises timeout-minutes 15 -> 60 to cover queue + build + submit (15 was budgeted for the fire-and-forget exit; keeping it would have failed the job on success). Adds a closing step stating what green does not mean: Apple can still reject the uploaded binary during processing and reports only by email β ITMS-90683 rejected v1.2.0 minutes after a fully green submit. No workflow can gate that, so it is said out loud instead of implied away. π€ Generated with [Claude Code](https://claude.com/claude-code)
β¦tale fix: DEBUG-511 honour ACK on a close that died without a verdict
chore: harden the release path against false greens
β¦journal-save-occluded-xxxl
β¦d-xxxl fix: DEBUG-516 journal-save-button is 100% occluded by the keyboard at the largest non-accessibility Dynamic Type step
β¦efect The comment asserted main's workflows were stale with a specific list (retired workflows, older NODE_VERSION, differing job-list). The v1.2.0 release synced them, making that false β but writing "they now match" would rot just as fast: release.yml diverged again within hours of the release, via PR #425. Restated as the structural rule instead. main carries whatever the last release shipped; every workflow change merged to development after that is absent there; the gap re-opens after every release. Names the check to run rather than a point-in-time state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
chore: INFRA-459 main-branch-health SCOPE comment stated drift as a defect
The main worktree is retired β main is only ever a ref in this repo, moved by the release and checked out by nothing. Its HEAD reflog held a single entry, its node_modules was six weeks stale, and app/ios had never been built. So 'cd ~/dev/being/main' names a path that does not exist, and the obvious substitute β git checkout main in the development worktree β would move that worktree off development, which /b-release and /b-close assume. Cuts a throwaway worktree off origin/main instead (satisfying strict: true by construction), spells out the env symlinks that worktree add does not create, and closes with its removal. π€ Generated with [Claude Code](https://claude.com/claude-code)
docs: hotfix cuts a worktree off origin/main
The completed Daily Practice card applied `opacity: 0.5` to the whole Pressable. React Native composites the entire subtree, so it scaled the contrast of every descendant at once β including `semantic.text.secondary` on the description and the `gray[400]` border chosen three lines above precisely to clear 3:1. Both fell under AA in the state a healthy daily user sees every single day after they practise. Opacity is not a colour token and cannot be contrast-audited, so completion is now expressed structurally: the filled full-width bar is replaced by a quiet `β Done today` status line in `themeColors.primary`. That also retires the "Complete" label, which in a high-contrast filled bar parsed as an imperative telling the reader to complete what they had just completed. The card remains the tap target and its accessibilityLabel/Hint still announce completion and offer the restart, so no touch target is lost. Press feedback (`opacity: 0.9`) is unchanged β this is scoped to the `isCompleted` branch. The completed state was previously unreachable from any test: the store mock hardcoded `isCheckInCompletedToday: () => false`. It is now mutable, mirroring the existing `mockFlags` pattern, with two controls that stay green across the change so a red cannot be confused with a dead harness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBAW7cM5qUVzcV3c6dAoYU
β¦eached
DEBUG-465 ruled, in this file, that "a scroll in front of `continue-button`
is now REQUIRED and is not the same act" as the scroll in front of
`daily-loop-support-line`, which stays banned forever. Only the Sphere
Sovereignty site ever got one. Quick beat 1 and deep beat 1 stayed bare, so a
Continue below the fold surfaced as a plain `element not found` β
indistinguishable from layout drift on a flow whose reds are usually layout.
Adds a boundary `scrollUntilVisible` (direction DOWN, no centerElement) at
both, making such a failure attributable. A detector, not a fix: nothing is red
today and `continueWrap`'s CRISIS_FAB_CLEARANCE is already unconditional.
The item's two stated mechanisms are both refuted, and the comments say so
rather than repeating them:
- The pinned support line cannot collide with these taps.
`showsSupportLine()` is false on Aware Presence, the beat both taps are
dispatched from; `supportLine: true` occurs only on RadicalAcceptance
StepConfigs.
- The `crisis-button-root` overlap does not reproduce here either. The 24pt
measurement at DailyLoopStepScreen.tsx derives from the support bar's 58pt
viewport reduction and scopes itself to beats where showsSupportLine() is
true. Without the bar the CTA sits below the FAB band, not inside it.
So no assertion claiming a crisis false positive was added at either site: it
could not go red, and this file already records that continue-button carries no
reachability contract.
No centerElement, deliberately. It is load-bearing at Sphere Sovereignty, where
it proves a real scroll happened and keeps the tap off the co-located support
line β untouched there. Here there is no co-located affordance, and centring
would re-position mid-content and reintroduce the DEBUG-477 swallowed tap
(probe F). As written both DEBUG-477 arms are clear: already-visible means zero
swipes; below the fold means a DOWN scroll to the last ScrollView child, which
terminates at the content boundary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
The item's premise was true when filed and is false now. FEAT-457 shipped guidance-suppressed-handoff.yaml (bb70cb8 / cc26461, PR #407) alongside the consumers whose absence had blocked this item, so the SUPPRESSED branch β the protective one, where a distressed reader must receive zero philosophy β is already pinned end to end. Authoring a second GAD-7-seeded flow against it would have produced two independently-seeded completions asserting one contract, which drift apart silently. What had no device-level pin at all is the POSITIVE side: that a reader the gate does not suppress actually receives Tier 0 and Tier 1, and that Tier 2/3 stay capped unless allowTier2Plus is true. That is what this flow adds. The failure it catches is over-disclosure, not crisis silence. It costs zero assessment taps. `gentle` is the verdict for MISSING data, not only for mid-band scores β guidanceGate.ts: "absence is never evidence of safety, so it cannot yield full ... gentle is the only defensible answer." The sibling records the same fact from the other side at its re-launch: "NO clearState HERE ... clearing would make the gate answer `gentle`." Two design points the flow's header derives rather than asserts: - Positive assertions come FIRST. useGuidanceGate reports `pending` while the encrypted store hydrates, and every assertNotVisible would pass vacuously in that state and on `guidance-error`. Asserting guidance-content and guidance-tier0 first is what makes the absences mean something. - No scrolling. Tier 2/3 render inside the guidance-content ScrollView, and per DEBUG-465 XCUITest retains elements clipped by a ScrollView, so assertNotVisible at offset 0 is a real claim about whether the tier RENDERED. A scroll would weaken it to absence-at-an-offset. Declares 375x667 rather than `any`: the contract is viewport-independent but the reach is not, and the only viewport on which tapping home-guidance-entry is proven is the one the sibling certifies. Count tripwire bumped 11 -> 12 in the same commit, as its own comment requires. ACs 4, 5 and 6 are .claude/-only (the /b-close Step 2.5.3 mapping and the CLAUDE.md flow count) and are gitignored on development, so they land as a separate _bare commit. AC2's boundary cases need both axes on record β 16 real UI taps β and are tracked in INFRA-529. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
Narrowed by founder decision to date range only. Search is FEAT-530 and tags
are dropped; the scope-down comment on the item records both.
The filter reads createdAt off JournalEntryMeta, which listEntryMetadata()
already returns from one decrypt of the single index record β zero per-entry
getEntry calls, zero new storage, no new screen and no new store. Selection
lives in component state for the same reason plaintext does: a persisted filter
is a record of which spans the reader returns to.
Full-text search cannot be built this way, which is why it is a separate item.
EncryptionService disables derived-key caching because each record carries its
own salt, so every getEntry is a fresh 100k-iteration PBKDF2 and search is O(N)
derivations with no amortisation.
Preset names (last7/last30/last90/all) are reused verbatim from
features/data-export's ExportRangePreset so the two surfaces agree. The code is
deliberately NOT imported: journalAnalyticsBoundary.contract.test.ts walks the
journal feature directory, so a shared module outside it would be invisible to
the egress checks that guard this feature.
Ordering is inherited, never recomputed. listEntryMetadata() returns
newest-first and Array.filter preserves order, so there is no sort call β and a
sort is exactly where a relevance ordering would enter. Recurrence is perceived
across time, so reverse-chronological is the axis the pattern lives on.
An empty RESULT is not an empty RECORD. journal-history-empty says reflections
"will appear here", which is false for a reader who has written some and
narrowed past them, so the filtered case gets its own copy reporting the
control's effect and nothing about the reader. The filter stays mounted above
it, or the only way out of an empty result would be to leave the screen.
Buttons only β no TextInput, so the keyboard still never rises on this screen
and CrisisKeyboardAccessory is not pulled onto a surface that has never needed
it. No RN <Modal>: crisis-zero-988-windows.test.tsx forbids one here
mechanically. Rows stay identical under every preset.
Also closes a pre-existing hole this feature's own AC depends on:
journalAnalyticsBoundary.contract.test.ts pinned PostHog, Supabase and raw
network egress and contained ZERO Sentry references, so
Sentry.addBreadcrumb({ message: `filter applied, ${n} results` }) passed every
check from inside features/journal. Sentry is now scanned with an empty
allow-list, captureException included β an exception thrown while handling
entry text can carry that text.
Tests: filter arithmetic with inclusive millisecond boundaries; the screen's
empty-result, escapability and decrypt-count behaviour; and a source-shape
examiner-boundary guard carrying all three DEBUG-390 rails, which these modules
need because they name the forbidden mechanisms in prose to warn readers off
them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
β¦ilter feat: FEAT-288 Voice journal β Slice C: date-range review (on-device, zero egress)
β¦continue-tap-scroll-guards
`crisisButtonGeometry.ts` was created by DEBUG-406 because of `HapticsOptInPrompt` and `ResumeSessionModal`, and never reached them. Both re-derived `104 + TOUCH_TARGETS.minimum + 12 + spacing[16]` as a literal for the whole life of the module, while the module's header named them as consumers. Two oracles, both answering "covered", both wrong in the same direction: a reader found the header sentence, and INFRA-531's import detector β anchored on the `@/features/crisis/constants/` specifier β found no import. So moving the FAB's `bottom` would update `CRISIS_BUTTON_BOTTOM_OFFSET`, keep the geometry test green, and leave both overlays under-reserved. At `zIndex: 9999` the button wins the overlap, making that a crisis FALSE POSITIVE (the DEBUG-547 shape) β aggravated on `HapticsOptInPrompt`, whose mis-tap lands on the DECLINE side of a once-ever question, and on `ResumeSessionModal`, which auto-triggers on stale-session detect. - Both files import `CRISIS_BUTTON_RESERVED_BAND` via the ALIAS specifier. A relative import is functionally identical and matches nothing in `I531_IMPORT_RE`, so it would reproduce the invisibility this item removes. - Local declarations deleted, not re-bound. The arithmetic recital in each doc block is deleted (it was the copy); the consequence prose is kept verbatim and moved to the `paddingBottom` site β `crisisButtonGeometry.ts:28` cites HapticsOptInPrompt's "an overlap is unrecoverable" by name. - Header corrected in the same commit: it asserted a consumption that did not exist, and that assertion is what stopped a reader checking. Tests. `ResumeSessionModal.test.tsx`'s loose `>= 156` floor becomes equality against the imported constant β the floor passed while the file held its own literal. `haptic-cues-accessibility.test.tsx` gains the band assertion it never had, which matters because the prompt is flag-dark in the gate build (`practice_haptics:false` in eas.json's e2e-sim profile), so no Maestro flow can render it and jest is its only oracle. A source-shape pin in the geometry suite enforces the alias form and the absence of a local declaration; it is the only CI-visible control, since the Protected Paths rows and the Phase 2.5 arm live in `.claude/`, gitignored on `development`. Crisis planning pass ruled GATE over EXEMPT (a paddingBottom sized from the FAB's own geometry is the placement decision `i531_exempt_reason()` excludes) and FILE-level over a directory clause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJmk5ePJDhGMWcSs2vTGrd
β¦umbing Slice C, PARTIAL β the Maestro flow is written but UNVERIFIED. See below. Verified and complete: - __tests__/safety/perfHudGate.config.test.ts (13 tests). Pins that EXPO_PUBLIC_PERF_HUD is enabled in NO eas.json profile (iterating every profile, so one added later fails by default rather than being silently exempt), that the schema defaults it 'false' so omission cannot enable the HUD, and that the probe is mounted exactly once and only behind the flag. Source assertions strip comments first and each matcher is proved to fire and to reject a known-bad literal (DEBUG-390). - e2e-safety.sh: device-only tag is now a union, (safety|perf)-device-only. Matching only the safety string would have cleared DEVICE_ONLY for the perf flow, sent it to a simulator, and measured the host Mac's GPU. No-op for existing flows: none carries the new tag. - e2e-safety.sh: --apple-team-id on device runs, read from app.json's expo.ios.appleTeamId. Without it maestro cannot build its XCUITest driver and dies with no report, which reads as a flow regression. - e2e-safety.sh: -e forwarding via E2E_MAESTRO_ENV. Empty when unset, so every existing invocation is byte-identical. Needed because the AC requires the threshold be a required parameter with NO default. - .maestro/breathing-fps-budget.yaml + npm run e2e:perf:breathing-fps. NOT verified, and not claimed: the flow has never executed. maestro 2.6.0 cannot build its iOS driver for a connected iPhone β its own extracted project is missing MaestroDriverLib/Info.plist. This is upstream, not configuration: it survives a clean ~/.maestro/maestro-iphoneos-driver-build and reproduces identically on crisis-988-dial. That last point is the bigger finding. crisis-988-dial is the only safety-device-only flow and it pins the crisis dial path; it fails in 8s with NO_REPORT on this toolchain. The documented device-only safety procedure cannot currently run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found while running this item's own device flow. maestro 2.6.0 cannot build its XCUITest driver for a connected iPhone, so the only safety-device-only flow fails in ~8s with NO_REPORT before any step runs. Annotated in the flow itself so a red is not misread as a dial-path regression. The config half stays pinned and CI-gated by lsApplicationQueriesSchemes.config.test.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
β¦tro version
No released Maestro can execute a flow on a physical iPhone, so both
`safety-device-only` flows β `crisis-988-dial` and `crisis-keyboard-accessory` β
have been silently unrunnable. The hazard is a procedure that reads as coverage
while being dead, so this records the loss rather than papering over it.
Measured 2026-09-07 on iPhone 16e / iOS 26.6 / Xcode 26.0.1 / team KN6FDLG98K
across 2.0.0, 2.1.0, 2.2.0, 2.4.0, 2.5.1, 2.6.0, 2.6.1, 2.7.0, 2.8.0, 2.9.0 and
2.10.0 β two failure modes, no survivor:
>= 2.2.0 the shipped driver Xcode project declares a MaestroDriverLib target
(47 pbxproj refs) and the MaestroDriverLib/ directory is shipped in
ZERO releases, while five UITests sources import it. Build dies in
~8s with no JUnit report. 2.10.0 is latest, so upgrading cannot fix.
<= 2.1.0 predates that target and BUILDS, but the XCUITest runner never
becomes ready within a 300s MAESTRO_DRIVER_STARTUP_TIMEOUT.
The hardware is healthy β the installed runner launches by hand via devicectl.
Simulator flows are unaffected: they use a prebuilt driver and never compile,
which is why 14 sim flows are green while these two cannot start.
Changes:
- e2e-safety.sh refuses the device path up front with exit 5 rather than letting
it die at ~8s with NO_REPORT, which is indistinguishable from a dial-path
regression. E2E_FORCE_DEVICE_ATTEMPT=1 re-attempts, for testing the exit
condition.
- Pin the Maestro version (app/package.json -> maestro.pinnedVersion), enforced
fail-closed. Nothing read `maestro --version` before, so `brew upgrade` moved
the toolchain with no diff and no failing check and the gate silently
re-baselined itself.
- Record the unavailability in both flow headers, docs/testing/e2e-maestro.md,
and (separately, on _bare) CLAUDE.md β with crisis's ruling that the two flows
are asymmetric: the dial is unrecoverable (sim canOpenURL is unconditionally
false), while the accessory's reachability half is sim-runnable today and is
recorded as NOT YET MIGRATED, not as unavailable.
- deviceOnlyFlowsUnavailable.test.ts pins that the record stays present, bounded
and true, incl. that the safety-device-only set is exactly those two so a third
cannot inherit the gap silently. Mutation-verified: both mechanisms go red
independently.
Per the crisis ruling, compensating follow-ups are filed: DEBUG-590 (move the
accessory reachability half to the sim suite), INFRA-591 (attended device
checklist, release-gated), INFRA-592 (assert the generated Info.plist keeps
tel/sms after plugin composition).
Verified: test:safety 630/630, typecheck clean, lint:baseline no new errors,
gate-tooling script suites green (exit-alphabet 18/18 incl. two new exit-5 pins).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NfQXVXhWB2S2pAqHkJUHVQ
β¦sedes it DEBUG-589 concluded that device flows are dead on every Maestro version 2.0.0-2.10.0, not merely broken on 2.6.0, and PR #474 annotates the flow properly with an up-front exit-5 refusal. My placeholder said "pending DEBUG-589", which would have been stale the moment #474 landed β and it was the only file conflicting with that branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught what my local run missed. Three suites spawn e2e-safety.sh end-to-end; I patched two and never ran the third, having excluded e2e-sim-build.test.js from the local sweep as slow. Both new pre-flights hit it: - Its project fixture writes a package.json with no `maestro.pinnedVersion`, so the version check refused (exit 2) and pre-empted every arm with the wrong message. `maestro` is not a CNG-projected key, so the fingerprint cases are undisturbed. - Its `maestro` stub did not answer `--version`. It now does, BEFORE the trace append, so the pre-flight's call cannot appear in a trace that other specs count invocations from. - The 14 INFRA-424 device-resolution specs reached the exit-5 refusal instead of device resolution. runSafety() now sets E2E_FORCE_DEVICE_ATTEMPT=1 for device-only selections, so they keep testing what they were written to test. That auto-set would otherwise hide the refusal from this file entirely, and a later edit removing it would read as a no-op β so two specs clear the hatch and pin the real default (exit 5, zero flows run, blames the toolchain not the hardware). Test-only change: `__tests__/` is excluded from Phase 2.5's classification because Maestro drives the running app, so the gate verdict on 4956174 (crisis-button-reachability PASS, CERTIFIED 375x667) still stands. Verified with CI's own invocation: npm run test:scripts -- --ci --testTimeout=20000 β Test Suites: 30 passed, Tests: 691 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NfQXVXhWB2S2pAqHkJUHVQ
β¦uild fix: DEBUG-589 record the device gate as unavailable and pin the Maestro version
β¦3-breathing-frame-probe
β¦crisis-geometry-import-swap
β¦ calibration Re-scope after DEBUG-589: no Maestro version can run a flow on a physical iPhone, so the device-only flow asserting the HUD is not blocked, it is unavailable. INFRA-373 therefore delivers a manual instrument and no gate. Runbook Β§5a now carries what was built, the measured calibration (iPhone 16e / iOS 26.6 / 60Hz: 599 frames, nominal 16.66, zero drops), the manual procedure, and the two control runs still owed before any threshold is set β which is the durable home the AC required, instead of a commit message. Also corrects two claims Β§5a made: - Normalising does NOT need a native call. The nominal comes from the modal interval; measured 16.66 with no UIScreen.maximumFramesPerSecond binding. - Take the mode, not the minimum. Measured min 16.42 against a true nominal of 16.67 β frames land early, so the minimum is biased low and inflates the dropped-frame ratio. Says "instrument, not gate" in as many words, because this section exists downstream of MAINT-307 correcting a claim that Maestro enforced budgets it never asserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
β¦rt-swap fix: DEBUG-586 import the crisis band instead of re-deriving it
Four readings on iPhone 16e / iOS 26.6 / 60Hz Release, all zero dropped frames, nominal 16.67 from the modal bin every time. Control (a) β the JS-thread haptic cue chain has no measurable effect on UI-thread frame delivery. Rows 1/1b/2 ran with the scheduler live and row 3 with the pipeline off. Both gates are required for it to run at all (tactileEnabled = flagOn && practiceHaptics === true, and this screen passes no announce), which is why the persisted opt-in state mattered. The null is the architecturally expected result and is the first on-device evidence here that the worklet/JS-thread separation holds. Control (b) β the probe's own per-frame cost is under one frame interval. Recorded as an upper bound, not a measurement: both arms run the probe, and a probe-off arm yields no numbers by definition. Also records run-to-run variance at Β±1 frame (rows 1 and 1b, same binary, same conditions), which any future threshold has to clear to mean anything. Explicitly does NOT set a threshold. Every run read exactly zero, which supports "ratio must be 0" β brittle β or nothing, and DEBUG-589 leaves no gate for a threshold to live in. Inventing one here would be the permanently-green no-op this item exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
β¦3-breathing-frame-probe
β¦uin selection Both Seneca *Letters* records declared `work: "Letters from a Stoic"` over `translation: "Richard Mott Gummere"`. That title is Robin Campbell's 1969 Penguin Classics selection, which is in copyright; Gummere's Loeb text is *Ad Lucilium Epistulae Morales*. So the metadata shelved a public-domain Loeb text under a copyrighted Penguin name β the DEBUG-352 / FEAT-567 / DEBUG-582 defect class one layer up, in the field a reader would use to find the source. Renamed to `Moral Letters to Lucilius`: the title DEBUG-582's pinned Wikisource transcription itself runs under, so the type key traces to the pin. `Epistulae Morales` was rejected as a truncation matching no title page in the pin. Title-cased in the union because it names the work; the suite docblock keeps sentence-case because it quotes the source page verbatim. Citations are deliberately UNCHANGED. `Letters 13.4` / `Letters 107.11` are standard scholarly short form, attach no edition identity, and are the only reader-facing strings here β `work` is metadata that no surface renders. Lengthening them would newly commit this defect in the one field a reader reads, and would desync `practiceQuotes.test.ts:323`, whose `Letters \d+(\.\d+)?` format gates the CORPUS lookup. README 152 and 413 are corrected in the same pass. 413 was the worst instance in the repo: it sat inside the sentence asserting "public-domain translations, the only renderings shipped in the app" while naming the copyrighted edition. 152 additionally dropped *Discourses*, which nothing in the repo ships from. `operations/resources.md` is untouched by design β it recommends the Penguin edition to a reader, which the existing suites deliberately partition from claiming it as our source. Also corrects the public-domain rationale DEBUG-582 landed: life+70 is not the "weaker footing" for a pre-1978 published work, it is the wrong theory outright. The US term for works published 1923-1977 runs from publication, never the author's life, so Gummere's 1955 death is irrelevant. Pre-1929 US publication is the sole basis. The new pin reads module assets HERE rather than in `moduleClassicalQuotes.test.ts`, which sits under `src/features/learn/__tests__/`, matches no CI `--testPathPattern`, and is listed in `scripts/ci-uncovered-tests.json` as ungated β a pin there runs on nobody's PR. Asserted on the `work` field, never raw file text, so it cannot collide with a note that names the banned title to warn against it. Balance ratchet re-derived rather than assumed: across all five files the only delta is the byWork KEY; every count and total is byte-identical, so passages-4 stays 2/2 against floor(4/2). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJmk5ePJDhGMWcSs2vTGrd
β¦-sheet blocker INFRA-571 registered three presenter call sites in PRESENTER_ALLOWLIST at two different evidentiary standards. This settles the expo-sharing one and records why the StoreKit/Play Billing one cannot be settled here. MEASURED (2026-09-07, Release build, iPhone SE 3rd gen / iOS 18.6, 375x667): with the share sheet up, `maestro hierarchy` carries ZERO app-owned nodes β an absent app tree, not a covered FAB, leaving a full-bleed PopoverDismissRegion and the sheet's out-of-process ShareSheet.RemoteContainerView. Presence was not the evidence; hit-testability was. A matched-pair coordinate tap inside the FAB's measured bounds reached CrisisResources with the sheet DOWN and did not with it UP β same route, same point, same run, twice. That pair is also the falsification demonstration. The inferred reading UNDERSTATED the occlusion. The exception STANDS and does not convert: UIActivityViewController is OS-owned and out-of-process, so no RN element, zIndex or z-order change reaches above it. It is bounded only by the user dismissing the sheet. IAPService stays NOT MEASURED, with its blockers now enumerated rather than left to be re-derived: mockMode = __DEV__ makes the call unreachable in any Debug build, no .storekit config exists so simulator StoreKit resolves no products, and Android needs a Play-signed build plus a licence tester against an Android e2e harness that does not exist. Also fixes the assertion that was supposed to police this boundary: it pinned the measured entry with /MEASURED/, which is a substring of "NOT MEASURED", so it passed on prose asserting the opposite. Measured entries now match /MEASURED ON (DEVICE|SIMULATOR)/ and are asserted not to carry the negation. ExportDataScreen's header said the route inherits "the sibling CollapsibleCrisisButton overlay". That mount was removed in MAINT-290; a probe written from it would assert an unmounted testID and report an occlusion it never tested. The measurement flow is committed under its own tag so the suite cannot select it. It pins a DEBT state β its load-bearing assertion is that the affordance is unreachable β so it stays green after a fix and must be deleted, not repaired. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVJWaGwB6q65r94ysb2Trs
Caught by CI's Script guard tests, which no precommit pattern runs. The team-id lookup hard-failed with exit 2 when app.json was unreadable. That is too aggressive: an unresolvable team id is recoverable β maestro reports it itself β and exit 2 means "no verdict", which is strictly worse than letting the driver build fail with its own message. It also broke the proceed-anyway contract the INFRA-424 device tests encode, since their sandbox stages the sourced helpers but not app.json. Now warns and omits the flag. 114/114 script guard tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
β¦measure-inferred-presenter-occlusions FEAT-570 deleted the DEBUG-533 allowlist entry along with the call it examined, so the measured/inferred partition this branch introduces now names ExportDataScreen as the measured side and IAPService as the inferred one. Two cross-references in this branch's ruling pointed at that deleted entry and are repaired: the structural-conclusion citation, and INFRA-571's reachability comparison against the Sentry widget, which no longer exists β the reachability claim now stands on its own facts. Verified the measurement still holds: every file on the measured path (ExportDataScreen, RootCrisisButton, crisisButtonGeometry, PrivacyDataScreen, rootOverlaySlot) is byte-identical between the measured tree and this merge base. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVJWaGwB6q65r94ysb2Trs
chore: INFRA-373 device-measured breathing frame probe (instrument, not gate)
β¦work-field-gummere-loeb-title
β¦oeb-title fix: DEBUG-585 name the Loeb edition Gummere translated, not the Penguin selection
β¦measure-inferred-presenter-occlusions
β¦measure-inferred-presenter-occlusions
β¦senter-occlusions fix: DEBUG-577 measure the share-sheet occlusion, rule the purchase sheet unmeasurable
Bump version across all four INFRA-141 sources: - app/package.json - app/app.json (Expo manifest + iOS buildNumber + Android versionCode) - .config/.env.production (untracked, edited in place) - .config/.env.development (untracked, edited in place) π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
A new HIGH advisory (CVSS 7.5, CWE-400 ReDoS-class: maxTotalMergeKeys does not limit CPU use for empty merge sources) published against js-yaml >=3.0.0 <3.15.2 and >=4.0.0 <4.3.2. It landed after development's last green run and fails `Security + compliance` on every open PR, including this release's bump PR β the gate is severity-based and this is un-allowlisted. Targeted fix per the standing npm-audit rule, never `npm audit fix`: the existing pins were already at 3.15.1 / 4.3.1, so the fixed releases are in-range patch bumps of the same pins. Bumped all three keys in `overrides` and mirrored them in `resolutions`: js-yaml@^3.13.1 3.15.1 -> 3.15.2 js-yaml@^4.1.0 4.3.1 -> 4.3.2 js-yaml@^4.1.1 4.3.1 -> 4.3.2 Not allowlisted, because a real fix exists and the consumers tolerate it β all three are build-time only (@react-native/jest-preset -> babel-jest -> babel-plugin-istanbul, eslint -> @eslint/eslintrc, and expo -> @expo/cli -> @expo/xcpretty). Nothing here reaches the app bundle. Lockfile movement is confined to js-yaml and the argparse it pulls: the 3.x nested copy bumps in place, and the two nested 4.3.1 copies dedupe into one hoisted 4.3.2. No expo or react-native package moved, so this carries no native-surface change (Phase 2.8 clean). Verified: `npx audit-ci --config .audit-ci.json` -> "Passed npm security audit", exit 0 (high 8 -> 7, the remaining 7 all allowlisted); `npx patch-package` -> expo-modules-jsi@56.0.12 β. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
chore(release): v1.3.0 version bump + unblock the npm-audit gate
Records `main`'s history on `development` with ZERO content change. The resolved tree is byte-identical to `origin/development` β verified with `git diff origin/development` (empty). Why it was needed. The v1.3.0 release PR opened CONFLICTING against `main` even though Phase 2.4 of /b-release found no divergence, and both checks were correct: - `main` carries hotfix PR #435 (`d1e6e840`, DEBUG-534 legal copy), which was backported to `development` by cherry-pick as `4b2119d7`, per the Hotfix Process. Cherry-pick rewrites the SHA, so Phase 2.4's `--cherry-pick --right-only` patch-id check correctly cleared it: `main` has no unique CONTENT. - But `development` then moved PAST that content. DEBUG-545 took privacy-policy.md 1.10 -> 1.11 and added three DPIA changelog rows. Git sees two independent edits to the same regions with no common ancestor for them, so a textual conflict remains even though one side is a strict superset of the other. Both conflicts resolved to `development`, which is the superset in each: docs/legal/privacy-policy.md main 1.10 vs dev 1.11 -> dev docs/legal/dpia-sensitive-wellness-data.md main empty vs dev rows 2.10/2.11/2.12 -> dev `docs/architecture/analytics-architecture.md` auto-merged and is likewise unchanged from `development`. Note for the Hotfix Process: CLAUDE.md states that after a cherry-pick backport "git handles identical content gracefully on the next release PR." That holds only while `development` does not subsequently edit the same lines. It did here, and the conflict surfaced at release time rather than at backport time. π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
chore: back-merge main into development to clear the release conflict
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v1.3.0 (build 6) β 88 commits since v1.2.1.
Features
Fixes
Crisis reachability & 988 affordances
Privacy & analytics
Assessment, accessibility & layout
Content authority
Build & release tooling
Chores
Release-time notes
overrides/resolutionspins to 3.15.2 / 4.3.2. Build-time consumers only.Main Branch Healthmain branch CI health failing (2026-09-01) β audit gateΒ #456 βmaincould not pass its ownSecurity + compliancegate. This release is that issue's documented carrier and should clear it.Legal Site Freshnessnow passes.π€ Generated with Claude Code
https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh