Telegram Mini-App · Surface walkthrough

Telegram Mini App · Per-surface detail

A per-surface walkthrough of the Telegram Mini-App telegram-miniapp surface: layout, states, interactions, data, and cross-references.

drafted
8sections6 minread

On this page

Context. surface telegram-miniapp · domain discovery · route /[surface] · auth none · source apps/oshun/telegram-miniapp/src/app/[surface]/page.tsx

Last walked.

Purpose#

The dynamic per-surface view renders one of seven curated Oshun surfaces inside Telegram. The route param surface is validated against SURFACE_SLUGS; an unknown slug falls back to 'today' (the Tara ritual). All page content comes from the module-level surfaceContent map — the page itself does no fetch and reads no session.

Source: apps/oshun/telegram-miniapp/src/app/[surface]/page.tsx (server component) which renders the client component apps/oshun/telegram-miniapp/src/app/[surface]/SurfaceWorkspace.tsx. The seven slugs are statically generated via generateStaticParams().

Entry points#

  • From / (home) via a surfaceLink anchor — href /${slug}/
  • Direct URL — /today/, /sophia/, /veritas/, /nyx/, /arete/, /nisaba/, /illustration/ (all pre-rendered by generateStaticParams)
  • Any other /[surface]/ value renders the Tara ritual ('today') fallback rather than a 404 (isSurfaceSlug guard in page.tsx)

Layout regions#

<main className="miniShell surfaceShell"> with --surface-accent set from surface.accent:

  • Header<a href="/" className="backLink">Back</a> (above the header block), then <header className="surfaceHeader">: eyebrow (surface.eyebrow), h1 (surface.title), deck paragraph (surface.deck), and a <div className="metaStrip"> showing three spans: surface.domain, Telegram Mini App, Curated only.
  • Main<div className="surfaceGrid"> containing <section className="workspacePanel" aria-label="${surface.title} workspace">, whose body is dispatched by renderSurfacePayload(surface, setStatus) per-slug (see Interactions).
  • Aside / secondary panel<aside className="inspectorPanel" aria-label="Evidence and provenance">: the EvidenceInspector (Evidence tablist + citation drill-down), a provenanceBlock (h2 "Provenance" + list of surface.provenance), and a disclosure paragraph (surface.disclosure).
  • Footer / sticky bottom<div className="actionBar" aria-label="Available actions"> with three buttons (primary action, secondary action, View provenance), followed by <p className="statusLine" role="status"> echoing the current status (initial value Ready).

States#

  • Loading — N/A: content is read synchronously from surfaceContent; no async fetch, no loading state.
  • Empty — N/A: every slug maps to a fully populated surfaceContent entry; evidence/provenance arrays are non-empty in data.
  • Populated (short) — the only state: the per-slug payload, evidence tabs (2–3 items), and provenance list (3 items) all render from data.
  • Populated (long) — N/A: per-surface content is fixed-size in surface-data.ts; no list grows unbounded.
  • Error (recoverable) — N/A: no query to fail.
  • Error (unrecoverable) — N/A: unknown slugs fall back to 'today' rather than erroring; no server data dependency to 500.
  • Offline — page renders from bundled data once cached; action buttons only mutate local status text (no network), so they work offline; no SW configured (see open questions).
  • Gated — N/A: no auth/role/flag gate; reads no session or initData.
  • Standalone PWA — N/A: hosted inside the Telegram WebApp container; no manifest/SW in the app (see open questions).
  • Slug fallback — any non-SURFACE_SLUGS param renders the Tara ritual ('today') surface (verify via e.g. /unknown/).

Interactions#

Shared chrome (present on every surface):

  • Back (link)

    • Function: navigates to / (home surface list).
    • Keyboard: native anchor; Tab-reachable; Enter activates.
    • Screen reader: announces "Back, link".
    • Touch target: per .backLink CSS; not asserted in code.
    • Mobile (≤ 640 px): no code-level delta.
    • Disabled when: never.
    • Offline behavior: standard navigation.
    • Telemetry: None.
  • Evidence tab (button, role="tab", one per surface.evidence item)

    • Function: sets activeEvidenceId; the citation drill-down below updates to that item's source, excerpt, and Open citation link.
    • Keyboard: button in a role="tablist"; Tab/Enter/Space activate.
    • Screen reader: announces the evidence label; selected tab carries aria-selected.
    • Disabled when: never.
    • Offline behavior: local state only; no network.
    • Telemetry: None.
  • Open citation (link, inside the active citation drill-down)

    • Function: anchor to activeEvidence.href (e.g. /nisaba/passages/attention-context) — an Oshun-web deep path, not a Mini App route.
    • Disabled when: never.
    • Telemetry: None.
  • Primary action (button, actionBar, label = surface.primaryAction)

    • Function: sets status to ${surface.primaryAction} queued. No network write; only the role="status" line changes.
    • Telemetry: None.
  • Secondary action (button, actionBar, label = surface.secondaryAction)

    • Function: sets status to ${surface.secondaryAction} ready.
    • Telemetry: None.
  • View provenance (button, actionBar)

    • Function: sets status to Provenance inspector open (the provenance list is already always visible in the aside; this only updates status text).
    • Telemetry: None.

