| Field | Value |
|---|---|
| Project team | project-shell |
| Repository role | Spectre reactive primitives |
| Package/artifact | @phcdevworks/spectre-shell-signals |
| Current version/status | 1.1.0 |
- Read AGENTS.md, then the agent-specific guide for the task.
- Check TODO.md and ROADMAP.md for current scope.
- Make the smallest repo-local change that satisfies the task.
- Run
npm run checkwhen validation is required or practical. - Update docs and CHANGELOG.md only when behavior, public contracts, or release-relevant metadata changed.
| Guide | Path |
|---|---|
| Agent rules | AGENTS.md |
| Claude Code | CLAUDE.md |
| Codex | CODEX.md |
| Copilot | COPILOT.md |
| Jules | JULES.md |
| Roadmap | ROADMAP.md |
| Todo | TODO.md |
| Changelog | CHANGELOG.md |
| Security | SECURITY.md |
Small synchronous reactive primitives for Spectre packages. The package provides signal, computed, and effect without tying Spectre runtime code to a UI framework.
Part of the PHCDevworks Spectre shell ecosystem — composable, zero-dependency packages for client-side shell applications.
Contributing | Changelog | Roadmap | Security Policy
- You need synchronous reactive primitives (
signal,computed,effect) without a full state management framework. - You want typed, lazily-evaluated derived values with explicit disposal.
- You are building on top of a Spectre shell or want framework-agnostic reactive state in vanilla TypeScript.
- You need a global store, atoms, selectors, or async resource primitives.
- You need framework-specific hooks such as
useSignalfor React or Vue. - You need persistence, devtools, observables, event buses, or middleware.
- You need cross-component state coordination patterns beyond sharing signal instances.
- Mutable signals through a
.valuegetter and setter, and a.peek()method for untracked reads. - Lazily evaluated computed values with dependency tracking.
- Synchronous effects with cleanup registration.
- Explicit disposal for computed values and effects.
- A deliberately small public API for shared Spectre runtime state.
npm install @phcdevworks/spectre-shell-signalsimport { computed, effect, signal } from '@phcdevworks/spectre-shell-signals'
const count = signal(0)
const doubled = computed(() => count.value * 2)
const stop = effect((onCleanup) => {
console.log(`count=${count.value}; doubled=${doubled.value}`)
onCleanup(() => console.log('effect cleanup'))
})
count.value = 2
stop()
doubled.dispose()signal(initialValue)returns a mutable signal with.value(tracked read/write) and.peek()(untracked read).computed(fn)returns a cached computed value withdispose().effect(fn, options?)runs immediately, reruns when tracked dependencies change, and returns a stop function. Pass{ onError }to handle errors without stopping the effect.batch(fn)defers subscriber notification untilfnreturns, so effects run once per batch rather than once per write.- Types include
Signal,Computed,EffectCallback,EffectCleanup,EffectOptions,CleanupRegistrar, andStopEffect.
peek() reads the current value without registering the caller as a subscriber. Use it inside an effect or computed body when you need the value but do not want to re-run the observer when it changes.
const count = signal(0)
effect(() => {
// re-runs whenever `flag` changes, but NOT when `count` changes
if (flag.value) {
console.log(count.peek())
}
})Pass onError to handle errors thrown inside an effect without stopping the reactive chain. The effect stays active and re-runs normally when its next dependency changes.
const count = signal(0)
const stop = effect(
() => {
if (count.value === 1) throw new Error('bad state')
console.log(count.value)
},
{ onError: (err) => console.error('effect error:', err) }
)
count.value = 1 // onError fires, effect stays alive
count.value = 2 // logs 2 normally
stop()Without onError, errors propagate synchronously to the caller — the initial run throws from effect(), and re-run errors throw from the signal setter.
spectre-shell-signals is the reactive primitive foundation for the Spectre stack:
| Package | Role |
|---|---|
spectre-shell-signals |
Reactive primitives (signal, computed, effect) |
spectre-tokens |
Visual language and token contracts (reactive token values) |
spectre-ui |
Token-driven styling and class recipes (reactive component state) |
spectre-ui-astro |
Astro component layer (island lifecycle integration) |
Consuming packages depend on this package for reactive state. They do not re-export its primitives or extend its API.
This package owns only low-level reactive primitives. It does not own DOM rendering, routing, lifecycle orchestration, async scheduling, stores, persistence, or framework adapters.
npm install
npm run checkUseful scripts:
npm run typecheckvalidates TypeScript without emitting files.npm run lintruns ESLint.npm run testruns the Vitest suite once.npm run buildemits ESM, CJS, and declarations todist.npm run check:version-syncconfirms the README "Current version/status" row matchespackage.json.npm run check:ecosystemvalidatesspectre.manifest.jsonagainst the ecosystem contract.npm run checkruns the standard package verification flow.
AI-agent coordination starts in AGENTS.md, with companion guidance in CLAUDE.md, CODEX.md, COPILOT.md, JULES.md, and .github/copilot-instructions.md.
| Problem | Likely cause | Fix |
|---|---|---|
npm run check fails on typecheck |
Type error in source or tests | Run npm run typecheck to isolate |
dist/ is missing after clone |
Build output is gitignored | Run npm run build |
| Tests fail in CI but pass locally | Node version mismatch | CI runs Node 22 and 24; match locally |
| Effect runs more than expected | Unintended .value read in tracked scope |
Move non-reactive reads outside the effect callback |
See CONTRIBUTING.md. The gate is npm run check — typecheck, lint, build, test, README version-sync, and ecosystem validation must all pass. Do not expand the reactive-primitives scope; see AGENTS.md for boundaries.
See CHANGELOG.md.
MIT. See LICENSE.