Home Doh Ref
Dohballs
  • 📁 doh_modules
    • 📦 dataforge
    • 📦 express
    • 📁 sso
    • 📁 user

USE State: Unified State Engine

This guide helps you understand Doh's USE (Unified State Engine) system, a comprehensive reactive state management solution that provides deep observation capabilities, automatic proxy generation, and seamless integrations with URL parameters, localStorage, and dynamic pattern instantiation.

The USE system features a revolutionary layered backend architecture with:

  • Layer 1: Immutable Proxy Layer - Stable proxy references that never change
  • Layer 2: Target Management Layer - Manages actual data behind proxies
  • Layer 3: Observer Notification Layer - Efficient observer management with batching
  • Advanced Array Diffing - Precise change detection for arrays
  • Pattern Integration - Automatic instantiation of Doh patterns from state data
  • Stable Array Methods - Array operations work correctly while maintaining stable proxy identity

This guide covers:

  • Creating and managing the singleton reactive state with Doh.use_state
  • Deep observation with path bubbling using Doh.observe_use
  • Pattern instantiation from state data with Doh.use_state_pattern
  • URL query parameter synchronization with Doh.use_url_query
  • localStorage integration with Doh.use_local_storage
  • State snapshots with Doh.use_snapshot
  • Advanced debugging and monitoring capabilities
  • Performance considerations and memory management

Core Concept: Layered Backend Architecture

The USE system maintains a single, global state object (Doh._use_state) accessed through a sophisticated layered backend architecture. The system creates stable proxies that maintain consistent identity while delegating to dynamically updated targets, enabling deep reactivity with intelligent change detection.

The Three Layers

  1. Immutable Proxy Layer (_use_state_backend.proxies): A Map storing stable proxy references by path. Once a proxy is created for a path, it never changes, ensuring consistent object identity.

  2. Target Management Layer (_use_state_backend.targets): A WeakMap that associates each proxy with its current and previous target data, enabling seamless target swapping and change detection.

  3. Observer Notification Layer (_use_state_backend.observers): A Map organizing observers by path with efficient batching via microtasks and comprehensive path bubbling.

Array Method Behavior and Stable References

The USE system wraps all native array methods to provide change detection and observer notifications while maintaining stable proxy identity. This creates a specific behavior difference from standard JavaScript arrays:

Mutating Methods (return underlying target, not proxy):

  • sort(), reverse(), fill(), copyWithin() - Array is correctly modified, but returned value ≠ proxy
  • push(), pop(), shift(), unshift(), splice() - Operate correctly and trigger observers

Non-Mutating Methods (work normally):

  • map(), filter(), slice(), concat(), join(), toString() - Return new values, no proxy involvement
const state = Doh.use_state({ numbers: [3, 1, 2] });

// ✅ Array IS sorted, observers ARE notified, but reference differs
const sorted = state.numbers.sort();
console.log(state.numbers);        // [1, 2, 3] ✅ Correctly sorted
console.log(sorted === state.numbers); // false ⚠️  Different references
console.log(sorted);               // [1, 2, 3] ✅ Still array-like

// ✅ This is the expected USE system behavior for stable proxy identity

use_state: Singleton State with Default Melding

The Doh.use_state function provides access to the singleton state object, optionally melding in default values. It returns the root proxy from the layered backend architecture.

Syntax

const state = Doh.use_state(defaults);

Parameters

  • defaults - (Optional) Object containing default values to meld into the state. Only keys that don't already exist or are undefined will be added.

Return Value

Returns the singleton state proxy object. All calls to use_state return the same proxy instance, ensuring shared state across your entire application.

Example

// First module initializes state with defaults
const state1 = Doh.use_state({
  theme: 'light',
  user: 'anonymous',
  version: '1.0.0'
});

console.log(state1.theme); // 'light'

// Second module adds more defaults without overriding existing values
const state2 = Doh.use_state({
  theme: 'dark',        // Won't override existing 'light'
  debug: false,         // Will be added (new key)
  apiUrl: '/api/v1'     // Will be added (new key)
});

console.log(state1 === state2);    // true (same singleton)
console.log(state2.theme);         // 'light' (preserved from first call)
console.log(state2.debug);         // false (added from second call)

// Direct property access and modification
state1.theme = 'dark';
console.log(state2.theme);         // 'dark' (same object)

// Nested object creation with automatic proxy wrapping
state1.user = { profile: { name: 'John', settings: { theme: 'blue' } } };
console.log(state1.user.profile.name); // 'John' - all levels are reactive proxies

observe_use: Deep Reactive Observation with Path Bubbling

The Doh.observe_use function provides comprehensive deep observation of state changes with intelligent path bubbling and efficient notification batching. It leverages the new backend architecture for optimal performance.

Syntax

const removeObserver = Doh.observe_use(paths, callback, onDelete);

