@adapt-ux/neuro-ux-sdk-signals
v0.1.0-rc.1
Published
Lightweight behavioral signal detectors that identify focus, overload, motion sensitivity, and interaction patterns for adaptive UI.
Maintainers
Readme
📡 NeuroUX Signals — Signal Manager API Reference
The NeuroUX Signals package provides lightweight behavioral signal detectors that identify user activity patterns (scroll, idle, focus, etc.) for adaptive UI. The Signal Manager is the central module that manages signal lifecycle and connects them to the Core Engine.
This document describes the public API available to developers when using:
import { SignalManager, IdleSignal, ScrollSignal } from "@adapt-ux/neuro-ux-sdk-signals";Overview
The Signal Manager system consists of three main components:
- SignalManager — Manages signal lifecycle and connects to Core Engine
- SignalContext — Provides isolated context for each signal to emit values
- SignalSnapshot — Maintains current state of all signals for rule evaluation
1. SignalManager
The SignalManager class is responsible for:
- Instantiating signals from constructors
- Controlling signal lifecycle (
startAll()/stopAll()) - Receiving values emitted by signals
- Forwarding values to the Core Engine
- Maintaining signal snapshot for rule processor
Signature
class SignalManager {
constructor(
signalConstructors: Array<new (ctx: SignalContext) => Signal>,
onEmit: (value: unknown) => void
);
startAll(): void;
stopAll(): void;
getSnapshot(): Record<string, any>;
clearSnapshot(): void;
}Basic Example
import { SignalManager, IdleSignal, ScrollSignal } from "@adapt-ux/neuro-ux-sdk-signals";
// Create manager with signal constructors
const manager = new SignalManager(
[IdleSignal, ScrollSignal],
(value) => {
// This callback receives all signal emissions
console.log('Signal emitted:', value);
// Forward to Core Engine for state updates and rule evaluation
}
);
// Start all signals (only in browser environment)
manager.startAll();
// Get current snapshot for rule evaluation
const snapshot = manager.getSnapshot();
// { idle: { type: 'idle', value: true, ts: 1234567890 }, ... }
// Stop all signals and clean up
manager.stopAll();Methods
startAll()
Starts all signal instances. Only runs in browser environment (checks for window). Safe to call in SSR environments (no-op).
manager.startAll();Behavior:
- Checks if
windowis defined (SSR safety) - Calls
start()on each signal instance - Handles errors gracefully (logs to console, continues with other signals)
stopAll()
Stops all signal instances and cleans up resources (removes event listeners, clears timers, etc.).
manager.stopAll();Behavior:
- Calls
stop()on each signal instance - Handles errors gracefully
- Safe to call multiple times
getSnapshot()
Returns the current snapshot of all signal values. Used by Rule Processor for evaluation.
const snapshot = manager.getSnapshot();
// Returns: { idle: { type: 'idle', value: true, ts: 1234567890 }, ... }Returns: A shallow copy of the current snapshot data.
clearSnapshot()
Clears all snapshot data. Useful for reset scenarios.
manager.clearSnapshot();2. SignalContext
The SignalContext interface provides the context for signals to emit values. Each signal receives its own isolated SignalContext instance.
Interface
interface SignalContext {
emit(value: unknown): void;
}Implementation
The SignalContextImpl class implements this interface:
class SignalContextImpl implements SignalContext {
constructor(
snapshot: SignalSnapshot,
onEmit: (value: unknown) => void
);
emit(value: unknown): void;
}Behavior:
- Updates the shared snapshot when value has a
typeproperty - Forwards all emissions to the Core Engine callback
- Provides isolation between signals
Usage in Custom Signals
When creating custom signals, you receive a SignalContext in the constructor:
import { BaseSignal } from "@adapt-ux/neuro-ux-sdk-signals";
import type { SignalContext } from "@adapt-ux/neuro-ux-sdk-signals";
class CustomSignal extends BaseSignal {
start() {
// Emit values through the context
this.ctx.emit({ type: 'custom', data: 'value' });
}
stop() {
// Cleanup
}
}3. SignalSnapshot
The SignalSnapshot class manages the current state of all signals. Each signal update stores its value with a timestamp.
Class
class SignalSnapshot {
update(value: { type: string; [key: string]: any }): void;
get(): Record<string, any>;
clear(): void;
}Factory Function
function createSignalSnapshot(): SignalSnapshot;Example
import { createSignalSnapshot } from "@adapt-ux/neuro-ux-sdk-signals";
const snapshot = createSignalSnapshot();
snapshot.update({ type: 'idle', value: true });
snapshot.update({ type: 'scroll', position: 440 });
const data = snapshot.get();
// {
// idle: { type: 'idle', value: true, ts: 1234567890 },
// scroll: { type: 'scroll', position: 440, ts: 1234567891 }
// }Snapshot Structure
Each signal value in the snapshot has the following structure:
{
[signalType]: {
type: string; // Signal type identifier
...signalData; // Additional signal-specific data
ts: number; // Timestamp of last update
}
}Example:
{
idle: {
type: 'idle',
value: true,
ts: 1234567890
},
scroll: {
type: 'scroll',
position: 440,
ts: 1234567891
}
}4. Built-in Signals
IdleSignal
Detects user idle state by emitting periodic updates.
import { IdleSignal } from "@adapt-ux/neuro-ux-sdk-signals";
// Emits every 5 seconds: { type: 'idle', ts: number }ScrollSignal
Detects scroll position changes.
import { ScrollSignal } from "@adapt-ux/neuro-ux-sdk-signals";
// Emits on scroll: { type: 'scroll', position: number }5. Creating Custom Signals
To create a custom signal, extend BaseSignal:
import { BaseSignal } from "@adapt-ux/neuro-ux-sdk-signals";
import type { SignalContext } from "@adapt-ux/neuro-ux-sdk-signals";
class FocusSignal extends BaseSignal {
private handler?: () => void;
start() {
this.handler = () => {
this.ctx.emit({
type: 'focus',
active: document.hasFocus(),
});
};
window.addEventListener('focus', this.handler);
window.addEventListener('blur', this.handler);
this.handler(); // Initial emission
}
stop() {
if (this.handler) {
window.removeEventListener('focus', this.handler);
window.removeEventListener('blur', this.handler);
}
}
}Requirements:
- Extend
BaseSignal - Implement
start()method - Implement
stop()method - Use
this.ctx.emit()to emit values - Emit values with a
typeproperty for snapshot tracking
6. SSR Safety
The Signal Manager is SSR-safe:
startAll()checks forwindowbefore starting signals- No errors thrown in SSR environments
- Signals simply don't start in SSR (graceful no-op)
// Safe to call in SSR
manager.startAll(); // No-op if window is undefined7. Integration with Core Engine
The Signal Manager is designed to integrate with the NeuroUX Core Engine:
import { createNeuroUX } from "@adapt-ux/neuro-ux-sdk-core";
import { SignalManager, IdleSignal, ScrollSignal } from "@adapt-ux/neuro-ux-sdk-signals";
const engine = createNeuroUX({
profile: "adhd",
signals: ["idle", "scroll"],
});
// Create signal manager with Core Engine callback
const signalManager = new SignalManager(
[IdleSignal, ScrollSignal],
(value) => {
// Forward to Core Engine for:
// - Internal state updates
// - Rule processor evaluation
// - Future logs
engine.handleSignal(value);
}
);
signalManager.startAll();
// Rule processor can access snapshot
const snapshot = signalManager.getSnapshot();
engine.evaluateRules(snapshot);8. Error Handling
The Signal Manager handles errors gracefully:
- Errors during
startAll()are caught and logged (other signals continue) - Errors during
stopAll()are caught and logged - Invalid signal values are ignored (no snapshot update, but callback still called)
9. Best Practices
- Always call
stopAll()when cleaning up to prevent memory leaks - Use
typeproperty in emitted values for snapshot tracking - Isolate signal logic — each signal should be independent
- Handle cleanup in
stop()method (remove listeners, clear timers) - Test SSR compatibility — ensure signals don't break in SSR environments
10. Type Definitions
interface SignalContext {
emit(value: unknown): void;
}
interface Signal {
start(): void;
stop(): void;
}
type SignalConstructor = new (ctx: SignalContext) => Signal;License
MIT
