Skip to content

feat: FEAT-293 Standalone-practice discoverability + propatheia content fix - #219

Merged
MP2EZ merged 2 commits into
developmentfrom
feat/FEAT-293-standalone-practice-discovery
Jul 25, 2026
Merged

feat: FEAT-293 Standalone-practice discoverability + propatheia content fix#219
MP2EZ merged 2 commits into
developmentfrom
feat/FEAT-293-standalone-practice-discovery

Conversation

@MP2EZ

@MP2EZ MP2EZ commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes FEAT-293

Standalone practices (breathing, body scan, reflection, and especially the Stoic control-sorting drill) were reachable only from deep inside Learn. This surfaces them from Home.

Scope narrowed from the original AC1. "A Home entry and/or a loop variation slot" was a material fork, not a hedge: no variation slot exists (zero grep hits for variation; StepConfig has no affordance to host one, and adding it mutates a crisis- and philosopher-reviewed config), and daily_loop:false in .config/.env.production means a loop-slot-only implementation would have been invisible to every real user the day it merged β€” making AC1's own words ("reachable from a primary surface") false in production. Loop slot deferred; Home entry ships.

What ships

  • Home: fixed-height "Practices / Explore" row below checkInSection, not a fifth CheckInCard. checkInSection is flex:1 and each card inside is also flex:1 with no ScrollView, so a fifth card squeezes all of them. The row also survives FEAT-298 retiring the three time-of-day cards.
  • New PracticeLibrary root screen. Sorting drill featured and named by principle, carrying the Enchiridion 1 framing and a "Read the full principle: Sphere Sovereignty" link to Module 3 β€” no context-free launch, since the drill's answer key encodes a counterintuitive doctrine that reads as arbitrary once Module 3's prose is out of view. Practices group under their own principle: breathing/body scan under Aware Presence (never labelled Stoic), reserve clause with Sphere Sovereignty (it is hypexhairesis).
  • Launch switch extracted from PracticeTab into a pure resolvePracticeRoute, shared by Learn and the library rather than two switches that can drift.
  • SortingPractice.scenarios is now optional, resolved by a wrapper that self-loads module content. This also repairs the pre-existing /sorting deep link, which could never have supplied a scenarios array.
  • No score/tally/streak/badge. Signed feedback copy and VIRTUE_CHECK_FEEDBACK untouched. The citation is imported from PRACTICE_QUOTES, not retyped.

Content safety fix β€” 5 cards, live in Learn today

medical-test-anxiety marked "Your anxiety about an upcoming medical test result" as in-control, explaining the anxiety itself is up to you. That conflates propatheia β€” the involuntary first movement, which classical Stoicism holds is not up to us β€” with assent. On a primary surface without Module 3's prose it tells a user with elevated GAD-7 that their anxiety is their own doing, and flags "Not Quite" when they disagree.

Rewritten to sort the response, first wave of fear moved to notInControl. The philosopher's review of that rewrite found it had made the rest of the deck inconsistent with its own new standard:

Card Fix
medical-test-anxiety text/explanation rewritten; two avoidance-shaped inControl items replaced
political-response its explanation contradicted its own notInControl list β€” both render in one viewport
traffic-cutoff asserted the arising anger is chosen
event-interpretation highest risk β€” stated the conflation as doctrine, on the card most likely to be loaded with a real difficult event
vacation-weather listed an outcome ("finding enjoyment") as a controllable

friend-no-reply was already correct and served as the model; six other cards verified clean.

Tests

AC3 ("no regression to existing Learn-launched entry points") had no mechanical pin β€” zero tests referenced PracticeTab. Added:

  • 13 cases locking resolvePracticeRoute param-for-param to the original inline switch, including its default durations; plus pins that the drill's citation resolves to Epictetus rather than silently falling back to the breathing quote, and that every catalog entry is filed under its own principle (a mislabel would propagate into the Insights engagement chart).
  • 6 render cases for SortingPracticeRoute, which is now the rendered component for the pre-existing SortingPractice route. The Maestro suite deliberately does not walk practice flows, so this path had no on-device gate. Load-bearing case: the Learn path passes scenarios through with no module load and no spinner frame.
  • 1 case in RootCrisisButton.test.tsx pinning that PracticeLibrary keeps the overlay in standard mode β€” unknown routes fall through to standard, but safe-by-accident is not a contract.

Verification

npm run precommit          exit 0 β€” safety 108, clinical 106, unit 587, privacy 51
npm run test:accessibility 11 suites, 87 passed
lint baseline              557 -> 555 (improved)

npm run e2e:safety (FULL suite β€” core/navigation is cross-cutting)
  PASS  crisis-button-reachability
  PASS  daily-loop-quick-depth
  PASS  gad7-severe
  PASS  phq9-severe-completion
  PASS  q9-single-alert

Follow-up (not in this PR)

Philosopher sequencing note: medical-test-anxiety is card 3 of 12. On a wrong answer the announcement lands hard for a user with a real pending result. Recommend re-ordering it later in the deck, or excluding it from the first-served set on a Home cold-start.

πŸ€– Generated with Claude Code

