Tilstandsmaskiner
Impulse AI bruker to fundamentalt forskjellige tilnærminger til tilstandshhandtering, avhengig av kontekst. API-laget bruker imperative tilstandsmaskiner for a sikre gyldige overganger. SwiftUI-laget bruker derivert tilstand via computed enums for reaktiv UI-oppdatering.
API-lagets tilstandsmaskin
heading.anchorLabelUnified Session Machine
heading.anchorLabelSesjonsoverganger styres av en enkel, eksplisitt tilstandsmaskin i apps/api/src/state-machines/session-machine.ts.
Maskinen definerer gyldige overganger for 5-stegs transformasjonssesjoner — identisk struktur for bade contraction og expansion.
Sesjonsstatus
heading.anchorLabelcreated ──[START]──> active ──[COMPLETE]──> completed │ └──[ADVANCE_STEP]──> active (neste steg)| Status | Beskrivelse |
|---|---|
created | Sesjon opprettet, ikke startet |
active | Bruker er i sesjonen |
completed | Alle 5 steg fullfort |
abandoned | Sesjon forlatt (timeout eller brukervalg) |
Events
heading.anchorLabel| Event | Fra | Til | Effekt |
|---|---|---|---|
START | created | active | Setter current_step til 1 |
ADVANCE_STEP | active | active | Oker current_step med 1 (1->2->3->4->5) |
COMPLETE | active (steg 5) | completed | Markerer sesjon som fullfort |
De 5 stegene
heading.anchorLabel| Nr | Engelsk navn | Norsk navn | Formaal |
|---|---|---|---|
| 1 | FEEL | Kjenn i kroppen | Somatisk bevissthet |
| 2 | OWN | Erkjenn energi-investeringen | Erkjennelse |
| 3 | SHIFT | Tilstand definerer virkelighet | Filosofisk skifte |
| 4 | ENVISION | Se for deg ny tilstand | Visualisering |
| 5 | RELEASE | Loskoble og affirmer | Losrivelse |
Valideringsfunksjoner
heading.anchorLabelTilstandsmaskinen eksponerer rene funksjoner for validering:
// Sjekk om en overgang er gyldigcanTransition(currentStatus: SessionStatus, event: SessionEvent): boolean
// Hent neste status etter et event (null = ugyldig)getNextStatus(currentStatus: SessionStatus, event: SessionEvent): SessionStatus | null
// Sjekk om steg kan avanserescanAdvanceStep(currentStep: number | null): boolean
// Sjekk om sesjon kan fullføres (ma vaere pa steg 5)canComplete(currentStep: number | null): booleanKomplett validering
heading.anchorLabelvalidateTransition() kombinerer alle sjekker og returnerer et strukturert resultat:
interface ValidationResult { valid: boolean nextStatus: SessionStatus | null nextStep: number | null error?: string}
// Eksempelconst result = validateTransition( { id: '...', status: 'active', currentStep: 3, classification: 'contraction', ... }, 'ADVANCE_STEP')// → { valid: true, nextStatus: 'active', nextStep: 4 }
const invalid = validateTransition( { id: '...', status: 'active', currentStep: 3, classification: 'contraction', ... }, 'COMPLETE')// → { valid: false, error: 'Cannot complete session from step 3 (must be on step 5)' }Stegformaal per klassifisering
heading.anchorLabelAI-konteksten for hvert steg varierer basert pa om sesjonen er contraction eller expansion:
getStepPurpose(step: number, classification: SessionClassification): string| Steg | Contraction-formaal | Expansion-formaal |
|---|---|---|
| 1 | Legg merke til de fysiske sansningene | Legg merke til hva du foler nar du tar intensjonen pa alvor |
| 2 | Erkjenn at du investerer energi i frykten | Klar opp hvorfor dette betyr noe for deg |
| 3 | Din indre tilstand skaper din virkelighet | Din indre tilstand skaper din virkelighet |
| 4 | Visualiser frihet — hvor i kroppen foler du det? | Se hvem du er nar dette allerede er sant |
| 5 | Slipp frykten. Bekreft din nye sannhet | Slipp behovet for kontroll. Stol pa prosessen |
SwiftUI: Derivert tilstand
heading.anchorLabelProblemet
heading.anchorLabelI SwiftUI oppdaterer flere @Observable stores seg uavhengig og asynkront. If-else-kjeder i view body er ordensavhengige, ikke-uttommende, og produserer race condition-bugs.
Losningen: Computed enums
heading.anchorLabelUtled visningstilstand gjennom en computed enum — aldri gjennom imperative overganger.
// 1. Enum -- en fil, fanger ALLE mulige tilstanderenum FooContentState { case loading case empty case content case error(String)}
// 2. Computed property -- leser @Observable storesprivate var contentState: FooContentState { if store.isLoading && !store.hasData { return .loading } if let error = store.error, !store.hasData { return .error(error) } if !store.hasData { return .empty } return .content}
// 3. Exhaustive switch -- kompilatoren sikrer fullstendighetvar body: some View { switch contentState { case .loading: ProgressView() case .empty: EmptyState() case .content: ContentView() case .error(let msg): ErrorView(msg) }}Egenskaper
heading.anchorLabel| Egenskap | Beskrivelse |
|---|---|
| Reaktiv | Computed property leser @Observable stores — SwiftUI reevaluerer automatisk |
| Uttommende | Ny enum-case gir kompilerfeil overalt switchen brukes |
| Ingen overganger | Tilstand utledes, ikke muteres — ingen race conditions |
| Prioritet kodet | Forste matchende betingelse vinner |
Aktive implementasjoner
heading.anchorLabel| View | Enum | Cases | Fil |
|---|---|---|---|
| ImpulseDetailView | SessionSectionState | 5 | Features/Impulses/SessionSectionState.swift |
| InsightsView | InsightsContentState + InsightsProductVisibility | 3 + 3 | Features/Insights/InsightsContentState.swift |
| JourneyView | JourneyContentState | 6 | Features/Journey/JourneyContentState.swift |
| UnifiedSessionView | SessionLayer | 8 | Definert inline |
Kontrast: API vs SwiftUI
heading.anchorLabelDe to tilnaermingene ligner, men er fundamentalt forskjellige i kontrollmodell:
| Dimensjon | API (session-machine) | SwiftUI (computed enum) |
|---|---|---|
| Kontroll | Kaller kontrollerer mutasjon | Stores muterer uavhengig |
| Validering | canTransition() guards | Kompilator-hhandheving |
| Tilstand | Eksplisitt mutert | Utledet fra stores |
| Overganger | Event -> ny status | Ingen — reevaluering |
| Feilhhandtering | Runtime-feil (ugyldig overgang) | Compile-time feil (manglende case) |
| Race conditions | Forhindret av guards | Forhindret av design |
Hvorfor to tilnaerminger?
heading.anchorLabelAPI-laget trenger eksplisitte overganger fordi:
- Flere klienter kan sende samtidige foresporsler
- Databasetilstand ma vaere konsistent
- Ugyldige overganger ma avvises med forklarende feilmelding
- Audit trail krever sporbare tilstandsendringer
SwiftUI-laget trenger derivert tilstand fordi:
- Stores oppdateres uavhengig (HomeStore, SessionStore, InsightsStore)
- Data ankommer til forskjellige tidspunkt (loading -> data -> error)
- UI ma alltid vise noe — aldri en ugyldig mellomtilstand
- View body ma vaere uttommende og deklarativ
Prosessuell status (impulser)
heading.anchorLabelI tillegg til sesjonsmaskinen har impulser en enkel kanban-status:
captured ──> transforming ──> transformed| Status | Beskrivelse |
|---|---|
captured | Impuls fanget, ingen sesjon startet |
transforming | Aktiv sesjon pagar |
transformed | Minst en sesjon fullfort |
Denne statusen oppdateres av API-et nar sesjoner starter og fullføres, og driver filtreringslogikk i Journey-viewet.
AI Workload Status
heading.anchorLabelAI-operasjoner (klassifisering, veiledning, oppsummering) har sin egen livssyklus:
idle ──> pending ──> processing ──> complete └──> failed| Status | Beskrivelse |
|---|---|
idle | Ingen AI-operasjon etterspurt |
pending | Jobb lagt i BullMQ-koen |
processing | AI-kall under utforelse |
complete | Resultat lagret i database |
failed | AI-kall feilet (retry uttomt) |
Denne statusen brukes pa:
impulses.ai_classification_status— klassifisering av impulsersessions.step_N_ai_status— veiledning per steg (1-5)sessions.ai_template_status— aggregert status for alle stegcheck_ins.ai_assessment_status— sjekk-inn vurdering