Parameters

  • paths - String or array of strings specifying which paths to observe (supports dot notation for nested properties)
  • callback - Function called for any change under the observed path(s). Receives a destructured object with change details
  • onDelete - (Optional) Function called when properties are deleted (set to undefined). Receives similar destructured object

Callback Object Properties

Both callbacks receive a destructured object with these properties:

Main Callback Object:

  • fullPath - Full dot-notation path where the change occurred (e.g., 'user.profile.name')
  • prop - The specific property that changed (e.g., 'name')
  • newValue - The new value being assigned
  • oldValue - The previous value
  • isNew - Boolean indicating if this property is being created for the first time
  • watchPath - The path you're watching that caught this change (e.g., 'user')

Delete Callback Object:

  • fullPath - Full dot-notation path where the deletion occurred
  • prop - The specific property that was deleted
  • oldValue - The value that was deleted
  • watchPath - The path you're watching that caught this change

Return Value

Returns a function that removes all observers when called.

Path Bubbling Behavior

The new backend architecture supports comprehensive path bubbling. When you observe a path, you receive notifications for all changes under that path:

const state = Doh.use_state({
  user: {
    profile: {
      name: 'John',
      settings: { theme: 'light' }
    }
  }
});

// Watch the user object - catches ALL changes under user.*
const userObserver = Doh.observe_use('user', ({ fullPath, prop, newValue, oldValue, isNew, watchPath }) => {
  console.log(`Change detected: ${fullPath} | ${prop}: ${oldValue} → ${newValue} | via ${watchPath}`);
});

state.user.profile.name = 'Jane';              // Triggers: "user.profile.name | name: John → Jane | via user"
state.user.profile.settings.theme = 'dark';    // Triggers: "user.profile.settings.theme | theme: light → dark | via user"
state.user.profile.age = 25;                   // Triggers: "user.profile.age | age: undefined → 25 | via user" (isNew=true)

Notification Batching

The system includes sophisticated notification batching:

const state = Doh.use_state({});
let notifications = [];

const observer = Doh.observe_use('', ({ fullPath, prop, newValue }) => {
  notifications.push({ fullPath, prop, newValue });
});

// Multiple synchronous changes are batched
state.a = 1;
state.b = 2;
state.c = 3;

console.log(notifications.length); // 0 (notifications are batched)

// Wait for microtask to process batched notifications
await Doh.macrotask();
console.log(notifications.length); // 3 (all processed together)

Advanced Array Support with Diffing

The USE system includes sophisticated array diffing for precise change detection:

const state = Doh.use_state({
  todos: [
    { id: 1, text: 'Task 1', completed: false },
    { id: 2, text: 'Task 2', completed: true }
  ]
});

// Watch the todos array
const todoObserver = Doh.observe_use('todos', ({ fullPath, prop, newValue, oldValue, isNew }) => {
  console.log(`Todo change: ${fullPath} | ${prop}: ${oldValue} → ${newValue} | new: ${isNew}`);
});

// All of these trigger the observer with precise notifications:
state.todos[0].completed = true;                    // "todos.0.completed | completed: false → true"
state.todos[0].priority = 'high';                  // "todos.0.priority | undefined → high" (isNew=true)
state.todos.push({ id: 3, text: 'New Task' });     // "todos.2 | 2: undefined → {id:3,...}" (new array item)

// Array method support with proper binding
const popped = state.todos.pop(); // Works correctly, maintains proxy context

// ⚠️  IMPORTANT: Stable Proxy References vs Array Method Return Values
// In the USE system, array methods work correctly but maintain stable proxy identity.
// Mutating methods that normally return 'this' will return the underlying target instead.

// Methods that return the underlying array (not the proxy):
const sorted = state.todos.sort();     // ✅ state.todos IS sorted, but sorted !== state.todos
const reversed = state.todos.reverse(); // ✅ state.todos IS reversed, but reversed !== state.todos
const filled = state.todos.fill(0);    // ✅ state.todos IS filled, but filled !== state.todos
const spliced = state.todos.splice(0, 1); // ✅ state.todos IS modified, but spliced is removed elements array

// The array itself is correctly modified - only the reference equality differs:
console.log(state.todos[0]); // ✅ Shows sorted/reversed/filled result as expected
console.log(Array.isArray(sorted)); // ✅ true - returned value is still array-like

// Methods that return new arrays work normally (no reference issues):
const mapped = state.todos.map(x => x.id);     // Returns new array, no proxy involved
const filtered = state.todos.filter(x => x.completed); // Returns new array, no proxy involved

Multiple Path Observation

You can observe multiple paths simultaneously:

// Watch multiple specific paths
const multiObserver = Doh.observe_use(['user.profile.name', 'todos'], 
  ({ fullPath, prop, newValue, oldValue, isNew, watchPath }) => {
    console.log(`[${watchPath}] detected change in ${fullPath}`);
  }
);

state.user.profile.name = 'Alice';       // Triggers via "user.profile.name"
state.user.profile.email = 'test@test';  // Does NOT trigger (not watching user.profile.email)
state.todos[0].text = 'Updated';          // Triggers via "todos"

Deletion Support

Properties can be deleted in two ways, both trigger the optional onDelete callback:

const state = Doh.use_state({
  user: { name: 'John', email: 'john@example.com', temp: 'data', cache: 'old' }
});

// Watch for changes and deletions
const observer = Doh.observe_use('user', 
  ({ fullPath, prop, newValue, oldValue }) => {
    console.log(`Changed: ${fullPath} - ${oldValue} → ${newValue}`);
  },
  ({ fullPath, prop, oldValue }) => {
    console.log(`Deleted: ${fullPath} - removed ${oldValue}`);
  }
);

state.user.name = 'Jane';           // Triggers change callback
state.user.temp = undefined;        // Triggers delete callback (set to undefined)
delete state.user.cache;            // Triggers delete callback (delete keyword)
state.user.missing = null;          // Triggers change callback (null is "unset", not deleted)

use_state_pattern: Pattern Instantiation from State Data

The Doh.use_state_pattern function instantiates Doh patterns from state properties based on their value types. It handles strings as pattern names, arrays of mixed strings/objects, and objects with .pattern keys. Objects without .pattern keys generate warnings but continue processing. This enables declarative UI development where state structure directly drives pattern instantiation.

Syntax

const instances = await Doh.use_state_pattern(...stateNames);

Parameters

  • ...stateNames - State property names (strings) or arrays of names to process

Return Value

Returns a Promise that resolves to:

  • Single state name: The instantiated pattern(s) for that state
  • Multiple state names: Array of results corresponding to each state name
  • Array state: Array of instantiated patterns
  • Object state: Single instantiated pattern
  • String state: Single instantiated pattern from the string name

Supported State Value Formats

The system supports multiple formats for pattern definitions:

String Values: Treated as pattern names

state.myWidget = 'ButtonWidget';
const widget = await Doh.use_state_pattern('myWidget');
// Equivalent to: AsyncNew('ButtonWidget')

Object Values: Must contain .pattern property

state.myButton = { 
  pattern: 'html', 
  tag: 'button', 
  html: 'Click me',
  attr: { class: 'primary' }
};
const button = await Doh.use_state_pattern('myButton');
// Equivalent to: AsyncNew('html', { tag: 'button', html: 'Click me', ... })

Array Values: Arrays of strings and/or objects with .pattern

state.todoItems = [
  { pattern: 'html', tag: 'li', html: 'Task 1', id: 1 },
  { pattern: 'html', tag: 'li', html: 'Task 2', id: 2 },
  'StandardListItem' // String patterns also supported
];
const items = await Doh.use_state_pattern('todoItems');
// Returns array of instantiated patterns

Automatic State Tracking and Updates

The system automatically tracks state changes and updates pattern instances accordingly:

const state = Doh.use_state({
  todos: [
    { pattern: 'html', tag: 'li', html: 'Original Task', id: 1 }
  ]
});

// Initial instantiation
const initialTodos = await Doh.use_state_pattern('todos');
console.log(initialTodos.length); // 1

// Add new item - automatically creates new pattern instance
state.todos.push({ pattern: 'html', tag: 'li', html: 'New Task', id: 2 });

// Wait for observer processing
await Doh.macrotask();

// Cache is automatically updated with new instance
const cache = Doh._use_state_pattern_cache.get('todos');
console.log(cache.instances.length); // 2 - automatically updated

Advanced Array Diffing

The system uses sophisticated array diffing to minimize unnecessary instantiation:

state.items = [
  { pattern: 'html', id: 1, html: 'Item 1' },
  { pattern: 'html', id: 2, html: 'Item 2' },
  { pattern: 'html', id: 3, html: 'Item 3' }
];

await Doh.use_state_pattern('items');

// Remove middle item - only destroys instance for removed item
state.items = state.items.filter(item => item.id !== 2);

// The system automatically:
// - Detects this as a removal operation at index 1
// - Destroys only the removed instance
// - Preserves instances for unchanged items
// - Updates the instances array efficiently

Instance Lifecycle Management

The system automatically manages the lifecycle of pattern instances:

// Automatic cleanup on replacement
state.widget = { pattern: 'OldWidget', config: 'old' };
await Doh.use_state_pattern('widget');

// Replace with new widget - automatically destroys old instance
state.widget = { pattern: 'NewWidget', config: 'new' };

// The system automatically calls appropriate cleanup:
// 1. instance.destroy() if available
// 2. instance.e.remove() for DOM elements  
// 3. parentNode.removeChild() for DOM nodes
// 4. Safe fallback handling

Multiple State Processing

You can process multiple state properties simultaneously:

state.header = { pattern: 'Header', title: 'My App' };
state.sidebar = [
  { pattern: 'MenuItem', label: 'Home' },
  { pattern: 'MenuItem', label: 'About' }
];
state.footer = 'StandardFooter';