MP2EZ and others added 2 commits July 25, 2026 14:24
…nt fix

Narrowed from the original ACs after a 3-lens planning panel. AC1's "a Home
entry and/or a loop variation slot" was a material fork, not a stylistic hedge:
no variation slot exists (zero grep hits), adding one mutates a crisis- and
philosopher-reviewed config, and `daily_loop` is false in .env.production β€” so
that branch would have shipped invisible to every real user on merge day. Home
entry only; the loop slot is deferred.

WHAT SHIPS

- Home: a fixed-height "Practices / Explore" row BELOW checkInSection, not a
  fifth CheckInCard. checkInSection is flex:1 and every card inside it is also
  flex:1 with no ScrollView, so a fifth card squeezes all of them. The row also
  survives FEAT-298 deleting the three time-of-day cards.
- New PracticeLibrary root screen. The sorting drill is featured and named by
  principle (SPHERE SOVEREIGNTY / Control Sorting), carries the Enchiridion 1
  framing and a "Read the full principle" link to Module 3 β€” the philosopher
  requires no context-free launch, since the drill's answer key encodes a
  counterintuitive doctrine that reads as arbitrary once Module 3's prose is out
  of view. Practices group under THEIR OWN principle: breathing and body scan
  are the Aware Presence limb and are never labelled Stoic; the reserve clause
  is hypexhairesis and stays with Sphere Sovereignty.
- The launch switch moved out of PracticeTab into a pure `resolvePracticeRoute`,
  so Learn and the library launch through identical code rather than two
  switches that can drift.
- SortingPractice's `scenarios` param is now optional, resolved by a wrapper
  that self-loads module content when the caller has none. This also repairs the
  pre-existing `/sorting` deep link, which could never have supplied an array.
- No score, tally, streak or badge anywhere; the signed feedback copy and
  VIRTUE_CHECK_FEEDBACK are untouched. The featured citation is IMPORTED from
  PRACTICE_QUOTES rather than retyped β€” a hand-typed second copy is exactly how
  the citation drift FEAT-268 cleaned up would recur.

CONTENT SAFETY FIX (philosopher-blocking, and live in Learn today)

`medical-test-anxiety` marked "Your anxiety about an upcoming medical test
result" as in-control, explaining the anxiety itself is up to you. That
conflates propatheia β€” the involuntary first movement, which classical Stoicism
holds is NOT up to us β€” with assent. Promoted to a primary surface without
Module 3's prose it tells a user with elevated GAD-7 that their anxiety is their
own doing, and flags "Not Quite" when they disagree.

The card now sorts the RESPONSE, with the first wave of fear moved to
notInControl. The philosopher's review of that rewrite then found the fix had
made the rest of the deck inconsistent with its own new standard, so four more
cards are corrected: `political-response` (its explanation contradicted its own
notInControl list on the same screen), `traffic-cutoff`, `event-interpretation`
(the highest-risk one β€” it stated the conflation as doctrine on the card most
likely to be loaded with a real difficult event), and `vacation-weather` (listed
an outcome, "finding enjoyment", as a controllable). Two avoidance-shaped
inControl items on the medical card were also replaced.

TESTS

AC3 ("no regression to existing Learn-launched entry points") had NO mechanical
pin β€” zero tests referenced PracticeTab. 13 new cases lock the resolver's output
param-for-param to the original inline switch, including its default durations,
plus: the drill's citation must resolve to Epictetus and not silently fall back
to the breathing quote; every catalog entry must be filed under its own
principle (a mislabel would propagate into the Insights engagement chart). One
case added to RootCrisisButton.test.tsx pinning that PracticeLibrary keeps the
overlay in standard mode β€” unknown routes fall through to standard, but safe-by-
accident is not a contract.

Verification: npm run precommit exit 0 (safety 108, clinical 106, unit 581,
privacy 51); npm run test:accessibility 87 passed; lint baseline improved
557 -> 555.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssion)

SortingPracticeRoute is now the rendered component for the pre-existing
`SortingPractice` route, so a bug in it breaks the Learn-launched sorting
practice that shipped long before FEAT-293.

That risk is invisible to the Maestro safety gate: crisis-button-reachability
deliberately does not walk practice flows (they need the long check-in
preamble), so no on-device gate exercises this path. The unit tests added with
the feature covered the pure route resolver, not the wrapper's rendering.

Six cases, the load-bearing one being the Learn path: when the caller already
holds the scenarios the wrapper must pass them straight through with NO module
load and NO spinner frame β€” same render as before FEAT-293. Plus the standalone
path (loads, then renders), and the three degradation paths that must never
reach SortingPracticeScreen, which throws on a missing scenario at the current
index: empty array, unknown practice id, and a rejected load. Also pins that a
load resolving after unmount does not set state.

Verification: npm run precommit exit 0 β€” safety 108, clinical 106, unit 587
(+6), privacy 51.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MP2EZ
MP2EZ merged commit c68605a into development Jul 25, 2026
22 checks passed
@MP2EZ
MP2EZ deleted the feat/FEAT-293-standalone-practice-discovery branch July 26, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant