Context. surface customer · domain arete · route /domains/arete/affirmations · auth signed-in · source apps/oshun/web/src/app/domains/arete/affirmations/page.tsx
Last walked. 2026-06-28 automated Playwright walk — authenticated direct route, daily card arrow and position controls, tab selected state, Browse filters, favorite toggle state, custom creation, route-level axe scan, render, expected content, and console/page-errors verified. Evidence: apps/oshun/web/e2e/arete-affirmations-accessibility.spec.ts
Purpose#
Internal /domains/arete/* alternate surface for affirmations: a tabbed
workspace with a daily card, browse / create / schedule / journal / streak
views. Backed by fixture constants from @/lib/arete/arete-extended-simulation
rather than a live BFF. Distinct from the polished /arete/* Lilith surfaces —
heavier, more app-like, client-rendered.
Entry points#
- Direct URL / bookmark — yes (signed-in)
- From
/domains/arete/*peer navigation — no explicit cross-link in source; each/domains/arete/<x>page renders its own component without shared chrome - No link from
/aretepolished surface — verify
Layout regions#
page.tsx is a 'use client' page: renders an SR-only heading "Affirmations ·
Arete · OSHUN" and <AreteAffirmations />. The component lives at
apps/oshun/web/src/components/domains/arete/AreteAffirmations.tsx.
- Container:
max-width: 900px; margin: 0 auto; padding: 24px - Header: Sparkles icon + h2 "Affirmations" + subtitle "Daily positive affirmations to transform your mindset and build self-belief"
- Tab bar (6 tabs,
overflowX: 'auto'):daily— Daily Card (Sparkles icon)browse— Browse (BookOpen icon)create— Create (Plus icon)schedule— Schedule (Clock icon)journal— Journal (Edit3 icon)streak— Streak (Flame icon)
- Content area (rendered per active tab):
daily→<DailyCardView />— gradient-backed card with one affirmation, prev/next arrows, dots indicator, 3 stat tiles (Day Streak, Favorites, Days Practiced) — driven byAFFIRMATIONS,AFFIRMATION_STREAKbrowse→<BrowseView />create→<CreateView />schedule→<ScheduleView />journal→<JournalView />(driven byAFFIRMATION_JOURNAL_ENTRIES)streak→<StreakView />
States#
- Loading — N/A: client component with sync fixture import; no async boundary
- Daily card revealed —
isRevealed === truerunsaff-card-revealanimation - Daily card hidden (mid-transition) — between prev/next,
isRevealed = false, thensetTimeout100ms re-true to retrigger animation - Tab switching — instant; no skeleton; covered by
arete-affirmations-accessibility.spec.ts - Empty
dailyAffirmations—if (!current) return nullshort-circuits to nothing - Empty
AFFIRMATIONS(favorites/others both empty) — same null return path - Error (recoverable) — relies on app-level error boundary; no
error.tsxunder/domains/arete - Offline — purely client-side fixture; renders identically offline once the bundle is loaded
- Gated — N/A
- Standalone PWA — no shell chrome; the page mounts directly with no
LWebShellor customer nav around it (compare to/aretewhich has full Lilith customer nav)
Interactions#
Tab bar (6 tabs)#
- Daily Card / Browse / Create / Schedule / Journal / Streak (buttons)
- Function: each calls
setActiveTab(tab.id)to toggle active view - Keyboard: native
<button>tab stops; Browser assertion covers tab switching via click, and native Enter/Space behavior is retained - Screen reader:
role="tab",aria-selected, and stabletabpanelassociation expose selected state - Touch target:
min-height: 44px - Active style: accent background tint and accent color
- Telemetry: none
- Function: each calls
Daily card#
- "Previous" arrow (button,
aria-label="Previous")- Function:
handlePrev— decrementscurrentIndexwith wraparound; flickersisRevealedto retrigger the entry animation - Screen reader: announces "Previous"
- Touch target: 44×44 px; covered by Playwright bounding-box assertion
- Function:
- "Next" arrow (button,
aria-label="Next")- Function:
handleNext— incrementscurrentIndexwith wraparound - Touch target: 44×44 px; covered by Playwright bounding-box assertion
- Function:
- Dot indicator (buttons named "Show affirmation N of 6")
- Function: clicking a dot jumps to that affirmation
- Keyboard: native button activation; Playwright focuses a position button and presses Enter to verify movement
- Screen reader: group label "Affirmation card position"; active button uses
aria-current="true"
- Category chip — shows
AFFIRMATION_CATEGORY_LABELS[current.category]; presentational
Stats row (Day Streak / Favorites / Days Practiced)#
- Stat tile — flame icon + numeral + label; static; no interaction
Browse view#
- Category filters (buttons)
- Function:
Alland each category callsetFilterCategory(...) - Screen reader: active filter exposes
aria-pressed="true" - Touch target:
min-height: 44px
- Function:
- Favorite heart (button per affirmation)
- Function: toggles the local
favoritesSet - Screen reader: accessible name changes between "Remove from favorites: ..." and "Save to favorites: ..."
- State: exposes
aria-pressed - Persistence: local component state only; no API write
- Function: toggles the local
Create view#
- Affirmation text / category fields
- Function: textarea stores the custom affirmation text; select stores the category
- Screen reader: controls expose
aria-label="Affirmation text"andaria-label="Affirmation category"
- Create Affirmation (button)
- Function: appends a custom affirmation to local component state and renders it under "Your Custom Affirmations"
- Persistence: local component state only; no API write
Data & contracts#
- Reads: import-time
AFFIRMATIONS,AFFIRMATION_SCHEDULE,AFFIRMATION_JOURNAL_ENTRIES,AFFIRMATION_STREAK,AFFIRMATION_CATEGORY_COLORS,AFFIRMATION_CATEGORY_LABELSfrom@/lib/arete/arete-extended-simulation. No BFF calls. - Writes: none —
useStatemutations are local to the component and not persisted - Realtime: none
- Caching: client bundle only
- Auth/role check: app-level middleware
Cross-references#
- Domain hub:
arete.md - Adjacent
/domains/arete/*pages — seearete.mdfor full list - No direct counterpart in
/arete/*polished routes (affirmations is a domains-namespace-only surface) - Component source:
apps/oshun/web/src/components/domains/arete/AreteAffirmations.tsx - Simulation data:
apps/oshun/web/src/lib/arete/arete-extended-simulation.ts - Types:
apps/oshun/web/src/lib/arete/arete-extended-types.ts
Open questions / known gaps#
- No shared chrome with the polished
/areteLilith room — verify whether/domains/arete/*is a V1-shipping customer surface or a design-system internal showcase - Dot indicators use native buttons with names, 44 px activation boxes, and
aria-current - Prev/next arrow touch targets are 44 px
- State mutations don't persist across page reloads (no API persistence)
- Browse favorite toggles expose accessible names and
aria-pressed; the action is intentionally local and still has no API persistence