// Process all at once
const [headerInstance, sidebarInstances, footerInstance] = await Doh.use_state_pattern([
  'header', 'sidebar', 'footer'
]);

console.log(headerInstance.title);     // 'My App'
console.log(sidebarInstances.length);  // 2
console.log(footerInstance.pattern);   // 'StandardFooter'

Error Handling and Warnings

The system provides helpful warnings for malformed data:

state.badData = [
  { pattern: 'html', html: 'Good Item' },
  { html: 'Missing Pattern Key' },  // Warns but continues
  { pattern: 'html', html: 'Another Good Item' }
];

await Doh.use_state_pattern('badData');
// Console: [USE STATE PATTERN] Array item must be string or object with .pattern key: {html: "Missing Pattern Key"}
// Still processes the valid items

Integration with UI Frameworks

Perfect for building reactive UIs:

// Reactive todo list
const state = Doh.use_state({
  todos: []
});

// Set up automatic pattern instantiation
await Doh.use_state_pattern('todos');

// Add new todos - UI automatically updates
function addTodo(text) {
  const newTodo = {
    pattern: 'TodoItem',
    id: Date.now(),
    text: text,
    completed: false
  };
  state.todos = [...state.todos, newTodo];
}

// Remove todos - UI automatically updates
function removeTodo(id) {
  state.todos = state.todos.filter(todo => todo.id !== id);
}

// Update todos - UI automatically updates  
function toggleTodo(id) {
  const todo = state.todos.find(t => t.id === id);
  if (todo) todo.completed = !todo.completed;
}

Cache Inspection and Debugging

The system maintains internal cache that you can inspect:

// After using use_state_pattern, inspect the cache
const cache = Doh._use_state_pattern_cache.get('myStateName');
console.log({
  instances: cache.instances,     // Array of instantiated patterns
  sourceRef: cache.sourceRef,     // Clean copy of source data
  remover: cache.remover,         // Observer cleanup function
  destroyers: cache.destroyers    // Instance cleanup functions
});

MOC Integration: Declarative State Composition

The USE system works naturally with Doh's MOC (Melded Object Composition) through independent tracks that handle state synchronization. Each USE track operates independently, so you can mix and match them based on your needs without requiring USE_STATE as a prerequisite.

USE_STATE_DEFAULT: Immediate State Connection

USE_STATE_DEFAULT is a melder that provides state-backed default cascade. When a property uses this melder, its value becomes the default for the global state, and the property immediately connects via mimic_use.

Pattern('StateWidget', {
  moc: {
    theme: 'USE_STATE_DEFAULT',           // Immediate state connection
    config: 'USE_STATE_DEFAULT'
  },

  theme: 'light',                         // Default for state.theme
  config: { size: 'medium', enabled: true }
});

// Property connects to state immediately on first set
const widget = New('StateWidget');
// widget.theme ↔ state.theme (bidirectional sync established)

USE Tracks: Independent State Features

The USE tracks operate independently and can work together or separately. Each provides its own state synchronization without requiring other tracks.

Pattern('FlexibleWidget', {
  moc: {
    todos: ['Array', 'USE_STATE_PATTERN'],              // Array + pattern instantiation
    theme: ['Object', 'USE_LOCAL_STORAGE'],             // Object + localStorage
    search: 'USE_URL_QUERY',                            // Just URL sync
    preferences: ['USE_STATE', 'USE_LOCAL_STORAGE'],    // State + localStorage
    complete: 'USE_STATE_DEFAULT'                       // Just state-backed defaults
  }
});

Track Application Order

MOC tracks are applied during object_phase in this sequence:

  1. Melder composition happens first (Array, Object, USE_STATE_DEFAULT, etc.)
    • If USE_STATE_DEFAULT: mimic_use connects to state on first set
  2. USE_URL_QUERY: In browser, defaults from URL parameters unless already set to composed value
  3. USE_LOCAL_STORAGE: In browser, defaults from localStorage unless already set to composed value
  4. USE_STATE_PATTERN: Defaults from composed value and enables pattern instantiation unless already set
  5. If no other tracks AND USE_STATE: Syncs composed value to global state
  6. mimic_use connects the property to global state for ongoing synchronization
Pattern('ComprehensiveExample', {
  moc: {
    items: ['Array', 'USE_LOCAL_STORAGE', 'USE_STATE_PATTERN']
  },
  items: [{ pattern: 'DefaultItem', text: 'starter' }]
});

// Execution flow:
// 1. Array melding processes default items with any inherited items
// 2. localStorage attempts to load previously saved items
// 3. Pattern instantiation enabled for the final array
// 4. mimic_use connects items to global state
// Result: Persistent, reactive array with automatic pattern instantiation

Performance Considerations

For array modifications, mutation is more performant than replacement:

Pattern('TodoList', {
  moc: { todos: ['Array', 'USE_STATE_PATTERN'] },

  // Preferred: Direct mutation
  addTodo(text) {
    this.todos.push({ pattern: 'TodoItem', text, id: Date.now() });
  },

  removeTodo(id) {
    const index = this.todos.findIndex(t => t.id === id);
    if (index >= 0) this.todos.splice(index, 1);
  },

  // Also works: Array replacement (less performant)
  addTodoReplacement(text) {
    this.todos = [...this.todos, { pattern: 'TodoItem', text, id: Date.now() }];
  }
});

Practical Patterns

Simple localStorage theme:

Pattern('ThemeManager', {
  moc: { theme: 'USE_LOCAL_STORAGE' },
  theme: 'system'  // localStorage default
});

URL-synchronized search with state:

Pattern('SearchInterface', {
  moc: {
    query: ['USE_URL_QUERY', 'USE_STATE'],
    filters: 'USE_URL_QUERY'
  },
  query: '',
  filters: { category: 'all' }
});

Full-featured reactive list:

Pattern('ReactiveList', {
  moc: {
    items: ['Array', 'USE_STATE_PATTERN', 'USE_LOCAL_STORAGE']
  },
  items: []
});

use_url_query: URL Query Parameter Synchronization

The Doh.use_url_query function creates bidirectional synchronization between USE state properties and URL query parameters, enabling shareable and bookmarkable application state with automatic URL encoding/decoding.

Syntax

const cleanup = Doh.use_url_query(...params);

Parameters

Each parameter can be:

  • String: Parameter name (uses state property of same name)
  • Array: Array of parameter names
  • Object: { paramName: defaultValue, ... }

Return Value

Returns a cleanup function that removes URL parameters but keeps state watching active.

Basic URL Sync

const state = Doh.use_state({ 
  searchTerm: '', 
  page: 1, 
  theme: 'light' 
});

// Sync single parameter
Doh.use_url_query('searchTerm');

// State changes update URL
state.searchTerm = 'react'; // URL becomes: ?searchTerm=react

// URL parameters initialize state on page load
// If URL is ?searchTerm=vue, state.searchTerm becomes 'vue'

Multiple Parameters and Types

// Sync multiple parameters
Doh.use_url_query(['searchTerm', 'page', 'theme']);

// Sync with default values
const cleanup = Doh.use_url_query({
  searchTerm: '',      // Default empty string
  page: 1,             // Default page 1  
  showFilters: false,  // Boolean values work
  category: null       // null is a valid value
});

// Complex objects are JSON-encoded
state.filters = { category: 'tech', active: true };
// URL: ?filters=%7B%22category%22%3A%22tech%22%2C%22active%22%3Atrue%7D

Value Handling and Encoding

The system automatically handles value encoding and type preservation:

// All these values appear correctly in URL:
state.searchTerm = 'hello world';  // Encoded: ?searchTerm=hello%20world
state.page = 0;                    // Shows: ?page=0
state.enabled = false;             // Shows: ?enabled=false  
state.data = null;                 // Shows: ?data=null
state.config = { theme: 'dark' };  // Shows: ?config=%7B%22theme%22%3A%22dark%22%7D

// Only undefined hides from URL (but keeps watching):
state.searchTerm = undefined;      // URL parameter removed, but still watched
state.searchTerm = 'back';         // URL parameter reappears: ?searchTerm=back

URL Parameter Validation

The system validates parameter names and values:

// Valid parameter names (alphanumeric, dash, underscore only)
Doh.use_url_query('search-term');    // ✓ Valid
Doh.use_url_query('page_number');    // ✓ Valid
Doh.use_url_query('user@email');     // ✗ Invalid - cleaned to 'useremail'

// Unsupported values are handled gracefully
state.invalidParam = Symbol('test'); // Won't appear in URL, shows warning

Initialization from URL

On page load, existing URL parameters initialize state:

// URL: ?searchTerm=javascript&page=3&showFilters=true

const state = Doh.use_state();
Doh.use_url_query(['searchTerm', 'page', 'showFilters']);

// After initialization:
console.log(state.searchTerm);  // 'javascript' (parsed from URL)
console.log(state.page);        // 3 (parsed as number)
console.log(state.showFilters); // true (parsed as boolean)

Cleanup Options

// The returned cleanup function only removes URL parameters, keeps watching state
const cleanup = Doh.use_url_query('searchTerm', 'page');
cleanup(); // Removes ?searchTerm&page from URL, but state changes still work

// To stop watching entirely, use use_remove_url_query
Doh.use_remove_url_query('searchTerm');     // Stops watching + removes URL
Doh.use_remove_url_query(['page', 'theme']); // Multiple parameters

use_local_storage: localStorage Integration

The Doh.use_local_storage function creates bidirectional synchronization between USE state properties and browser localStorage, enabling persistent storage across browser sessions with automatic serialization and type preservation.

Syntax

const cleanup = Doh.use_local_storage(...params);

Parameters

