# Mutable Mode Guide

Mutable mode makes Travels play nicely with observable state libraries (MobX, Vue's `reactive`, Pinia stores, etc.) that rely on **stable object references** to notify subscribers. Instead of replacing the entire state object on every update, Travels mutates the original object in place while still recording JSON Patches for undo/redo.

Use this guide to decide when to enable `mutable: true`, understand how it works internally, and apply it safely in production apps.

## When You Should Enable Mutable Mode

- You hand Travels the same object that your UI library observes and you cannot replace that reference without breaking reactivity (MobX stores, Vue/Pinia stores, custom proxies).
- You want undo/redo without extra `setState({...state})` copies or garbage-collection churn.
- You batch updates with `autoArchive: false` but still expect the live store reference to update immediately.

Stick with the default immutable mode when you already replace references (React/Redux style reducers, Zustand, etc.) or when you prefer structural sharing for diffing.

## Enabling Mutable Mode

```ts
import { createTravels } from 'travels';

const store = reactive({ count: 0 }); // Vue/Pinia example
const travels = createTravels(store, { mutable: true });

travels.setState((draft) => {
  draft.count += 1; // Mutates `store` in place
});
```

`travels.getState()` now always returns the same reference (`store`), but every mutation still produces patches so undo/redo keeps working.

## How It Works Under the Hood

1. `createTravels` deep clones the initial state once (via `deepClone(initialState)`) to keep a pristine copy for `reset()`.
2. Each `setState` call runs through Mutative's `create(...)` to generate patches/inverse patches. Those patches are immediately applied back to the live object via `apply(..., { mutable: true })`, so the reference never changes.
3. Navigation commands (`back`, `forward`, `go`) reuse the stored patches. `reset()` instead computes a fresh diff back to the JSON-cloned initial snapshot so it can restore the original data shape without replaying every history step.
4. If a history step replaces the entire root (patch path `[]` with `op: 'replace'`), Travels falls back to immutable assignment for that jump to guarantee correctness.

The full implementation lives in `src/travels.ts` and is exercised by `test/mutable-mode.test.ts` and `test/primitive-edge-cases.test.ts`.

## Behavior by Operation

| Operation            | Reference preserved?                                       | Notes                                                                                 |
| -------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `setState`           | Yes, as long as current state root is an object            | Non-object roots (numbers, strings, `null`) trigger an automatic immutable fallback   |
| `back` / `forward`   | Yes, unless the step applies a root-level replacement      | Happens when you undo a change that swapped the entire state object or type           |
| `go(position)`       | Same as `back/forward`                                     | Travels checks for root-replace patches before deciding whether it can mutate in place|
| `archive` (manual)   | Yes                                                        | Temporary patches are merged and applied to the same live object                      |
| `reset`              | Usually                                                    | Mutates in place only when both the current state and the stored initial snapshot are objects; otherwise it reassigns the root |
| `getHistory`         | Mixed                                                      | Includes the live state for the current position and cloned values for past/future steps |
| `subscribe`          | Receives the live (mutated) reference                      | Subscribers can safely compare by reference; values reflect the current undo position |

## Fallback & Safety Rails

- **Non-object roots**: If the current state is a primitive or `null`, Travels logs a dev warning and behaves immutably for that update. Undo/redo still works—it just cannot mutate a primitive in place.
- **Root replacements in history**: Navigating to a step that replaces the entire root (e.g., switching from `{...}` to `[]` or a primitive) forces a new reference for that jump only.
- **Function updaters returning a new root**: In mutable mode, `setState(() => newState)` that replaces the root falls back to immutable assignment for that update (dev warning emitted).
- **Root array time travel**: When the root state is an array, mutable back/forward/go can exhibit ordering limitations due to patch application semantics. If you rely on array-root navigation, use immutable mode or wrap the array in an object.
- **JSON-only data**: Travels clones the initial state via `deepClone(initialState)` the moment you call `createTravels`. Any non-JSON values are therefore lost up front, and `reset()` simply copies from that sanitized snapshot. The same constraint applies regardless of mutable mode.
- **Draft best practices**: Prefer mutating the provided draft (`draft.count++`) instead of returning a brand new object. Mutating drafts lets Travels keep using in-place patches during navigation.

## Integration Patterns

### MobX (simplified observable)

```ts
const mobxStore = makeAutoObservable({ todos: [] });
const travels = createTravels(mobxStore, { mutable: true });

autorun(() => {
  // mobxStore reference never changes
  console.log(mobxStore.todos.length);
});

function addTodo(title: string) {
  travels.setState((draft) => {
    draft.todos.push({ id: nanoid(), title, done: false });
  });
}
```

### Vue / Pinia

```ts
export const useTodosStore = defineStore('todos', () => {
  const state = reactive({ items: [] });
  const travels = createTravels(state, { mutable: true });

  const controls = travels.getControls();

  function addTodo(text: string) {
    travels.setState((draft) => {
      draft.items.push({ id: crypto.randomUUID(), text, done: false });
    });
  }

  return { state, addTodo, travels, controls };
});
```

The reactive `state` reference is the same object that Vue components bind to, so they instantly see mutations while retaining undo/redo controls.

### Manual Archive + Mutable

```ts
const travels = createTravels(store, { mutable: true, autoArchive: false });

function commitTransaction(cb: () => void) {
  cb();            // Run multiple travels.setState calls
  travels.archive(); // Save them as one undoable step
}
```

Because the state is mutated in place, your UI keeps updating during the transaction, but history only grows when you call `archive()`.

## Performance & Testing Notes

- Mutable mode still generates JSON patches, so you can persist or inspect diffs just like immutable mode.
- The dedicated `test/mutable-mode.test.ts` suite verifies reference stability across `setState`, `back`, `forward`, `go`, `reset`, `archive`, and `subscribe`.
- `test/bug-fixes.test.ts` and `test/coverage-improvements.test.ts` include regression tests for resetting nested objects, deleting extra properties, and handling sparse arrays (mutable value updates fall back to immutable to preserve holes).

## Troubleshooting Checklist

1. **Did the root reference change unexpectedly?** Check whether that history step replaced the entire state (e.g., `setState(() => newState)` returning a fresh object). Prefer draft mutations to avoid this.
2. **Not seeing updates in manual archive mode?** Remember that `travels.setState` still mutates the object immediately; `archive()` merely decides what becomes undoable.
3. **Seeing a warning about primitives?** Ensure your root state is an object. You can wrap primitives (`{ value: 0 }`) if you need mutable semantics.
4. **Need to confirm mode at runtime?** Inspect `travels.mutable`. It returns the current mode, which is handy for writing integration tests.

## Summary

Mutable mode gives Travels the ergonomics of reactive stores without giving up its patch-based history. Enable it when you need reference stability, follow the JSON-only constraint, and prefer draft mutations to keep undo/redo fast and predictable.
