V1 Web PWA · Surface walkthrough

Arete Journal Reflection Workspace

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections6 minread

On this page

Context. surface customer · domain arete · route /domains/arete/journal · auth signed-in · source apps/oshun/web/src/app/domains/arete/journal/page.tsx

Last walked. 2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data (2xx), console/page-errors, expected content, screenshot verified; live screen-reader, touch, offline, and telemetry-delivery checks pending a manual AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content re-verified 2026-06-03 against current source

Purpose#

Internal /domains/arete/* journal-and-reflection workspace. A three-panel layout: reflection prompts (evening / weekly / open windows) · compose draft · continuity inspector (recent reflections, brief, theme-graph links). Wired to the @oshun/domain-arete library for canonical reflection builders, the Oshun assistant entry, and a domain-route helper for navigating across Nisaba / Veritas / Tara / Arete / Nyx.

Entry points#

  • Direct URL / bookmark — yes (signed-in)
  • ?entry=<id> query parameteruseBrowserSearchParams().get('entry') is read by the page and passed as initialEntryId; selects a specific entry on mount
  • Back navigation — page passes onBack={() => router.back()}
  • Theme-graph link from another reflection — the workspace itself can produce a targetPath of the form ?entry=<id>; navigating to this page with that param sets selectedEntryId
  • Polished /arete Journal tab — the polished room's LSubNav item carries href="/domains/arete/journal" and navigates here

Layout regions#

page.tsx is a 'use client' page using useRouter and useBrowserSearchParams. Renders <AreteJournalReflectionWorkspace onBack={() => router.back()} initialEntryId={searchParams.get('entry')} />. Component at apps/oshun/web/src/components/domains/arete/AreteJournalReflectionWorkspace.tsx.

  • Container (section): cream-paper radial gradient background; column flex; padding 24px (desktop) / 16px
  • Header tile: Back button (aria-label="Go back"), eyebrow + h1 "Keep reflection close enough to shape the next move.", lede paragraph, "Open reflection assistant" CTA (calls dispatchOshunAssistantOpen)
  • Window selector: row of 3 pill buttons — evening, weekly, open (data-arete-journal-reflection-period)
  • Metric strip (3 tiles): Entries this week / Reflection tone / Draft state (Captured / In progress)
  • Artifact action panel: <AreteArtifactActionPanel /> with save and export handlers, saved-at timestamp from local artifact store
  • Three-column grid (desktop) / single column mobile:
    • Left: Reflection prompts lane — data-arete-journal-reflection-prompt-lane. Eyebrow "Reflection mode", reflection-window heading and summary, list of available prompts (filtered by reflectionWindowPromptCategories[window]). Each is a button with category eyebrow + text.
    • Middle: Compose surface — data-arete-journal-reflection-compose. Selected-prompt title, instruction body, textarea draft (aria-label="Journal reflection draft"), draft-state label (Captured / In progress), two CTAs: "Capture reflection" and "Send to assistant" (dispatchOshunAssistantOpen with prompt+draft+continuity context)
    • Right: Reflection continuity inspectordata-arete-journal-reflection-inspector. "Recent reflections" section with entry-select buttons. Continuity brief card (MessageSquareQuote icon) with reflectionSummary text and 2 talking points. "Tags in focus" chips. Theme-graph links: per themeGraphThreads, an article with theme label, summary, source signals, and per-link buttons that dispatch handleThemeGraphLink.

States#

  • Loading — N/A: client component with sync fixture; saved-at hydrated via useEffect against localStorage-backed readSavedAreteArtifact
  • Initial entry from ?entry=<id> — if the param matches an entries[i].entry.entryId, that entry is pre-selected
  • Window switched — toggling evening/weekly/open re-derives availablePrompts and resets selectedPromptId to a category-matching prompt
  • Draft capturedsetDraftCaptured(true) updates the data-arete-journal-reflection-draft-state attribute to "captured"
  • Theme-graph link to a practice — when link.targetKind === 'practice', the handler parses the ?entry=<id> from link.targetPath, sets selectedEntryId, and forces window to 'open'
  • Theme-graph link to a non-practice surfacerouter.push(buildConceptGraphDomainHref(link)) navigates to Nisaba / Veritas / Tara / Arete / Nyx based on link.targetKind
  • Desktop vs. mobile layoutuseSemanticViewport + isLaptopUp(viewport) gates the 3-column grid vs. single-column
  • Saved-at hydratedsetArtifactSavedAt reads localStorage on mount and on artifactId change
  • Error (recoverable) — relies on app-level error boundary
  • Offline — purely client; identical offline (saved artifacts live in localStorage)
  • Shell entitlement granted/domains/arete?path=/journal is Arete pro-gated; E2E upgrades the seeded account through the real admin bulk-entitlements BFF flow before hydration
  • Free-tier gated fallback — verify the shell upgrade screen for users without Arete entitlement
  • Standalone PWA — no shell chrome around the page

Interactions#

Top bar#

  • Back button (aria-label="Go back")
    • Function: calls router.back() via onBack prop
  • "Open reflection assistant" (button)
    • Function: dispatches dispatchOshunAssistantOpen with source arete-journal-reflection:<window> and a prompt that includes the window label, the selected prompt text, and the selected entry's excerpt
    • Telemetry: relies on the assistant-entry event for instrumentation

Window selector#

  • Evening / Weekly / Open (3 pill buttons)
    • Function: setWindow(option) + reset selectedPromptId to a prompt matching the new window's categories
    • Data attribute: data-arete-journal-reflection-period={option}
    • Touch target: 10px+14px padding; E2E verifies ≥ 44 px height on mobile

Reflection prompts (per availablePrompts)#

  • Prompt card (button, data-arete-journal-reflection-prompt={prompt.promptId}, data-arete-journal-reflection-prompt-selected="true|false")
    • Function: setSelectedPromptId(prompt.promptId)
    • Active style: accent border + tinted background

Compose surface#

  • Draft textarea (aria-label="Journal reflection draft", data-arete-journal-reflection-draft)
    • Function: typing updates draftText and resets draftCaptured to false
  • "Capture reflection" (button, data-arete-journal-reflection-capture)
    • Function: setDraftCaptured(true)
  • "Send to assistant" (button, data-arete-journal-reflection-assistant-handoff)
    • Function: dispatches dispatchOshunAssistantOpen with source arete-journal-reflection:assistant:<promptId> and a multi-line prompt (prompt + draft + continuity cue from selected entry)

Continuity inspector#

  • Entry selector (per entries, data-arete-journal-reflection-entry)
    • Function: setSelectedEntryId(item.entry.entryId)
    • Style: active gets accent border, includes the sentiment tone as a chip
  • Theme-graph link (per thread.links, data-arete-journal-theme-graph-action, data-arete-journal-theme-graph-action-kind)
    • Function: handleThemeGraphLink(link) — either selects an entry (when link.targetKind === 'practice') or routes via buildConceptGraphDomainHref(link)

Artifact actions (AreteArtifactActionPanel)#

  • SavehandleSaveArtifact calls saveAreteArtifact with the snapshot JSON (window, promptId, entryId, draftText, draftCaptured)
  • ExporthandleExportArtifact calls buildAreteJournalReflectionExport and triggerAreteArtifactDownload
  • Share disabled stateAreteArtifactActionPanel renders the journal_reflection share action disabled under the current policy

Data & contracts#

  • Reads: imports from @oshun/domain-arete: buildAreteJournalReflectionExport, buildAreteCoachingSummaryShellModel, buildCanonicalAreteCoachingSummary, buildAreteThemeConceptGraphReflections, buildAreteThemeConceptGraphThreads, getAreteArtifactPolicy. Types: AreteCanonicalCoachingSummary, AreteJournalEntry, AreteJournalPrompt, AreteJournalPromptCategory, AreteThemeConceptGraphLink. In-file fixture prompts (5 entries) and entries (multiple ReflectionEntryModels).
  • Writes: saveAreteArtifact (localStorage); no BFF write
  • Realtime: none
  • Caching: client bundle + localStorage for saved artifacts
  • Auth/role check: app-level middleware

Cross-references#

  • Domain hub: arete.md
  • Related polished surfaces: arete-review.md (weekly review uses similar prompt scaffolding), arete-coaching.md (letter from Lilith)
  • Sibling internal alternates:
  • Cross-domain: theme-graph links route to Nisaba / Veritas / Tara / Arete / Nyx via buildConceptGraphDomainHref
  • Component source: apps/oshun/web/src/components/domains/arete/AreteJournalReflectionWorkspace.tsx
  • Library: libs/domain-arete/* (resolves as @oshun/domain-arete)
  • Assistant dispatch: apps/oshun/web/src/navigation/assistant-entry.ts
  • Artifact support: apps/oshun/web/src/components/domains/arete/areteArtifactSupport.ts, AreteArtifactActionPanel.tsx

E2E coverage#

  • apps/oshun/web/e2e/arete-journal-reflection-workspace.spec.ts covers the dedicated route's prompt selection, draft capture, top-bar and compose assistant event payloads, localStorage save, saved-at hydration after reload, entry selection, disabled share control, and Markdown export.
  • The same spec covers shell hydration through /domains/arete?origin=home&path=%2Fjournal using the real admin bulk-entitlements BFF flow to grant pro access before asserting the journal workspace, all three reflection windows, and the shell back button.
  • Theme-graph coverage includes ?entry=entry-language-fragile, the in-place practice link back to the architecture reflection, and the Tara ritual route generated by buildConceptGraphDomainHref.
  • Responsive coverage asserts the desktop three-column lane alignment, mobile stacked order, no horizontal overflow, and minimum touch targets for the window selector.

Open questions / known gaps#

  • Direct /domains/arete/journal still renders as a bare internal page; shell chrome is covered separately through /domains/arete?path=/journal
  • Saved artifacts live in localStorage only; no server persistence
  • dispatchOshunAssistantOpen event payloads are verified; separately verify how the receiving assistant surface (Sophia / Lilith) consumes the seeded prompt
  • Theme-graph E2E verifies the practice link and Tara ritual route; confirm the remaining Nisaba, Veritas, Arete, and Nyx route mappings against actual route slugs
  • In-file entries and prompts arrays are fixtures; no BFF wiring
  • How does this surface relate to the polished /arete "Journal" tab (the polished room's LSubNav "Journal" item carries href="/domains/arete/journal" and navigates here)