Each parameter can be:

  • String: Parameter name (uses state property of same name)
  • Array: Array of parameter names
  • Object: { paramName: defaultValue, ... }

Return Value

Returns a cleanup function that removes localStorage entries and stops watching.

Basic Storage Sync

const state = Doh.use_state({ 
  theme: 'light',
  userPrefs: { lang: 'en' },
  autoSave: false
});

// Sync single parameter
Doh.use_local_storage('theme');

// State changes save to localStorage
state.theme = 'dark'; // localStorage.setItem('theme', 'dark')

// On page reload, state.theme will be 'dark' (restored from localStorage)

Multiple Parameters and Types

// Sync multiple parameters
Doh.use_local_storage(['theme', 'userPrefs', 'autoSave']);

// Sync with default values
const cleanup = Doh.use_local_storage({
  theme: 'light',          // Default theme
  userPrefs: { lang: 'en', notifications: true }, // Default object
  recentItems: [],         // Default array
  lastVisited: null        // Default null value
});

// Complex objects are JSON-serialized safely
state.userPrefs = { lang: 'es', notifications: false, fontSize: 14 };
// localStorage: {"lang":"es","notifications":false,"fontSize":14}

Data Persistence and Types

The system preserves value types correctly across browser sessions:

// All value types are preserved correctly:
state.theme = 'dark';           // String: stored as "dark"
state.fontSize = 16;            // Number: stored as "16", parsed back to 16
state.enabled = false;          // Boolean: stored as "false", parsed back to false
state.data = null;              // Null: stored as "null", parsed back to null
state.config = { key: 'val' };  // Object: JSON serialized and parsed

// Only undefined removes from localStorage:
state.theme = undefined;        // localStorage.removeItem('theme')
state.theme = 'light';          // Reappears in localStorage

Safe Serialization

The system includes safe serialization to handle circular references and Doh objects:

// Circular references are handled
const circular = { name: 'test' };
circular.self = circular;
state.circularData = circular; // Safely serialized with circular reference markers

// Doh objects are filtered out to avoid corruption
const dohInstance = New('html', { html: 'test' });
state.mixedData = {
  text: 'safe data',
  dohObj: dohInstance,  // Will be filtered as '[Filtered Doh Object]'
  numbers: [1, 2, 3]    // Will be preserved
};

Session Persistence

const state = Doh.use_state();

// Set up persistent storage
Doh.use_local_storage({
  userSettings: {
    theme: 'light',
    language: 'en',
    autoSave: true
  },
  workspaceLayout: 'default',
  recentFiles: []
});

// User makes changes
state.userSettings.theme = 'dark';
state.recentFiles = ['file1.txt', 'file2.txt'];

// Page refresh - all values automatically restored from localStorage
// No additional code needed for persistence!

Deep Equal Comparison

The system uses intelligent deep comparison to avoid unnecessary localStorage writes:

// Only actual changes trigger localStorage updates
state.config = { theme: 'dark', fontSize: 14 };

// This won't trigger localStorage update (same value)
state.config = { theme: 'dark', fontSize: 14 };

// This will trigger update (different value)
state.config = { theme: 'dark', fontSize: 16 };

Environment Safety

// Automatically handles environment availability
Doh.use_local_storage('userPrefs'); // Works in browser

// In Node.js environments:
// [USE STORAGE] localStorage not available - gracefully ignored

// Storage quota handling:
state.largeData = { /* huge object */ }; 
// [USE STORAGE] Error setting localStorage value: QuotaExceededError

Cleanup Options

// The returned cleanup removes localStorage entries AND stops observers
const cleanup = Doh.use_local_storage('theme', 'userPrefs');
cleanup(); // Removes 'theme' and 'userPrefs' from localStorage + stops watching

// To stop watching entirely, use use_remove_local_storage  
Doh.use_remove_local_storage('theme');        // Stops watching + removes from localStorage
Doh.use_remove_local_storage(['prefs', 'cache']); // Multiple parameters

use_snapshot: State Snapshots and Time Travel

The USE system includes an extensive snapshot system for capturing and replaying state changes with sophisticated trigger logic, timeline management, and persistent storage options. This provides powerful capabilities like undo/redo systems, session restoration, and state debugging.

The snapshot system supports:

  • Flexible Triggers: AND/OR logic, specific values, wildcards, and custom functions
  • Dual Storage: Browser history integration and IndexedDB timelines
  • Shared Timelines: Multiple snapshots can contribute to unified undo/redo streams via save_as
  • Time Travel: Navigate backwards/forwards through state history with async operations
  • Automatic Rotation: Configurable limits to prevent storage bloat
  • Session Persistence: Timeline snapshots survive browser refresh and restart

Quick Example

// Set up automatic document snapshots with timeline storage
await Doh.use_snapshot('document_history', {
  triggers: { 'document.content': '*' },      // Any content change
  states: ['document.content', 'document.title'],
  in_timeline: 50                            // Keep last 50 in IndexedDB
});