Per-surface workspace body (dispatched by renderSurfacePayload):

  • today — Tara ritual (TaraRitual)

    • Next breath cue (button): advances cueIndex cyclically through payload.breathCues; sets status Breath cue advanced.
    • Play / Pause narration (button): toggles narrationPlaying; status Narration playing / Narration paused. No audio element is mounted (state only).
    • Complete ritual (button): sets complete = true; status tara.ritual.completed queued; reveals "Reflection capture is ready."
    • Renders the timer face (payload.durationLabel, payload.templateId) and the payload.steps ordered list.
  • sophia — Sophia Q&A (SophiaQa)

    • Save to notebook (button): status ${payload.notebookTarget} saved.
    • Renders question, answer blockquote, and a fact row (confidenceBand, evidencePackId).
  • veritas — Veritas claim (VeritasClaim)

    • Regret flag (button): sets flagged = true; status Regret flag queued for review; label flips to Regret flag queued and class to dangerButton.
    • Renders claim, confidence badge, summary, and split Sources / Counterclaims lists.
  • nyx — Nyx sky (NyxSky)

    • Layer button (button, one per payload.sceneLayers, segmentedControl): sets layerIndex; status ${label} layer selected; detail text below updates.
    • Add to calendar (button): status ${payload.calendarEntry} added.
    • Renders the decorative skyScene (role="img") and event fact row (observationWindow, qualityBand).
  • arete — Arete check-in (AreteCheckIn)

    • Tick habit / Habit ticked (button, aria-pressed): toggles habitDone; status Habit tick saved / Habit tick cleared.
    • Mood button (button, one per payload.moods, segmentedControl): sets mood; status ${mood} mood selected.
    • Renders the humane recoveryState block (payload.streak, payload.recoveryState).
  • nisaba — Nisaba reader (NisabaReader)

    • Edition button (button, one per payload.editions, segmentedControl): sets editionIndex; status ${label} edition selected; passage + note update.
    • Lexicon term button (button, one per payload.lexicon, segmentedControl): sets termIndex; status ${term} lexicon entry selected; gloss line updates.
    • Annotation (textarea): defaultValue = payload.annotationPrompt; uncontrolled, no onChange handler in code.
    • Save annotation (button): status Annotation saved to notebook.
  • illustration — Illustration card (IllustrationCard)

    • Request generation / Generation queued (button): sets requested = true; status Generation request queued with lineage; label flips.
    • Renders decorative illustrationPreview (role="img"), workflow class, request title, prompt, policyState, and the payload.lineage ordered list.

Data & contracts#

  • Reads: surfaceContent[slug] from ../surface-data (module constant) — one of seven SurfaceContent union members. No network read.
  • Writes: None over the network. Every button updates only local React state (status, and per-surface toggles); no BFF//api call exists in this app.
  • Realtime: None.
  • Caching: statically generated per slug via generateStaticParams() (Next.js App Router); no SWR/ISR/SW-cache configured.
  • Auth/role check: None. No middleware; the page reads no session and no Telegram initData.

Cross-references#

  • Sibling route: home.md (/ — the surface list landing)
  • Server page: apps/oshun/telegram-miniapp/src/app/[surface]/page.tsx
  • Client workspace: apps/oshun/telegram-miniapp/src/app/[surface]/SurfaceWorkspace.tsx
  • Surface data + per-slug payload types: apps/oshun/telegram-miniapp/src/app/surface-data.ts
  • E2E coverage: apps/oshun/telegram-miniapp/e2e/miniapp.spec.ts (per-surface tests for sophia, today, veritas, nyx, arete, plus the landing list)
  • Unused Telegram bridge helper: apps/oshun/telegram-miniapp/src/lib/telegram-webapp.ts

Open questions / known gaps#

  • All action buttons are status-text-only stubs at the app level — they mutate local React state but issue no BFF write (no tara.ritual.completed emit, no notebook save, no calendar add, no regret-flag submission, no generation request). The data layer the labels imply (notebook/calendar/ review/generation) is not reachable from this Mini App in code. Confirm whether real BFF wiring is planned and which contracts/routes back each action.
  • The Telegram WebApp runtime is NOT wired here either: no telegram-web-app.js, no window.Telegram, and no use of src/lib/telegram-webapp.ts (theme params, MainButton/BackButton, HapticFeedback, initData header). The actionBar buttons could be promoted to the Telegram MainButton, and the Back link to BackButton, but neither is done in code. Confirm intended wiring.
  • The Nisaba Annotation textarea is uncontrolled (defaultValue, no onChange), so edits are never read by Save annotation (which emits a fixed status string). Confirm whether annotation capture is intended.
  • No telemetry is emitted on any interaction; confirm event names if surface-open / action analytics are required.
  • No PWA manifest/SW, so Offline / Standalone-PWA states cannot be produced here; confirm this is intentional for a Telegram-hosted Mini App.