From use-legend-functions
Use when building a custom reactive utility for an app using @usels/core — decides between "use scope" core-only style vs traditional 2-layer hook+core style and shows the pattern for each
npx claudepluginhub tigerwest/use-legend --plugin use-legend-functionsThis skill uses the workspace's default tool permissions.
Use this skill when your app needs a reusable reactive utility that wraps observables, timers, event listeners, or derived state. Pick the style based on how the rest of the app consumes reactivity.
Provides ClickHouse patterns for MergeTree schemas, query optimization, aggregations, window functions, joins, and data ingestion for high-performance analytics.
Orchestrates multi-agent coding tasks via Claude DevFleet: plans projects into mission DAGs, dispatches parallel agents to isolated git worktrees, monitors progress, and retrieves structured reports.
Audits UI buttons and touchpoints by tracing state changes in handlers to find canceling side effects, race conditions, and inconsistent final states after refactors or for user-reported bugs.
Use this skill when your app needs a reusable reactive utility that wraps observables, timers, event listeners, or derived state. Pick the style based on how the rest of the app consumes reactivity.
| Primary app style | Choose |
|---|---|
Components/functions that use the "use scope" directive | Style A — core-only (createX only) |
Components that only use React hooks (useX()) | Style B — 2-layer (createX + useX wrapper) |
| Publishing a utility others will consume both ways | Style B |
"use scope" consumers)Inside a "use scope" block, call createX(...) directly. No React hook wrapper needed.
import { observable, type Observable } from "@legendapp/state";
import { observe, onUnmount } from "@usels/core";
interface DebouncedOptions {
maxWait?: number;
}
export function createDebounced<T>(
source$: Observable<T>,
delay: number,
options?: DebouncedOptions
): { value$: Observable<T> } {
const opts$ = observable(options);
const value$ = observable<T>(source$.peek());
let timer: ReturnType<typeof setTimeout> | undefined;
observe(() => {
const val = source$.get();
clearTimeout(timer);
timer = setTimeout(() => value$.set(val), delay);
});
onUnmount(() => clearTimeout(timer));
return { value$ };
}
Consumer usage:
function MyComponent() {
"use scope";
const text$ = observable("");
const { value$ } = createDebounced(text$, 200);
return <input onChange={(e) => text$.set(e.target.value)} />;
}
Wrap the core function with useScope + toObs. toObs(p) converts the ReactiveProps proxy into an Observable<Props> that flows reactively into the core.
import { useScope, toObs } from "@usels/core";
import { createDebounced, type DebouncedOptions } from "./createDebounced";
import type { Observable } from "@legendapp/state";
export type UseDebounced = typeof createDebounced;
export const useDebounced: UseDebounced = (source$, delay, options) => {
return useScope(
(scalars, opts) => {
const scalars$ = toObs(scalars);
const opts$ = toObs(opts);
return createDebounced(
source$,
scalars$.delay.get() as number,
opts$.get()
);
},
{ delay },
options
);
};
| Shape you accept | Pass to useScope how |
|---|---|
source$: Observable<T> | Pass as-is: useScope((p) => ..., source$) |
scalar: number | string | boolean | Fn | Wrap: useScope((p) => ..., { value: scalar }) |
options?: DeepMaybeObservable<Options> | Pass as-is: useScope((p) => ..., options) |
| Multiple params | Each as its own useScope arg — do NOT merge primitives into the options object |
| API | Use for |
|---|---|
observe(() => …) | Reactive re-run, auto-cleanup on scope dispose |
onMount(() => cleanup?) | Post-mount setup, optional cleanup return |
onUnmount(() => …) | Teardown only |
onBeforeMount(() => …) | Layout-effect timing, before paint |
All imported from @usels/core.
useState, useEffect, etc.) inside a useScope factory. Put that logic in createX using observe/onMount instead..peek() on options before useScope. Always pass options through; peek inside the factory if a mount-time snapshot is needed.dispose function from createX when the scope already runs. Use onUnmount(…) — the scope disposes it.$ suffix on Observable return fields.