// Use timeline navigation for undo/redo
await Doh.use_timeline_undo('document_history');   // Undo last change
await Doh.use_timeline_redo('document_history');   // Redo last change

📖 For complete snapshot documentation, see use_snapshot.md

The snapshot system is powerful enough to warrant its own comprehensive documentation covering all trigger types, storage options, timeline management, and advanced patterns.

Advanced Observer Functions

The USE system provides several specialized observer functions for different use cases:

observe_new_use: New Property Detection

Monitors when new properties are added to the state:

const removeNewObserver = Doh.observe_new_use((propName, value, fullPath) => {
  console.log(`New property detected: ${propName} = ${value} at ${fullPath}`);
  
  // Plugin logic based on property patterns
  if (propName.includes('_ui')) {
    createUIControl(propName, value);
  } else if (propName.includes('_config')) {
    updateConfiguration(propName, value);
  }
});

state.new_ui_widget = 'button';      // Triggers: "New property detected: new_ui_widget = button"
state.user_config_theme = 'dark';    // Triggers: "New property detected: user_config_theme = dark"

observe_all_use: Comprehensive State Monitoring

Provides comprehensive monitoring of all root-level state changes:

const removeAllObserver = Doh.observe_all_use((propName, newValue, oldValue, isNewProperty) => {
  const operation = isNewProperty ? 'CREATE' : 'UPDATE';
  console.log(`Storage: ${operation} ${propName} - ${oldValue} → ${newValue}`);
  
  // Don't persist temporary values
  if (!propName.startsWith('_temp')) {
    saveToStorage(propName, newValue);
  }
});

state.count = 5;              // Storage: UPDATE count - 0 → 5
state.newProperty = 'hello';   // Storage: CREATE newProperty - undefined → hello
state._temp_calc = 42;        // Storage: CREATE _temp_calc... (but not saved)

mimic_use: Bidirectional State Synchronization

Creates bidirectional synchronization between external objects and USE state:

const state = Doh.use_state({ theme: 'light' });
const uiComponent = { currentTheme: 'initial' };

// Bidirectional sync: uiComponent.currentTheme ↔ state.theme
const removeMimic = Doh.mimic_use(uiComponent, 'currentTheme', 'theme', 
  (newVal, oldVal) => console.log(`Theme synced: ${oldVal} → ${newVal}`)
);

console.log(uiComponent.currentTheme); // 'light' (synced from state)

// Change via state - updates external object
state.theme = 'dark';
console.log(uiComponent.currentTheme); // 'dark'

// Change via external object - updates state
uiComponent.currentTheme = 'blue';
console.log(state.theme); // 'blue'

removeMimic();

Debug and Monitoring Tools

The USE system includes comprehensive debugging and monitoring capabilities:

Debug Mode

// Enable debug logging for all proxy operations
Doh.DebugMode = true;
// AND
Doh.enableUseDebug(true);

// All proxy operations will now be logged:
// [USE DEBUG] proxy_create at user.profile: {new: true}
// [USE DEBUG] proxy_get at user.profile.name: {hasValue: true, valueType: 'string'}
// [USE DEBUG] proxy_set at user.profile.name: {isNew: false, oldValue: 'string', newValue: 'string'}

state.user = { profile: { name: 'John' } };
state.user.profile.name = 'Jane';

// Disable debug logging
Doh.DebugMode = false;
Doh.enableUseDebug(false);

Debug History

// Get history of all debug operations
const history = Doh.getUseDebugHistory();
console.log('Debug operations:', history);

// Clear debug history
Doh.clearUseDebugHistory();

Backend Inspection

// Inspect the layered backend architecture
const backend = Doh._use_state_backend;

console.log({
  proxyCount: backend.proxies.size,        // Number of stable proxies
  observerPaths: backend.observers.size,   // Number of observed paths
  pendingNotifications: backend.pendingNotifications.size, // Queued notifications
  debugEnabled: backend.debug.enabled     // Debug status
});

// Inspect specific proxy relationships
backend.proxies.forEach((proxy, path) => {
  const target = backend.targets.get(proxy);
  console.log(`Proxy ${path}:`, { 
    hasTarget: !!target, 
    currentType: typeof target?.current 
  });
});

Storage Plugin Pattern

Pattern('StoragePlugin', {
  object_phase: function() {
    this.observer = Doh.observe_all_use((prop, newVal, oldVal, isNew) => {
      if (!prop.startsWith('_temp')) {
        localStorage.setItem(`app_${prop}`, JSON.stringify(newVal));
      }
    });
  }
});

// Plugin automatically persists all state changes
const plugin = New('StoragePlugin');
const state = Doh.use_state({ setting: 'value' });
state.userTheme = 'dark'; // Automatically saved to localStorage

Performance Considerations

The USE system is designed for high performance with several key optimizations:

