diff --git a/docs/src/content/docs/concepts-actions.md b/docs/src/content/docs/concepts-actions.md
new file mode 100644
index 00000000..11d461ee
--- /dev/null
+++ b/docs/src/content/docs/concepts-actions.md
@@ -0,0 +1,538 @@
+---
+title: Understanding Actions
+tags: concept
+---
+
+# Understanding Actions
+
+Actions are side effects that occur during transitions. They let you update context, make API calls, update the DOM, log events, or perform any other effects when moving between states.
+
+In finite state machines, state transitions should be pure and declarative. Actions provide a controlled way to handle the inevitable side effects your application needs while keeping your state machine logic clean and predictable.
+
+## Why Actions Matter
+
+State machines are about defining valid states and transitions. But real applications need to do more than just change state - they need to update data, call APIs, notify users, and interact with the outside world. Actions provide a structured way to handle these side effects.
+
+```js
+// Without actions - side effects mixed with state logic
+if (currentState === 'idle') {
+ currentState = 'loading';
+ showSpinner();
+ trackEvent('loading_started');
+ fetchData().then(data => {
+ currentState = 'loaded';
+ hideSpinner();
+ });
+}
+
+// With actions - clean separation
+idle: state(
+ transition('fetch', 'loading',
+ action(showSpinner),
+ action(trackEvent)
+ )
+)
+```
+
+## Defining Actions
+
+Actions are defined using the `action` function and can be added to transitions:
+
+```js
+import { createMachine, state, transition, action } from 'robot3';
+
+const logTransition = action((ctx, event) => {
+ console.log('Transitioning with:', event);
+ return ctx; // Return unchanged context
+});
+
+const machine = createMachine({
+ idle: state(
+ transition('start', 'running', logTransition)
+ ),
+ running: state()
+});
+```
+
+## Action Functions
+
+Action functions receive two arguments:
+- **Context**: The current machine context
+- **Event**: The event that triggered the transition
+
+They must return the context (modified or unchanged):
+
+```js
+const updateUser = action((ctx, event) => {
+ return {
+ ...ctx,
+ user: event.user,
+ lastUpdated: Date.now()
+ };
+});
+
+const machine = createMachine({
+ idle: state(
+ transition('login', 'authenticated', updateUser)
+ ),
+ authenticated: state()
+});
+```
+
+## Types of Actions
+
+### Context Updates
+
+The most common use of actions is updating the machine's context:
+
+```js
+const increment = action((ctx) => ({
+ ...ctx,
+ count: ctx.count + 1
+}));
+
+const setUser = action((ctx, event) => ({
+ ...ctx,
+ user: event.data
+}));
+
+const machine = createMachine({
+ idle: state(
+ transition('increment', 'idle', increment),
+ transition('login', 'authenticated', setUser)
+ ),
+ authenticated: state()
+}, () => ({
+ count: 0,
+ user: null
+}));
+```
+
+### Side Effects
+
+Actions can perform side effects without modifying context:
+
+```js
+const logEvent = action((ctx, event) => {
+ console.log('Event occurred:', event.type);
+ return ctx; // Context unchanged
+});
+
+const showNotification = action((ctx, event) => {
+ alert(`Status: ${event.message}`);
+ return ctx;
+});
+
+const trackAnalytics = action((ctx, event) => {
+ analytics.track('state_changed', {
+ from: ctx.previousState,
+ to: event.type
+ });
+ return ctx;
+});
+```
+
+### DOM Manipulation
+
+Actions can update the UI directly:
+
+```js
+const showSpinner = action((ctx) => {
+ document.querySelector('#spinner').style.display = 'block';
+ return ctx;
+});
+
+const hideSpinner = action((ctx) => {
+ document.querySelector('#spinner').style.display = 'none';
+ return ctx;
+});
+
+const updateUI = action((ctx) => {
+ document.querySelector('#status').textContent = ctx.status;
+ return ctx;
+});
+```
+
+### API Calls
+
+Actions can trigger API calls (though consider using `invoke` for async operations):
+
+```js
+const saveToAPI = action((ctx) => {
+ fetch('/api/save', {
+ method: 'POST',
+ body: JSON.stringify(ctx.data)
+ });
+ return ctx;
+});
+```
+
+## The `reduce` Helper
+
+Robot provides a `reduce` helper as a shorthand for context-updating actions:
+
+```js
+import { reduce } from 'robot3';
+
+// Using action
+const increment = action((ctx) => ({ ...ctx, count: ctx.count + 1 }));
+
+// Using reduce (same thing, less verbose)
+const increment = reduce((ctx) => ({ ...ctx, count: ctx.count + 1 }));
+
+// Common usage
+idle: state(
+ transition('increment', 'idle',
+ reduce((ctx) => ({ ...ctx, count: ctx.count + 1 }))
+ )
+)
+```
+
+## Multiple Actions
+
+You can chain multiple actions - they execute in sequence:
+
+```js
+const validate = reduce((ctx, ev) => ({
+ ...ctx,
+ isValid: ev.data.length > 0
+}));
+
+const trackSubmit = action((ctx) => {
+ analytics.track('form_submitted');
+ return ctx;
+});
+
+const clearForm = reduce((ctx) => ({
+ ...ctx,
+ data: ''
+}));
+
+const machine = createMachine({
+ editing: state(
+ transition('submit', 'submitted',
+ validate, // 1. Validate data
+ trackSubmit, // 2. Track event
+ clearForm // 3. Clear form
+ )
+ ),
+ submitted: state()
+});
+```
+
+Actions execute in order, and each action receives the context returned by the previous action.
+
+## Action Execution Order
+
+When a transition occurs, this is the execution order:
+
+1. **Event received**: Machine receives an event
+2. **Transition found**: Matching transition in current state
+3. **Guards checked**: All guards must pass (see [Guards](/docs/concepts-guards/))
+4. **Actions execute**: Actions run in sequence
+5. **Context updated**: New context from actions takes effect
+6. **State changes**: Machine transitions to target state
+7. **Listeners notified**: Change listeners called with new state and context
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'processing',
+ guard((ctx) => ctx.isValid), // 1. Guard checks
+ action((ctx) => { // 2. First action
+ console.log('First:', ctx.count); // Shows 0
+ return { ...ctx, count: 1 };
+ }),
+ action((ctx) => { // 3. Second action
+ console.log('Second:', ctx.count); // Shows 1
+ return { ...ctx, count: 2 };
+ })
+ )
+ ),
+ processing: state() // 4. New state with count: 2
+}, () => ({ count: 0, isValid: true }));
+```
+
+## Actions vs Invoke
+
+Choose between actions and invoke based on your needs:
+
+### Use Actions When:
+- Making synchronous updates
+- Performing immediate side effects
+- Updating context based on event data
+- No need to wait for completion
+
+```js
+// ✅ Good use of actions
+idle: state(
+ transition('update', 'idle',
+ reduce((ctx, ev) => ({ ...ctx, value: ev.value }))
+ )
+)
+```
+
+### Use Invoke When:
+- Handling async operations
+- Waiting for promises to resolve
+- Need to transition based on success/failure
+- Want automatic error handling
+
+```js
+// ✅ Good use of invoke
+idle: state(
+ transition('fetch', 'loading')
+),
+loading: invoke(fetchData,
+ transition('done', 'loaded'),
+ transition('error', 'error')
+)
+```
+
+## Action Patterns
+
+### Validation and Enrichment
+
+```js
+const validateAndEnrich = reduce((ctx, ev) => {
+ const cleaned = ev.data.trim();
+ const isValid = cleaned.length > 0;
+ return {
+ ...ctx,
+ data: cleaned,
+ isValid,
+ validatedAt: Date.now()
+ };
+});
+
+idle: state(
+ transition('submit', 'validating', validateAndEnrich)
+)
+```
+
+### Logging and Analytics
+
+```js
+const trackTransition = action((ctx, ev) => {
+ console.log(`[${new Date().toISOString()}] ${ev.type}`);
+ analytics.track('transition', {
+ event: ev.type,
+ state: ctx.currentState
+ });
+ return ctx;
+});
+
+// Add to every important transition
+transition('submit', 'processing', trackTransition)
+```
+
+### Error Handling
+
+```js
+const captureError = reduce((ctx, ev) => ({
+ ...ctx,
+ error: ev.error,
+ errorTime: Date.now(),
+ errorMessage: ev.error.message
+}));
+
+const clearError = reduce((ctx) => ({
+ ...ctx,
+ error: null,
+ errorMessage: null
+}));
+
+error: state(
+ transition('retry', 'loading', clearError)
+)
+```
+
+### State Persistence
+
+```js
+const saveToLocalStorage = action((ctx) => {
+ localStorage.setItem('appState', JSON.stringify(ctx));
+ return ctx;
+});
+
+// Save context after important transitions
+transition('save', 'saved', saveToLocalStorage)
+```
+
+## Best Practices
+
+### Keep Actions Pure When Possible
+
+Prefer pure context updates over side effects:
+
+```js
+// ✅ Good - pure context update
+const setUser = reduce((ctx, ev) => ({
+ ...ctx,
+ user: ev.user
+}));
+
+// ⚠️ OK but not ideal - side effect
+const setUserAndNotify = action((ctx, ev) => {
+ showNotification('User updated'); // Side effect
+ return { ...ctx, user: ev.user };
+});
+```
+
+### One Responsibility Per Action
+
+Keep actions focused on a single task:
+
+```js
+// ❌ Bad - doing too much
+const megaAction = action((ctx, ev) => {
+ console.log('Event:', ev);
+ localStorage.setItem('data', ctx.data);
+ analytics.track('event');
+ document.title = ctx.page;
+ return { ...ctx, processed: true };
+});
+
+// ✅ Better - separate concerns
+const logEvent = action((ctx, ev) => { /* ... */ });
+const persistData = action((ctx) => { /* ... */ });
+const trackEvent = action((ctx) => { /* ... */ });
+const updateTitle = action((ctx) => { /* ... */ });
+const markProcessed = reduce((ctx) => ({ ...ctx, processed: true }));
+
+transition('submit', 'processing',
+ logEvent,
+ persistData,
+ trackEvent,
+ updateTitle,
+ markProcessed
+)
+```
+
+### Avoid Async Operations in Actions
+
+Don't return promises from actions. Use `invoke` instead:
+
+```js
+// ❌ Bad - async action
+const fetchUserBad = action(async (ctx) => {
+ const user = await fetch('/api/user').then(r => r.json());
+ return { ...ctx, user };
+});
+
+// ✅ Good - use invoke
+loading: invoke(fetchUser,
+ transition('done', 'loaded')
+)
+```
+
+### Return Context
+
+Always return context from actions, even if unchanged:
+
+```js
+// ❌ Bad - forgot to return context
+const logBad = action((ctx) => {
+ console.log(ctx);
+ // Missing return!
+});
+
+// ✅ Good - always return context
+const logGood = action((ctx) => {
+ console.log(ctx);
+ return ctx;
+});
+```
+
+## Testing Actions
+
+Actions are easy to test since they're just functions:
+
+```js
+const increment = reduce((ctx) => ({
+ ...ctx,
+ count: ctx.count + 1
+}));
+
+// Test without a machine
+const ctx = { count: 5 };
+const result = increment(ctx, {});
+console.assert(result.count === 6);
+```
+
+## Common Pitfalls
+
+### Mutating Context
+
+Never mutate context directly:
+
+```js
+// ❌ Bad - mutating context
+const badAction = action((ctx) => {
+ ctx.count++; // Mutation!
+ return ctx;
+});
+
+// ✅ Good - returning new context
+const goodAction = reduce((ctx) => ({
+ ...ctx,
+ count: ctx.count + 1
+}));
+```
+
+### Side Effects Without Returning Context
+
+Don't forget to return context after side effects:
+
+```js
+// ❌ Bad
+const badSideEffect = action((ctx) => {
+ console.log('Event!');
+ // Forgot to return ctx!
+});
+
+// ✅ Good
+const goodSideEffect = action((ctx) => {
+ console.log('Event!');
+ return ctx; // Always return
+});
+```
+
+### Complex Business Logic
+
+Keep complex logic out of actions when possible:
+
+```js
+// ❌ Bad - complex logic in action
+const complexAction = action((ctx, ev) => {
+ let newValue = ctx.value;
+ if (ev.type === 'increment') {
+ newValue += ev.amount || 1;
+ } else if (ev.type === 'decrement') {
+ newValue -= ev.amount || 1;
+ }
+ // ... more logic
+ return { ...ctx, value: newValue };
+});
+
+// ✅ Better - separate functions
+function calculateNewValue(ctx, ev) {
+ if (ev.type === 'increment') return ctx.value + (ev.amount || 1);
+ if (ev.type === 'decrement') return ctx.value - (ev.amount || 1);
+ return ctx.value;
+}
+
+const simpleAction = reduce((ctx, ev) => ({
+ ...ctx,
+ value: calculateNewValue(ctx, ev)
+}));
+```
+
+## Related Topics
+
+- [Transitions](/docs/concepts-transitions/) - Where actions execute
+- [Guards](/docs/concepts-guards/) - Conditions checked before actions
+- [Events](/docs/concepts-events/) - What triggers actions
+- [action API](/docs/action/) - Technical reference for the action function
+- [reduce API](/docs/reduce/) - Technical reference for the reduce helper
+- [invoke API](/docs/invoke/) - For async operations instead of actions
diff --git a/docs/src/content/docs/concepts-events.md b/docs/src/content/docs/concepts-events.md
new file mode 100644
index 00000000..f5b9c893
--- /dev/null
+++ b/docs/src/content/docs/concepts-events.md
@@ -0,0 +1,416 @@
+---
+title: Understanding Events
+tags: concept
+---
+
+# Understanding Events
+
+Events are the triggers that cause state transitions. They represent things that happen in your application - user interactions, API responses, timers, system notifications, or any other occurrence that should cause your machine to change state.
+
+In traditional imperative code, you handle events by directly manipulating state variables. With finite state machines, events are sent to the machine, which then determines if and how the state should change based on the current state and defined transitions.
+
+## Sending Events
+
+Events are sent to a machine service using the `send` method:
+
+```js
+import { interpret } from 'robot3';
+
+const service = interpret(machine, () => {
+ console.log('State changed to:', service.machine.current);
+});
+
+// Send events to trigger transitions
+service.send('fetch');
+service.send('success');
+service.send('retry');
+```
+
+## Event Types
+
+Robot supports several types of events that work together to create responsive state machines.
+
+### User Events
+
+User events are explicitly sent via `service.send()`. These represent direct interactions or manual triggers:
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('start', 'running')
+ ),
+ running: state(
+ transition('pause', 'paused'),
+ transition('stop', 'idle')
+ ),
+ paused: state(
+ transition('resume', 'running'),
+ transition('stop', 'idle')
+ )
+});
+
+const service = interpret(machine);
+
+// User clicks a button
+button.addEventListener('click', () => {
+ service.send('start'); // Manually triggered user event
+});
+```
+
+### Immediate Transitions
+
+Immediate transitions automatically trigger when entering a state, without requiring an explicit event:
+
+```js
+import { createMachine, state, transition, immediate } from 'robot3';
+
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ ),
+ loading: state(
+ transition('done', 'validate')
+ ),
+ validate: state(
+ // Automatically transitions when entering 'validate'
+ immediate('loaded', guard(isValid)),
+ immediate('error', guard(isInvalid))
+ ),
+ loaded: state(),
+ error: state()
+});
+```
+
+### Invoked Events
+
+When using `invoke` for async operations, Robot automatically generates `done` and `error` events:
+
+```js
+import { createMachine, invoke, state, transition } from 'robot3';
+
+async function fetchUsers() {
+ const response = await fetch('/api/users');
+ return response.json();
+}
+
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ ),
+ loading: invoke(fetchUsers,
+ // 'done' event fires automatically on success
+ transition('done', 'loaded'),
+ // 'error' event fires automatically on failure
+ transition('error', 'error')
+ ),
+ loaded: state(),
+ error: state()
+});
+```
+
+## Event Payloads
+
+Events can carry additional data that's used by guards, actions, or reducers:
+
+```js
+// Simple string event
+service.send('increment');
+
+// Event object with data
+service.send({
+ type: 'login',
+ username: 'alice',
+ timestamp: Date.now()
+});
+
+// Event with complex data
+service.send({
+ type: 'updateUser',
+ data: {
+ id: 123,
+ name: 'Alice',
+ email: 'alice@example.com'
+ }
+});
+```
+
+### Accessing Event Data
+
+Event data is available in guards, actions, and reducers:
+
+```js
+import { createMachine, state, transition, guard, reduce } from 'robot3';
+
+const machine = createMachine({
+ idle: state(
+ transition('login', 'authenticating',
+ // Guard can check event data
+ guard((ctx, ev) => ev.username && ev.password),
+ // Reducer can use event data
+ reduce((ctx, ev) => ({
+ ...ctx,
+ username: ev.username
+ }))
+ )
+ ),
+ authenticating: state()
+});
+
+// Send event with data
+service.send({
+ type: 'login',
+ username: 'alice',
+ password: 'secret123'
+});
+```
+
+## Event Naming Conventions
+
+Choose event names that describe what happened, using past tense or present tense:
+
+```js
+// ✅ Good - describes what happened
+'clicked'
+'submitted'
+'success'
+'error'
+'timeout'
+'userLoggedIn'
+
+// ❌ Avoid - describes what to do
+'goToNextPage'
+'shouldLoad'
+'handleClick'
+'makeRequest'
+```
+
+This keeps your state machine declarative and easier to reason about.
+
+## Event Flow
+
+Here's how events flow through a state machine:
+
+1. **Event sent**: `service.send('eventName')` is called
+2. **Current state checked**: Machine looks at transitions in the current state
+3. **Matching transition found**: Machine finds a transition with that event name
+4. **Guards evaluated**: If guards exist, they check if transition can proceed
+5. **Actions executed**: If guards pass, actions/reducers run
+6. **State changes**: Machine transitions to target state
+7. **Listeners notified**: Any change listeners are called
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading',
+ guard((ctx) => ctx.hasToken),
+ reduce((ctx) => ({ ...ctx, loading: true }))
+ )
+ ),
+ loading: state()
+});
+
+// This triggers the entire flow above
+service.send('fetch');
+```
+
+## Event Queuing
+
+Events are processed synchronously in the order they're sent. If you send multiple events, they're handled one at a time:
+
+```js
+service.send('first'); // Processed immediately
+service.send('second'); // Processed after 'first' completes
+service.send('third'); // Processed after 'second' completes
+```
+
+This predictable ordering prevents race conditions and makes debugging easier.
+
+## Ignored Events
+
+If an event is sent but there's no matching transition in the current state, the event is ignored:
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('start', 'running')
+ // No 'stop' transition defined
+ ),
+ running: state(
+ transition('stop', 'idle')
+ )
+});
+
+const service = interpret(machine);
+
+// Machine is in 'idle' state
+service.send('stop'); // Ignored - no 'stop' transition from 'idle'
+service.send('start'); // Works - transitions to 'running'
+```
+
+This is intentional and prevents invalid state transitions. Only events defined in the current state's transitions can cause state changes.
+
+## Event-Driven Architecture
+
+Events make your state machines reactive and decoupled:
+
+```js
+// Different parts of your app can send events
+button.addEventListener('click', () => {
+ service.send('userAction');
+});
+
+socket.on('message', (data) => {
+ service.send({ type: 'messageReceived', data });
+});
+
+setTimeout(() => {
+ service.send('timeout');
+}, 5000);
+
+// The machine handles all events based on current state
+const machine = createMachine({
+ waiting: state(
+ transition('userAction', 'processing'),
+ transition('messageReceived', 'processing'),
+ transition('timeout', 'expired')
+ ),
+ processing: state(),
+ expired: state()
+});
+```
+
+## Common Event Patterns
+
+### Request/Response Pattern
+
+```js
+idle: state(
+ transition('fetch', 'loading')
+),
+loading: state(
+ transition('success', 'loaded'),
+ transition('failure', 'error')
+)
+
+// Usage:
+service.send('fetch');
+// Later, after API call:
+service.send({ type: 'success', data: result });
+// Or:
+service.send({ type: 'failure', error: err });
+```
+
+### User Interaction Pattern
+
+```js
+closed: state(
+ transition('open', 'opening')
+),
+opening: state(
+ transition('opened', 'open')
+),
+open: state(
+ transition('close', 'closing')
+),
+closing: state(
+ transition('closed', 'closed')
+)
+```
+
+### Validation Pattern
+
+```js
+editing: state(
+ transition('submit', 'validating')
+),
+validating: state(
+ transition('valid', 'submitting'),
+ transition('invalid', 'editing')
+),
+submitting: state(
+ transition('success', 'submitted'),
+ transition('error', 'editing')
+)
+```
+
+## Why Event-Based State Changes Matter
+
+### Predictable State Changes
+
+Events make all state changes explicit and traceable:
+
+```js
+// Without FSM - state changes are implicit ❌
+function onClick() {
+ isLoading = true;
+ fetchData().then(() => {
+ isLoading = false;
+ hasData = true;
+ });
+}
+
+// With FSM - state changes are explicit ✅
+function onClick() {
+ service.send('fetch');
+}
+
+// Machine handles the state changes
+loading: invoke(fetchData,
+ transition('done', 'loaded')
+)
+```
+
+### Centralized State Logic
+
+All state change logic lives in one place - your machine definition:
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('start', 'running')
+ ),
+ running: state(
+ transition('pause', 'paused'),
+ transition('stop', 'idle')
+ ),
+ paused: state(
+ transition('resume', 'running'),
+ transition('stop', 'idle')
+ )
+});
+
+// Any part of your code can send events
+service.send('start');
+service.send('pause');
+service.send('resume');
+```
+
+### Decoupled Components
+
+Components don't need to know about state logic - they just send events:
+
+```js
+// Component only sends events
+function PlayButton() {
+ return ;
+}
+
+function PauseButton() {
+ return ;
+}
+
+// Machine determines what happens
+const machine = createMachine({
+ stopped: state(transition('start', 'playing')),
+ playing: state(transition('pause', 'paused')),
+ paused: state(transition('start', 'playing'))
+});
+```
+
+## Related Topics
+
+- [Transitions](/docs/concepts-transitions/) - How events trigger state changes
+- [Guards](/docs/concepts-guards/) - Conditional event handling
+- [Actions](/docs/concepts-actions/) - Side effects when events occur
+- [invoke API](/docs/invoke/) - Automatic event generation from async operations
+- [immediate API](/docs/immediate/) - Automatic transitions without explicit events
diff --git a/docs/src/content/docs/concepts-guards.md b/docs/src/content/docs/concepts-guards.md
new file mode 100644
index 00000000..55258665
--- /dev/null
+++ b/docs/src/content/docs/concepts-guards.md
@@ -0,0 +1,394 @@
+---
+title: Understanding Guards
+tags: concept
+---
+
+# Understanding Guards
+
+Guards are conditions that must be met for a transition to occur. They let you add conditional logic to your state machines without complicating the state structure.
+
+Think of guards as gatekeepers - they decide whether a transition can proceed based on the current context and event data. This allows your state machine to make intelligent decisions while keeping your states clean and focused.
+
+## Why Guards Matter
+
+Without guards, you'd need separate states for every conditional path, leading to state explosion:
+
+```js
+// ❌ Without guards - too many states
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'checkingValid')
+ ),
+ checkingValid: state(
+ transition('isValid', 'checkingPermission')
+ ),
+ checkingPermission: state(
+ transition('hasPermission', 'submitting'),
+ transition('noPermission', 'idle')
+ )
+});
+```
+
+With guards, you can handle conditions inline:
+
+```js
+// ✅ With guards - clean and concise
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'submitting',
+ guard(isValid),
+ guard(hasPermission)
+ )
+ ),
+ submitting: state()
+});
+```
+
+## Defining Guards
+
+Guards are functions that return `true` to allow a transition or `false` to prevent it:
+
+```js
+import { createMachine, state, transition, guard } from 'robot3';
+
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'processing',
+ guard((ctx) => ctx.form.isValid)
+ )
+ ),
+ processing: state()
+});
+```
+
+## Guard Functions
+
+Guard functions receive two arguments:
+- **Context**: The current machine context
+- **Event**: The event that triggered the transition
+
+```js
+const isValidUser = (ctx, event) => {
+ return event.user && event.user.age >= 18;
+};
+
+const machine = createMachine({
+ waiting: state(
+ transition('login', 'authenticated',
+ guard(isValidUser)
+ )
+ ),
+ authenticated: state()
+}, () => ({
+ currentUser: null
+}));
+
+// Usage
+service.send({
+ type: 'login',
+ user: { name: 'Alice', age: 25 }
+});
+```
+
+## Multiple Guards
+
+You can chain multiple guards - all must return `true` for the transition to proceed:
+
+```js
+const isValid = (ctx) => ctx.input.length > 0;
+const hasPermission = (ctx) => ctx.user.role === 'admin';
+const notRateLimited = (ctx) => ctx.requestCount < 10;
+
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'processing',
+ guard(isValid),
+ guard(hasPermission),
+ guard(notRateLimited)
+ )
+ ),
+ processing: state()
+});
+```
+
+Guards are evaluated in order. If any guard returns `false`, the transition is blocked and subsequent guards don't run.
+
+## Guards vs State Splits
+
+When should you use guards vs creating separate states? Here's a guideline:
+
+### Use Guards When:
+- The condition is a validation check
+- The condition is temporary or context-based
+- You want to block a transition conditionally
+
+```js
+// ✅ Good use of guards
+idle: state(
+ transition('submit', 'processing',
+ guard((ctx) => ctx.email && ctx.password) // Validation
+ )
+)
+```
+
+### Use Separate States When:
+- The condition represents a distinct mode or phase
+- The state has different UI requirements
+- Different transitions are available in each condition
+
+```js
+// ✅ Good use of separate states
+loggedOut: state(
+ transition('login', 'authenticating')
+),
+loggedIn: state(
+ transition('logout', 'loggingOut')
+)
+```
+
+## Guard Patterns
+
+### Validation Guards
+
+Check if data meets requirements before proceeding:
+
+```js
+const hasEmail = (ctx, ev) => ev.email && ev.email.includes('@');
+const hasPassword = (ctx, ev) => ev.password && ev.password.length >= 8;
+const termsAccepted = (ctx, ev) => ev.acceptedTerms === true;
+
+const machine = createMachine({
+ form: state(
+ transition('submit', 'submitting',
+ guard(hasEmail),
+ guard(hasPassword),
+ guard(termsAccepted)
+ )
+ ),
+ submitting: state()
+});
+```
+
+### Permission Guards
+
+Check if user has required permissions:
+
+```js
+const isAdmin = (ctx) => ctx.user.role === 'admin';
+const isOwner = (ctx, ev) => ctx.user.id === ev.resourceOwnerId;
+const hasEditAccess = (ctx) => ctx.user.permissions.includes('edit');
+
+const machine = createMachine({
+ viewing: state(
+ transition('edit', 'editing',
+ guard(isAdmin) // Only admins can edit
+ ),
+ transition('delete', 'deleting',
+ guard(isOwner) // Only owner can delete
+ )
+ ),
+ editing: state(),
+ deleting: state()
+});
+```
+
+### Rate Limiting Guards
+
+Prevent too many actions in a time period:
+
+```js
+const notRateLimited = (ctx) => {
+ const now = Date.now();
+ const timeSinceLastRequest = now - ctx.lastRequestTime;
+ return timeSinceLastRequest > 1000; // 1 second between requests
+};
+
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading',
+ guard(notRateLimited)
+ )
+ ),
+ loading: state()
+}, () => ({
+ lastRequestTime: 0
+}));
+```
+
+### Feature Flag Guards
+
+Enable/disable features based on configuration:
+
+```js
+const featureEnabled = (ctx) => ctx.features.newUI === true;
+const betaUser = (ctx) => ctx.user.betaTester === true;
+
+const machine = createMachine({
+ home: state(
+ transition('openNewFeature', 'newFeature',
+ guard(featureEnabled),
+ guard(betaUser)
+ )
+ ),
+ newFeature: state()
+});
+```
+
+## Conditional Transitions
+
+You can have multiple transitions for the same event with different guards. The first transition whose guards pass will be taken:
+
+```js
+const isValid = (ctx) => ctx.data.isValid;
+const isInvalid = (ctx) => !ctx.data.isValid;
+
+const machine = createMachine({
+ validating: state(
+ // First matching transition wins
+ transition('done', 'success',
+ guard(isValid)
+ ),
+ transition('done', 'error',
+ guard(isInvalid)
+ )
+ ),
+ success: state(),
+ error: state()
+});
+```
+
+This is how you achieve branching logic in state machines.
+
+## Guard Execution Timing
+
+Guards execute before any actions or reducers. This ensures:
+- Guards see the old context (before actions modify it)
+- Actions only run if guards pass
+- State changes only happen if guards pass
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('submit', 'processing',
+ guard((ctx) => {
+ console.log('Guard checking:', ctx.value); // Sees old value
+ return ctx.value > 0;
+ }),
+ reduce((ctx) => {
+ console.log('Reducer running:', ctx.value); // Only runs if guard passes
+ return { ...ctx, value: ctx.value + 1 };
+ })
+ )
+ ),
+ processing: state()
+}, () => ({ value: 5 }));
+```
+
+## Pure Guards
+
+Guards should be pure functions - they should:
+- Not modify context or external state
+- Return the same result for the same inputs
+- Not have side effects
+
+```js
+// ✅ Good - pure guard
+const isValid = (ctx) => ctx.count > 0;
+
+// ❌ Bad - has side effects
+const isValidWithSideEffect = (ctx) => {
+ console.log('Checking validity'); // Side effect
+ ctx.count++; // Mutates context
+ return true;
+};
+
+// ❌ Bad - non-deterministic
+const isValidRandom = (ctx) => Math.random() > 0.5; // Random result
+```
+
+Keep guards pure to ensure predictable behavior and easier testing.
+
+## Testing Guards
+
+Guards are easy to test since they're pure functions:
+
+```js
+const isEligible = (ctx, ev) => {
+ return ev.age >= 18 && ctx.country === 'US';
+};
+
+// Test without a full machine
+console.assert(
+ isEligible({ country: 'US' }, { age: 21 }) === true
+);
+console.assert(
+ isEligible({ country: 'US' }, { age: 16 }) === false
+);
+console.assert(
+ isEligible({ country: 'UK' }, { age: 21 }) === false
+);
+```
+
+## Common Pitfalls
+
+### Avoid Complex Logic in Guards
+
+Keep guards simple and focused:
+
+```js
+// ❌ Too complex
+const complexGuard = (ctx, ev) => {
+ if (ctx.user.role === 'admin') {
+ if (ev.action === 'delete') {
+ if (ctx.items.length > 0) {
+ return ctx.items.every(item => item.status !== 'locked');
+ }
+ }
+ }
+ return false;
+};
+
+// ✅ Better - break into smaller guards
+const isAdmin = (ctx) => ctx.user.role === 'admin';
+const isDeleteAction = (ctx, ev) => ev.action === 'delete';
+const hasItems = (ctx) => ctx.items.length > 0;
+const noLockedItems = (ctx) => ctx.items.every(item => item.status !== 'locked');
+
+transition('submit', 'processing',
+ guard(isAdmin),
+ guard(isDeleteAction),
+ guard(hasItems),
+ guard(noLockedItems)
+)
+```
+
+### Don't Use Guards for Navigation Logic
+
+Guards should validate conditions, not determine destinations:
+
+```js
+// ❌ Bad - using guards for routing
+idle: state(
+ transition('next', 'stateA', guard(conditionA)),
+ transition('next', 'stateB', guard(conditionB)),
+ transition('next', 'stateC', guard(conditionC))
+)
+
+// ✅ Better - use separate events or immediate transitions
+idle: state(
+ transition('next', 'deciding')
+),
+deciding: state(
+ immediate('stateA', guard(conditionA)),
+ immediate('stateB', guard(conditionB)),
+ immediate('stateC') // Default
+)
+```
+
+## Related Topics
+
+- [Transitions](/docs/concepts-transitions/) - How guards control transitions
+- [Events](/docs/concepts-events/) - What triggers guard evaluation
+- [Actions](/docs/concepts-actions/) - What happens after guards pass
+- [guard API](/docs/guard/) - Technical reference for the guard function
+- [immediate API](/docs/immediate/) - Using guards with immediate transitions
diff --git a/docs/src/content/docs/concepts-state.md b/docs/src/content/docs/concepts-state.md
new file mode 100644
index 00000000..4859c4bc
--- /dev/null
+++ b/docs/src/content/docs/concepts-state.md
@@ -0,0 +1,176 @@
+---
+title: Understanding State
+tags: concept
+---
+
+# Understanding State
+
+States are the foundation of any finite state machine. At any given time, your machine is in exactly one state. States represent the different modes or conditions your application can be in.
+
+In traditional programming, we often use boolean flags or multiple variables to track application state. This can lead to invalid state combinations and bugs. With finite state machines, you explicitly define all possible states, making your application's behavior predictable and easy to reason about.
+
+## Defining States
+
+In Robot, states are created using the `state` function and organized within a machine:
+
+```js
+import { createMachine, state } from 'robot3';
+
+const machine = createMachine({
+ idle: state(),
+ loading: state(),
+ loaded: state(),
+ error: state()
+});
+```
+
+States in Robot are declarative - you define what states exist and what transitions are possible from each state. This makes your application's behavior predictable and easy to reason about.
+
+## Initial State
+
+Every machine starts in an initial state. By default, this is the first state defined in your machine:
+
+```js
+const machine = createMachine({
+ idle: state(), // This is the initial state
+ active: state()
+});
+```
+
+You can also explicitly specify the initial state:
+
+```js
+const machine = createMachine('active', {
+ idle: state(),
+ active: state() // Machine starts here
+});
+```
+
+## Final States
+
+States with no transitions are considered final states. When a machine reaches a final state, it stays there unless externally reset:
+
+```js
+const machine = createMachine({
+ processing: state(
+ transition('complete', 'finished')
+ ),
+ finished: state() // Final state - no way out
+});
+```
+
+Final states are useful for representing completed workflows, terminated processes, or end conditions in your application logic.
+
+## State Names
+
+State names should be:
+- **Descriptive**: Choose names that clearly describe what the state represents
+- **Unique**: Each state must have a unique name within a machine
+- **Verb or noun form**: Use names like `loading`, `idle`, `authenticated` rather than `isLoading`, `shouldLoad`
+
+```js
+// Good state names
+const machine = createMachine({
+ idle: state(),
+ authenticating: state(),
+ authenticated: state(),
+ failed: state()
+});
+
+// Avoid boolean-style names
+const machine = createMachine({
+ isIdle: state(), // ❌ Don't prefix with 'is'
+ hasAuthenticated: state(), // ❌ Don't prefix with 'has'
+ shouldRetry: state() // ❌ Don't prefix with 'should'
+});
+```
+
+## Why States Matter
+
+### Eliminating Invalid States
+
+Without explicit states, applications often use multiple booleans that can create impossible combinations:
+
+```js
+// Without FSM - invalid states possible
+let loading = false;
+let loaded = false;
+let error = false;
+
+// What does this mean? ❌
+loading = true;
+loaded = true;
+error = true;
+```
+
+With finite state machines, you can only be in one state at a time:
+
+```js
+// With FSM - always valid ✅
+const machine = createMachine({
+ idle: state(),
+ loading: state(),
+ loaded: state(),
+ error: state()
+});
+// Machine is in exactly ONE state
+```
+
+### Making State Explicit
+
+Explicit states make your code self-documenting. Instead of scattered conditional logic, your states are clearly defined:
+
+```js
+// Without FSM - implicit state
+function handleClick() {
+ if (!loading && !error && data === null) {
+ // What state are we in? 🤔
+ fetchData();
+ }
+}
+
+// With FSM - explicit state ✅
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ ),
+ loading: state(
+ transition('success', 'loaded'),
+ transition('error', 'error')
+ ),
+ loaded: state(),
+ error: state()
+});
+```
+
+## State vs Context
+
+It's important to distinguish between **state** (which mode you're in) and **context** (the data associated with that state):
+
+- **State**: The current mode or phase (e.g., `loading`, `idle`, `error`)
+- **Context**: Data that persists across states (e.g., user info, error messages, counters)
+
+```js
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ ),
+ loading: state(
+ transition('success', 'loaded')
+ ),
+ loaded: state()
+}, () => ({
+ // This is context - data that can change
+ users: [],
+ errorMessage: null,
+ retryCount: 0
+}));
+```
+
+States tell you *where* you are in your application flow. Context tells you *what* data you have while you're there.
+
+## Related Topics
+
+- [Transitions](/docs/concepts-transitions/) - How to move between states
+- [Events](/docs/concepts-events/) - What triggers state changes
+- [state API](/docs/state/) - Technical reference for the state function
diff --git a/docs/src/content/docs/concepts-transitions.md b/docs/src/content/docs/concepts-transitions.md
new file mode 100644
index 00000000..7c3d0733
--- /dev/null
+++ b/docs/src/content/docs/concepts-transitions.md
@@ -0,0 +1,263 @@
+---
+title: Understanding Transitions
+tags: concept
+---
+
+# Understanding Transitions
+
+Transitions define how your machine moves from one state to another in response to events. They're the arrows in your state diagram, the pathways that connect your states together.
+
+In traditional imperative programming, state changes happen anywhere in your code. With finite state machines, all possible state changes are explicitly declared as transitions, making your application's behavior predictable and preventing unexpected state changes.
+
+## Defining Transitions
+
+Transitions are defined within states using the `transition` function:
+
+```js
+import { createMachine, state, transition } from 'robot3';
+
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ ),
+ loading: state(
+ transition('success', 'loaded'),
+ transition('error', 'error')
+ ),
+ loaded: state(),
+ error: state(
+ transition('retry', 'loading')
+ )
+});
+```
+
+## Transition Anatomy
+
+Each transition has three parts:
+
+1. **Event name**: What triggers the transition
+2. **Target state**: Where to go when triggered
+3. **Optional modifiers**: Guards, actions, or reducers (covered in other guides)
+
+```js
+transition('eventName', 'targetState')
+```
+
+### Event Names
+
+Event names are strings that identify what action or occurrence triggers the transition:
+
+```js
+idle: state(
+ transition('start', 'running'),
+ transition('cancel', 'cancelled')
+)
+```
+
+Choose event names that describe what happened, not what should happen:
+- ✅ Good: `'success'`, `'error'`, `'click'`, `'timeout'`
+- ❌ Avoid: `'goToLoaded'`, `'shouldError'`, `'handleClick'`
+
+### Target States
+
+The target state must be a valid state name defined in your machine:
+
+```js
+const machine = createMachine({
+ red: state(
+ transition('next', 'yellow') // 'yellow' must exist
+ ),
+ yellow: state(
+ transition('next', 'green')
+ ),
+ green: state(
+ transition('next', 'red')
+ )
+});
+```
+
+## Deterministic Transitions
+
+For any given state and event combination, there's only one possible outcome. This determinism is key to making state machines predictable:
+
+```js
+loading: state(
+ // From 'loading', the 'done' event ALWAYS goes to 'loaded'
+ transition('done', 'loaded')
+)
+```
+
+You cannot have multiple transitions with the same event name in a single state (without guards):
+
+```js
+// ❌ Ambiguous - which transition happens?
+loading: state(
+ transition('done', 'loaded'),
+ transition('done', 'error') // Can't have duplicate events
+)
+
+// ✅ Use guards for conditional transitions
+loading: state(
+ transition('done', 'loaded',
+ guard(hasData)
+ ),
+ transition('done', 'error',
+ guard(hasError)
+ )
+)
+```
+
+## Multiple Transitions
+
+A state can have multiple transitions for different events:
+
+```js
+editing: state(
+ transition('save', 'saving'),
+ transition('cancel', 'view'),
+ transition('delete', 'deleting'),
+ transition('preview', 'previewing')
+)
+```
+
+This explicitly defines all the ways you can leave a state.
+
+## Self-Transitions
+
+A transition can target its own state, useful for updating context without changing state:
+
+```js
+input: state(
+ transition('change', 'input',
+ reduce((ctx, ev) => ({ ...ctx, value: ev.target.value }))
+ ),
+ transition('submit', 'submitted')
+)
+```
+
+## Why Explicit Transitions Matter
+
+### Preventing Invalid State Changes
+
+Without explicit transitions, any code can change state at any time:
+
+```js
+// Without FSM - state can change anywhere ❌
+let currentState = 'idle';
+
+function someFunction() {
+ currentState = 'loaded'; // Can this happen from 'idle'? 🤔
+}
+
+function anotherFunction() {
+ currentState = 'error'; // Should this be possible? 🤔
+}
+```
+
+With explicit transitions, only defined paths are possible:
+
+```js
+// With FSM - only valid transitions allowed ✅
+const machine = createMachine({
+ idle: state(
+ transition('fetch', 'loading')
+ // Can't go directly to 'loaded' or 'error' from 'idle'
+ ),
+ loading: state(
+ transition('success', 'loaded'),
+ transition('error', 'error')
+ )
+});
+```
+
+### Self-Documenting State Flow
+
+Your transitions create a complete map of your application's state flow:
+
+```js
+const authMachine = createMachine({
+ loggedOut: state(
+ transition('login', 'authenticating')
+ ),
+ authenticating: state(
+ transition('success', 'loggedIn'),
+ transition('failure', 'loggedOut'),
+ transition('timeout', 'loggedOut')
+ ),
+ loggedIn: state(
+ transition('logout', 'loggingOut')
+ ),
+ loggingOut: state(
+ transition('done', 'loggedOut')
+ )
+});
+```
+
+Just by reading the machine definition, you can see every possible state change and what triggers it.
+
+## Transition Execution Order
+
+When an event is sent, transitions execute in this order:
+
+1. **Event received**: The machine receives an event
+2. **Transition matched**: The machine finds a transition with that event name
+3. **Guards checked**: If guards exist, they must pass (covered in [Guards](/docs/concepts-guards/))
+4. **Actions executed**: Any actions or reducers run (covered in [Actions](/docs/concepts-actions/))
+5. **State changed**: The machine transitions to the target state
+6. **Context updated**: Any context changes from actions take effect
+
+This predictable execution order ensures consistent behavior.
+
+## Common Patterns
+
+### Loading Pattern
+
+```js
+idle: state(
+ transition('fetch', 'loading')
+),
+loading: state(
+ transition('success', 'loaded'),
+ transition('error', 'error')
+),
+loaded: state(
+ transition('refresh', 'loading')
+),
+error: state(
+ transition('retry', 'loading')
+)
+```
+
+### Form Validation Pattern
+
+```js
+editing: state(
+ transition('submit', 'validating')
+),
+validating: state(
+ transition('valid', 'submitting'),
+ transition('invalid', 'editing')
+),
+submitting: state(
+ transition('success', 'submitted'),
+ transition('error', 'editing')
+)
+```
+
+### Toggle Pattern
+
+```js
+off: state(
+ transition('toggle', 'on')
+),
+on: state(
+ transition('toggle', 'off')
+)
+```
+
+## Related Topics
+
+- [Events](/docs/concepts-events/) - What triggers transitions
+- [Guards](/docs/concepts-guards/) - Conditional transitions
+- [Actions](/docs/concepts-actions/) - Side effects during transitions
+- [transition API](/docs/transition/) - Technical reference for the transition function
diff --git a/docs/src/content/docs/core-concepts.md b/docs/src/content/docs/core-concepts.md
index f6f63d14..8b2ad9d7 100644
--- a/docs/src/content/docs/core-concepts.md
+++ b/docs/src/content/docs/core-concepts.md
@@ -1,20 +1,27 @@
---
title: Core Concepts
tags: core
-permalink: core/concepts.html
---
-Robot is built around finite state machines. Understanding these core concepts will help you build more predictable and maintainable applications.
+# Core Concepts
-## STATE
+Robot is built around finite state machines (FSMs). Understanding these core concepts will help you build more predictable and maintainable applications.
-
+## What Are Finite State Machines?
-States are the foundation of any state machine. At any given time, your machine is in exactly one state. States represent the different modes or conditions your application can be in.
+A finite state machine is a mathematical model of computation that can be in exactly one state at any given time. The machine can change from one state to another in response to events. This change is called a transition.
-```js
-import { createMachine, state } from 'robot3';
+FSMs have been around since the 1950s and are used everywhere - from traffic lights to complex software systems. They provide a structured way to model behavior that involves distinct modes or phases.
+
+## The Five Core Concepts
+
+Robot's state machines are built on five fundamental concepts:
+
+### [State](/docs/concepts-state/)
+
+States represent the different modes or conditions your application can be in. At any given time, your machine is in exactly one state.
+```js
const machine = createMachine({
idle: state(),
loading: state(),
@@ -23,191 +30,123 @@ const machine = createMachine({
});
```
-States in Robot are declarative - you define what states exist and what transitions are possible from each state. This makes your application's behavior predictable and easy to reason about.
-
-### Initial State
-
-Every machine starts in the first state defined. In the example above, the machine begins in the `idle` state.
-
-### Final States
-
-States with no transitions are considered final states. When a machine reaches a final state, it stays there unless externally reset.
+**Learn more**: [Understanding State](/docs/concepts-state/)
-## TRANSITIONS
+### [Transitions](/docs/concepts-transitions/)
-
-
-Transitions define how your machine moves from one state to another in response to events. They're the arrows in your state diagram.
+Transitions define how your machine moves from one state to another in response to events. They're the pathways that connect your states.
```js
-import { createMachine, state, transition } from 'robot3';
-
-const machine = createMachine({
- idle: state(
- transition('fetch', 'loading')
- ),
- loading: state(
- transition('done', 'loaded'),
- transition('error', 'error')
- ),
- loaded: state(),
- error: state(
- transition('retry', 'loading')
- )
-});
+idle: state(
+ transition('fetch', 'loading')
+),
+loading: state(
+ transition('success', 'loaded'),
+ transition('error', 'error')
+)
```
-### Transition Syntax
-
-Each transition takes:
-- An event name (what triggers the transition)
-- A target state (where to go when triggered)
-- Optional guards and actions
-
-Transitions are deterministic - for any given state and event combination, there's only one possible outcome.
-
-## EVENTS
+**Learn more**: [Understanding Transitions](/docs/concepts-transitions/)
-
+### [Events](/docs/concepts-events/)
-Events are the triggers that cause state transitions. They represent things that happen in your application - user interactions, API responses, timers, etc.
+Events are the triggers that cause state transitions. They represent things that happen - user interactions, API responses, timers, or any occurrence that should cause your machine to change state.
```js
-import { interpret } from 'robot3';
-
-const service = interpret(machine, () => {
- console.log('State changed to:', service.machine.current);
-});
+const service = interpret(machine);
// Send events to trigger transitions
service.send('fetch');
-service.send('done');
-```
-
-### Event Data
-
-Events can carry data that's used by guards and actions:
-
-```js
-service.send({ type: 'done', data: { user: 'Alice' } });
+service.send({ type: 'success', data: users });
```
-### Event Types
-
-Robot supports several types of events:
-- **User events**: Explicitly sent via `service.send()`
-- **Immediate transitions**: Automatically triggered when entering a state
-- **Invoked promises**: Success/error events from async operations
-
-## GUARDS
+**Learn more**: [Understanding Events](/docs/concepts-events/)
-
+### [Guards](/docs/concepts-guards/)
Guards are conditions that must be met for a transition to occur. They let you add conditional logic to your state machines without complicating the state structure.
```js
-import { createMachine, state, transition, guard } from 'robot3';
-
-const machine = createMachine({
- idle: state(
- transition('submit', 'processing',
- guard((ctx) => ctx.form.isValid)
- )
- ),
- processing: state()
-});
+idle: state(
+ transition('submit', 'processing',
+ guard((ctx) => ctx.form.isValid)
+ )
+)
```
-### Guard Functions
-
-Guards are pure functions that receive the context and event, returning `true` to allow the transition or `false` to prevent it:
-
-```js
-const isValidUser = (ctx, event) => {
- return event.user && event.user.age >= 18;
-};
-
-const machine = createMachine({
- waiting: state(
- transition('login', 'authenticated',
- guard(isValidUser)
- )
- ),
- authenticated: state()
-});
-```
+**Learn more**: [Understanding Guards](/docs/concepts-guards/)
-### Multiple Guards
+### [Actions](/docs/concepts-actions/)
-You can chain multiple guards - all must pass for the transition to occur:
+Actions are side effects that occur during transitions. They let you update context, make API calls, update the DOM, or perform any other effects.
```js
-transition('submit', 'processing',
- guard(isValid),
- guard(hasPermission)
+idle: state(
+ transition('login', 'authenticated',
+ reduce((ctx, ev) => ({ ...ctx, user: ev.user }))
+ )
)
```
-## ACTIONS
+**Learn more**: [Understanding Actions](/docs/concepts-actions/)
-
+## Why Use Finite State Machines?
-Actions are side effects that occur during transitions. They let you update context, make API calls, update the DOM, or perform any other effects.
+### Eliminate Invalid States
+
+Without FSMs, applications often use multiple booleans that can create impossible combinations:
```js
-import { createMachine, state, transition, action } from 'robot3';
+// Without FSM - invalid states possible ❌
+let loading = false;
+let loaded = false;
+let error = false;
+
+// What does this mean?
+loading = true;
+loaded = true;
+```
-const updateUser = action((ctx, event) => {
- return { ...ctx, user: event.user };
-});
+With FSMs, you're always in exactly one valid state:
+```js
+// With FSM - always valid ✅
const machine = createMachine({
- idle: state(
- transition('login', 'authenticated', updateUser)
- ),
- authenticated: state()
+ idle: state(),
+ loading: state(),
+ loaded: state(),
+ error: state()
});
```
-### Action Types
-
-Robot supports different types of actions:
-
-#### Context Actions
-Update the machine's context:
+### Make Behavior Explicit
-```js
-const increment = action((ctx) => ({ ...ctx, count: ctx.count + 1 }));
-```
-
-#### Side Effect Actions
-Perform effects without changing context:
+FSMs make your application's behavior self-documenting:
```js
-const log = action((ctx, event) => {
- console.log('Transitioning with:', event);
- return ctx; // Return unchanged context
+const authMachine = createMachine({
+ loggedOut: state(
+ transition('login', 'authenticating')
+ ),
+ authenticating: state(
+ transition('success', 'loggedIn'),
+ transition('failure', 'loggedOut')
+ ),
+ loggedIn: state(
+ transition('logout', 'loggedOut')
+ )
});
```
-#### Multiple Actions
+Just by reading the machine, you can see every possible state and how they connect.
-Chain multiple actions to execute in sequence:
+### Prevent Unexpected Behavior
-```js
-transition('submit', 'success',
- validate,
- saveToAPI,
- updateUI
-)
-```
+Only defined transitions can occur. You can't accidentally jump from `idle` to `loaded` if you didn't define that transition.
-### Action Timing
+### Enable Better Testing
-Actions execute during the transition, after guards have passed but before entering the new state. This timing ensures:
-- Guards see the old context
-- The new state receives the updated context
-- Side effects don't occur if guards fail
+With explicit states and transitions, you can test every path through your application systematically.
## Putting It All Together
@@ -265,4 +204,33 @@ This example demonstrates:
- **Guards**: Validation before allowing transitions
- **Actions**: Context updates and side effects
-These core concepts form the foundation of Robot's state management. Master these, and you'll be able to model complex application behavior in a clear, maintainable way.
\ No newline at end of file
+## Next Steps
+
+Now that you understand the core concepts, you can:
+
+1. **Dive deeper**: Read the detailed guides for each concept
+ - [Understanding State](/docs/concepts-state/)
+ - [Understanding Transitions](/docs/concepts-transitions/)
+ - [Understanding Events](/docs/concepts-events/)
+ - [Understanding Guards](/docs/concepts-guards/)
+ - [Understanding Actions](/docs/concepts-actions/)
+
+2. **Explore the API**: Check out the API reference for implementation details
+ - [createMachine](/docs/createmachine/)
+ - [state](/docs/state/)
+ - [transition](/docs/transition/)
+ - [guard](/docs/guard/)
+ - [action](/docs/action/)
+
+3. **Learn patterns**: See how to apply these concepts in real applications
+ - [Composition](/docs/composition/)
+ - [Nested States](/docs/nested-states/)
+ - [Async Execution](/docs/awaiting-asynchronous-execution/)
+
+4. **Integrate with frameworks**: Use Robot with your favorite UI library
+ - [React Robot](/docs/react-robot/)
+ - [Preact Robot](/docs/preact-robot/)
+ - [Svelte Robot](/docs/svelte-robot-factory/)
+ - [Lit Robot](/docs/lit-robot/)
+
+These core concepts form the foundation of Robot's state management. Master these, and you'll be able to model complex application behavior in a clear, maintainable way.
diff --git a/docs/src/pages/docs/index.astro b/docs/src/pages/docs/index.astro
index effcd92f..6a2b9a91 100644
--- a/docs/src/pages/docs/index.astro
+++ b/docs/src/pages/docs/index.astro
@@ -113,7 +113,7 @@ const machine = createMachine({