S Sulci Early Access

App Developer Docs

Features

Chat

The chat interface is a single-page, in-memory conversation. Nothing is stored — the conversation starts fresh on every session.

How it works

  1. The user types a message and sends (Enter or the send button).
  2. The frontend appends the user message to the local messages array.
  3. A POST /api/chat request is sent with the full conversation history.
  4. The backend prepends the system prompt and calls the OpenAI completions API.
  5. The reply is appended to the messages array and rendered.

Because the full history is sent on every request, the AI has context for the whole conversation. Because nothing is persisted, there’s no privacy exposure and no cleanup logic needed.

Shift+Enter

Pressing Enter sends the message. Shift+Enter inserts a newline. The textarea auto-scrolls to the bottom after each message.

State Check integration

When the user attaches a state check result from the State Check tab, it prepends a formatted block to the input:

[State Check]
Arousal: Mostly agree (4/5)
Valence: Neutral (3/5)
Approach: Mostly agree (4/5)
Agency: Neutral (3/5)
Bandwidth: Mostly disagree (2/5)
Social: Neutral (3/5)
Temporal: Mostly agree (4/5)
Meaning: Neutral (3/5)

The user then types their question below the state check block and sends. The AI receives both as a single user message and uses the dimensional context to shape its response.

System prompt

The system prompt is defined in backend/src/routes/chat.js. It:

  • Describes the app’s purpose (conscious habit choice → neural change)
  • Names and defines the 8 dimensions with their 1–5 scale ends
  • Instructs the model to use state context when present and keep responses concise

Update the system prompt there to change how the AI interprets and responds.

Component

frontend/src/components/ChatView.vue — all state is local (ref). No Pinia store. Listening for sulci:attach-state is set up in onMounted and torn down implicitly when the component unmounts.


State Check

Eight questions, one per nervous system dimension. The user answers on a 1–5 scale and taps “Attach to chat →” to inject the results into the chat input.

The 8 dimensions

LabelQuestion
ArousalI feel physically activated or energized right now.
ValenceMy emotional state feels predominantly positive.
ApproachI feel drawn toward engaging with my environment.
AgencyI feel in control of what happens next.
BandwidthI have mental space to think clearly right now.
SocialI feel open to connecting with other people.
TemporalI can think ahead and imagine future possibilities.
MeaningMy current experience feels meaningful or purposeful.

The 5-point scale

ValueLabel
1Totally disagree
2Mostly disagree
3Neutral
4Mostly agree
5Totally agree

This is a 1–5 self-report scale. It maps roughly to the 1–7 scale used in the platform’s dimensional model (the SpiderChart on the science pages), but is simpler for everyday use. The mapping is intentionally loose — the goal is directional context for the AI, not precise measurement.

Attach flow

The “Attach to chat →” button is disabled until all 8 questions are answered. When tapped:

  1. Formats results as a text block (see above).
  2. Dispatches window.dispatchEvent(new CustomEvent('sulci:attach-state', { detail: text })).
  3. Navigates to #/chat.

The Chat view receives the event and prepends the block to the current input. If the user already has text in the input, the state check is prepended with a blank line separator.

The Reset button clears all 8 answers. State check results are not persisted.

Component

frontend/src/components/StateCheckView.vue — all state is local. Dimensions and answer labels are defined as constants at the top of the component’s <script setup> block. To add, remove, or reword a dimension, edit those constants.


Panic

The Panic view (frontend/src/components/PanicView.vue) guides users through a panic moment — naming what they’re feeling, recognizing the cycle it belongs to, and choosing to stay in the cycle rather than exit.

Flow

  1. Entry gate — User taps PANIC MOMENT. A full-screen breathing animation runs (4 press-and-hold rounds, inhale 3s / exhale 6s). A Skip button in the top-right bypasses it.
  2. Chat — After the animation, the user describes what they’re feeling. The AI responds via a streaming connection to POST /api/panic/:id/message.
  3. Recognition panel — After the first AI reply, a collapsible panel opens below the messages (capped at 50vh so the response stays visible). The user can select:
    • Panic type — one of 4 types from lib/panicTypes.js
    • Goal — an existing goal or a new one created inline
    • Cycle phase — a phase from the selected goal’s generated cycles
  4. Reply — The “Reply” button in the panel sends the current selections as a plain-text context message to the AI and gets another response. After Reply the panel collapses and stays collapsed for all future AI turns (recognitionUserCollapsed flag). The user can still expand it manually via the “Recognition (click to expand)” toggle.
  5. Stay in the cycle — A persistent button outside the recognition panel. Always visible once the AI has responded. Resolves the panic moment in the backend and plays the ascend animation.
  6. Resolved — “Cycle recognized” card with option to start a new panic moment.

Key state

RefPurpose
activeMomentCurrent open panic moment from the backend (null until first message sent)
recognitionOpenWhether the recognition panel is expanded
recognitionUserCollapsedSet true when user hits Reply; stops future AI replies from auto-reopening the panel. Reset on new moment.
entryModeTrue until user completes or skips the breathing animation

Component

frontend/src/components/PanicView.vue — uses useBrainScene (Three.js brain in the header) and usePanicAnimations (breathing and ascend overlays). Both are mocked in tests. Panic moment state (messages, selections) is in-memory only; the backend persists the moment record but not the full message history beyond the session.