Layered Backend Architecture

  • Stable Proxy Identity: Proxies never change once created, eliminating reference updates
  • Efficient Target Swapping: WeakMap-based target management with minimal memory overhead
  • Intelligent Path Caching: Proxy paths are cached to avoid repeated string operations

Observer Management

  • Microtask Batching: Notifications are batched and processed in microtasks for optimal performance
  • Path-Based Organization: Observers are organized by path for efficient notification targeting
  • Memory-Safe Cleanup: Automatic cleanup prevents memory leaks from abandoned observers

Array Diffing

  • Minimal Instantiation: Sophisticated diffing minimizes unnecessary pattern creation/destruction
  • Reference Preservation: Unchanged array items maintain their proxy references
  • Efficient Change Detection: Advanced comparison algorithms for complex objects

Safe Object Handling

  • Circular Reference Protection: Built-in protection against infinite recursion
  • Doh Object Filtering: Automatic detection and safe handling of Doh framework objects
  • Memory Leak Prevention: Comprehensive cleanup and garbage collection support

Best Practices

  1. Initialize State Early: Set up your state structure early in your application lifecycle using default melding.

  2. Use Descriptive Property Names: Leverage naming patterns for plugin systems (e.g., module_action_target).

  3. Always Clean Up: Store and call observer remover functions to prevent memory leaks:

    const cleanup = [];
    cleanup.push(Doh.observe_use('path', callback));
    cleanup.push(Doh.use_url_query('param'));
    cleanup.push(Doh.use_local_storage('data'));
    
    // Later...
    cleanup.forEach(fn => fn());
    
  4. Avoid Observer Loops: Be mindful of observer chains that might create circular updates:

    // Potential loop - be careful
    Doh.observe_use('a', () => state.b = 'updated');
    Doh.observe_use('b', () => state.a = 'updated');
    
  5. Use Prefixes for Control: Use prefixes like _temp or _private for properties that shouldn't trigger certain plugins.

  6. Leverage State Normalization: Keep your state structure flat when possible to maximize plugin effectiveness.

  7. Debug Complex Flows: Use debug mode to understand complex observation patterns:

    Doh.enableUseDebug(true);
    // ... perform operations ...
    const history = Doh.getUseDebugHistory();
    Doh.enableUseDebug(false);
    
  8. Batch State Updates: When making multiple changes, batch them to minimize observer notifications:

    // Good: Single notification batch
    state.user = { ...state.user, name: 'John', email: 'john@test.com' };
    
    // Less efficient: Multiple notifications
    state.user.name = 'John';
    state.user.email = 'john@test.com';
    
  9. Array Method Reference Behavior: Understand that array methods work correctly but don't return proxy references:

    // ⚠️  The operation works, but reference equality differs
    const sorted = state.items.sort((a, b) => a.name.localeCompare(b.name));
    console.log(state.items[0].name); // ✅ Correctly shows first sorted item
    console.log(sorted === state.items); // ❌ false - sorted is the target, not proxy
    
    // ✅ Correct way to check if operation worked:
    state.numbers = [3, 1, 2];
    state.numbers.sort();
    console.log(state.numbers); // ✅ [1, 2, 3] - array is correctly sorted
    
    // ✅ Methods affected: sort(), reverse(), fill(), copyWithin()
    // ✅ Methods NOT affected: map(), filter(), slice(), concat() (return new arrays)
    

Cleanup and Resource Management

The USE system provides comprehensive cleanup utilities:

Individual Cleanup

const observer1 = Doh.observe_use('prop1', callback1);
const observer2 = Doh.observe_new_use(callback2);
const mimic1 = Doh.mimic_use(obj, 'prop', 'stateProp');
const urlSync = Doh.use_url_query('param');
const storageSync = Doh.use_local_storage('data');

// Remove individual resources
observer1();
observer2();
mimic1();
urlSync();
storageSync();

Bulk Cleanup

// Remove all USE observers
Doh.clear_use_observers();

// Clear pattern cache
Doh._use_state_pattern_cache.clear();

// Remove all URL query syncs
Doh.use_remove_url_query(['param1', 'param2']);

// Remove all localStorage syncs
Doh.use_remove_local_storage(['data1', 'data2']);

Timing and Microtasks

USE state changes and observer propagation occur on the microtask queue. If you set a state value and need to immediately read a mirrored/derived value in the same tick (especially in tests), flush with:

// Set state values
state.searchTerm = 'react';
state.page = 2;

// Wait for observers and syncs to complete with a timeout
await Doh.macrotask();
// or use the Doh utility for microqueue waiting:
await Doh.microtask();

// Now all observers, URL updates, and localStorage syncs have completed
console.log(new URL(window.location).searchParams.get('searchTerm')); // 'react'
console.log(localStorage.getItem('page')); // '2'

The USE system represents the evolution of reactive state management in Doh.js, providing a powerful foundation for building sophisticated, observable applications while maintaining compatibility with existing Doh patterns and infrastructure.

Last updated: 10/22/2025