@kumbatio/energy-system 0.6.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +172 -0
- package/README.md +105 -3
- package/SPEC.md +439 -0
- package/api-surface.json +3 -1
- package/conformance.json +3176 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +21 -43
- package/dist/engine.js.map +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +94 -30
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/levels.d.ts +16 -0
- package/dist/levels.d.ts.map +1 -1
- package/dist/levels.js +54 -1
- package/dist/levels.js.map +1 -1
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +16 -23
- package/dist/persistence.js.map +1 -1
- package/dist/reconcile.d.ts +26 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +75 -0
- package/dist/reconcile.js.map +1 -0
- package/package.json +23 -11
- package/spec/conformance.schema.json +395 -0
- package/spec/energy-state.schema.json +53 -0
- package/src/energy.css +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@kumbatio/energy-system` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and from `1.0.0` this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
For this package, semver covers more than type signatures. **A shipped strategy
|
|
9
|
+
table's values are API**: changing what `notificationStrategy` returns at level
|
|
10
|
+
50 changes how every consumer behaves, so it is a major-version change — and a
|
|
11
|
+
change to [SPEC.md](./SPEC.md), not only to this library. The same is true of the
|
|
12
|
+
reconciliation rule, which two implementations must agree on to share state at
|
|
13
|
+
all. Prose returned by `describe()` is not covered; wording is a product decision.
|
|
14
|
+
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
|
|
17
|
+
Corrections to `1.0.0`. No type signature changed, but two of these change
|
|
18
|
+
runtime behavior and one narrows an install range — see the note at the top of
|
|
19
|
+
this file on why that is more than a patch for this package.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **The notification gate re-judges everything it is holding when energy or
|
|
24
|
+
suppression changes.** A notification was classified once, when published,
|
|
25
|
+
and an open batch window was then delivered under whatever policy happened to
|
|
26
|
+
be in force later. So an intent admitted at Steady arrived in the middle of a
|
|
27
|
+
focus session, and one batched at Steady was surfaced at Rest with every
|
|
28
|
+
channel disabled. `flush()` bypassed active suppression the same way.
|
|
29
|
+
Batched intents the current policy no longer admits are now moved to the
|
|
30
|
+
deferred queue instead, and released when something admits them. The batch
|
|
31
|
+
deadline is anchored to when the window opened, so a config change moves the
|
|
32
|
+
deadline rather than restarting the wait.
|
|
33
|
+
- **External state is validated against the published JSON Schema exactly.**
|
|
34
|
+
`createEnergyState()` accepted fractional timestamps, which
|
|
35
|
+
[spec/energy-state.schema.json](./spec/energy-state.schema.json) does not
|
|
36
|
+
allow, and persisted state carrying unknown properties was silently trimmed
|
|
37
|
+
to fit rather than rejected — so two implementations could exchange a state
|
|
38
|
+
and disagree about what they had exchanged. Persistence loads, cross-context
|
|
39
|
+
observations and `memoryPersistence` now share one strict boundary parser.
|
|
40
|
+
- **A configured `originId` no longer corrupts the unproduced sentinel.**
|
|
41
|
+
Construction stamped the configured producer identity onto the untouched
|
|
42
|
+
default state, which SPEC.md §3.2 requires to stay distinguishable from a
|
|
43
|
+
real one. `isUnproducedState()` returned `false` for it and
|
|
44
|
+
`getEnergyMetrics()` reported an age measured from the epoch. The sentinel is
|
|
45
|
+
now always `origin: "0-initial"`; the configured identity owns the first
|
|
46
|
+
state the engine actually produces.
|
|
47
|
+
- **`api-surface.json` includes `EnergyEngine.resolve()`.** The declaration
|
|
48
|
+
parser did not recognise generic members, so a public method was missing from
|
|
49
|
+
the frozen surface — and a method absent from the freeze is a method nobody
|
|
50
|
+
notices removing.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **React peer range is now `>=19.2.0`** for both `react` and `@types/react`.
|
|
55
|
+
The React entry point imports `<Activity>`, added in React 19.2, so the
|
|
56
|
+
previous `>=19` advertised a compatibility that throws on first render under
|
|
57
|
+
19.0 and 19.1.
|
|
58
|
+
- **Generated artifacts are checked, not regenerated, during validation.**
|
|
59
|
+
`pnpm test` used to run the full build first, so the drift guard compared
|
|
60
|
+
`conformance.json` against a copy it had just written — it could not fail,
|
|
61
|
+
whatever was committed. Generation now belongs to `pnpm run build`; both
|
|
62
|
+
generators take `--check`, and both artifacts are verified by the suite.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **[spec/conformance.schema.json](./spec/conformance.schema.json)** — the
|
|
67
|
+
schema `conformance.json` has always pointed at via `$schema` and which did
|
|
68
|
+
not exist. The generator now validates its own output against it before
|
|
69
|
+
emitting, and it is exported from the package so the relative reference
|
|
70
|
+
resolves for consumers.
|
|
71
|
+
- Packaged-consumer tests: the suite packs a tarball, unpacks it, and asserts
|
|
72
|
+
that every `exports` target is present, that all four entry points import,
|
|
73
|
+
that the conformance `$schema` reference resolves from the package root, and
|
|
74
|
+
that the React peer floor matches the APIs the entry point imports.
|
|
75
|
+
|
|
76
|
+
## [1.0.0]
|
|
77
|
+
|
|
78
|
+
The API is frozen. Everything below documents what that commitment now covers.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **[SPEC.md](./SPEC.md)** — the model specified independently of this
|
|
83
|
+
implementation: levels, energy state, reconciliation, the strategy contract,
|
|
84
|
+
autonomy, inbound demand, the runtime invariants, and the accessibility
|
|
85
|
+
requirements. Language-independent and RFC 2119-worded, so an implementation in
|
|
86
|
+
another language is an implementation of the same model rather than a port of
|
|
87
|
+
this one.
|
|
88
|
+
- **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the
|
|
89
|
+
interchange format. Sharing one person's energy state across processes or
|
|
90
|
+
languages is a wire-format problem, and this is the wire format.
|
|
91
|
+
- **[conformance.json](./conformance.json)** — 252 vectors plus every strategy
|
|
92
|
+
table, generated from the built library on each build and shipped in the
|
|
93
|
+
package. An implementation passes by loading and replaying them; a stale file
|
|
94
|
+
fails this package's own build.
|
|
95
|
+
- `isPreferredEnergyState(candidate, current)` — the reconciliation rule, which
|
|
96
|
+
was internal to the engine. It is the hardest part of the model to reimplement
|
|
97
|
+
correctly, so it is now readable, testable, and covered by vectors on its own.
|
|
98
|
+
- Accessibility handling in the reference stylesheet for
|
|
99
|
+
`prefers-contrast: more` and `forced-colors: active`. Both declare
|
|
100
|
+
`!important` deliberately: `applyEnergyLevel()` writes the same custom
|
|
101
|
+
properties inline, and a preference the person set must outrank a value the
|
|
102
|
+
program computed.
|
|
103
|
+
- Coverage across every one of the 20 level transitions, in both directions, and
|
|
104
|
+
across strategy composition — including the model's directional invariants
|
|
105
|
+
(protection never decreases and automation never gains discretion as capacity
|
|
106
|
+
falls), which a future edit to one table would otherwise break silently.
|
|
107
|
+
|
|
108
|
+
### Changed
|
|
109
|
+
|
|
110
|
+
- The reference stylesheet is explicit that its resting chrome opacities at Low
|
|
111
|
+
and Rest do not meet WCAG 1.4.11, that this is a design default rather than a
|
|
112
|
+
conformance claim, and that every value is an overridable custom property.
|
|
113
|
+
Saying so is more useful than quietly shipping numbers that imply otherwise.
|
|
114
|
+
- `readOnlyCursor` is documented as a hint that reaches pointer users only, and
|
|
115
|
+
which must be paired with actually disabling controls if "read only" is meant.
|
|
116
|
+
|
|
117
|
+
## [0.6.0]
|
|
118
|
+
|
|
119
|
+
### Added
|
|
120
|
+
|
|
121
|
+
- `autonomyStrategy` / `AutonomyConfig` — what automation may do unattended:
|
|
122
|
+
confidence threshold, whether wording may be composed, and how many steps may
|
|
123
|
+
chain. The mirror of interaction forgiveness, which protects against the
|
|
124
|
+
_user's_ mistakes at low energy where this protects against the _agent's_.
|
|
125
|
+
What narrows as energy falls is discretion, not action: at Rest the threshold
|
|
126
|
+
is 1, admitting only certainty, and a single templated step is still allowed.
|
|
127
|
+
- `demandAdmissionStrategy` and the pure `resolveDemandOutcome` — an
|
|
128
|
+
energy-resolved policy for arrivals that ask something of the person: reach
|
|
129
|
+
them now, acknowledge and queue, or queue silently. Originator tiers, the
|
|
130
|
+
exempt-tier invariant, and escalation to silence when a classification is not
|
|
131
|
+
confident enough to speak. Policy only; the effects it implies leave the
|
|
132
|
+
process and cannot be made transactional by an in-process runtime, so the
|
|
133
|
+
orchestration belongs to the consuming app.
|
|
134
|
+
|
|
135
|
+
## [0.5.4]
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- `createEnergyOrigin` exported from the index.
|
|
140
|
+
|
|
141
|
+
## [0.4.0]
|
|
142
|
+
|
|
143
|
+
Patterns studied in a field ADHD app and reimplemented as first-class, tested
|
|
144
|
+
primitives — including guarantees against the two failure modes observed in the
|
|
145
|
+
wild: suppressed reminders destroyed instead of deferred, and focus suppression
|
|
146
|
+
that never auto-expired.
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- Presence annotation: `defineEnergyPresence`, `presenceAtOrAbove`,
|
|
151
|
+
`presenceAtOrBelow`, `createPresenceStrategy`, `useEnergyPresence`,
|
|
152
|
+
`<EnergyGate>`, and the CSS-only `data-energy-min` / `data-energy-max` path.
|
|
153
|
+
- Notification gate: runtime enforcement of `NotificationConfig` with a
|
|
154
|
+
defer-not-drop guarantee.
|
|
155
|
+
- Focus sessions: time-boxed suppression with auto-expiry events and break nudges.
|
|
156
|
+
- Deferral presets with energy-aware ordering (`deferralStrategy`).
|
|
157
|
+
- Interaction forgiveness strategy.
|
|
158
|
+
- `prefers-reduced-motion` handling for the stylesheet's own transitions.
|
|
159
|
+
|
|
160
|
+
## [0.1.0]
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
|
|
164
|
+
- Renamed to `@kumbatio/energy-system`; the previous package name is deprecated.
|
|
165
|
+
|
|
166
|
+
## [0.0.x]
|
|
167
|
+
|
|
168
|
+
Foundations: the 5-level model with immutable revisioned state, the
|
|
169
|
+
framework-agnostic engine with strategy resolution, the first three built-in
|
|
170
|
+
strategies, the DOM adapter, the React provider and hooks, persistence adapters
|
|
171
|
+
with external observation, deterministic clocks, derived metrics, and legacy
|
|
172
|
+
level compatibility mapping.
|
package/README.md
CHANGED
|
@@ -5,6 +5,48 @@ Framework-agnostic TypeScript library for building **energy-aware applications**
|
|
|
5
5
|
Instead of adapting software to clock time, adapt behavior to current cognitive capacity.
|
|
6
6
|
`energy-system` models energy as explicit state and resolves strategies from that state.
|
|
7
7
|
|
|
8
|
+
## At a glance
|
|
9
|
+
|
|
10
|
+
A library about low capacity should be readable at low capacity. Everything you
|
|
11
|
+
need to use it is here; the rest of this file is reference.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @kumbatio/energy-system
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createEnergyEngine, uiVisibilityStrategy } from '@kumbatio/energy-system'
|
|
19
|
+
|
|
20
|
+
const engine = createEnergyEngine({ initialLevel: 75 })
|
|
21
|
+
|
|
22
|
+
engine.setLevel(25) // the person says they are running low
|
|
23
|
+
engine.resolve(uiVisibilityStrategy) // -> what the UI should do about it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
React:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { EnergyProvider, useEnergy } from '@kumbatio/energy-system/react'
|
|
30
|
+
|
|
31
|
+
const { state, setLevel } = useEnergy()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| If you want to | Go to |
|
|
35
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
36
|
+
| Set and read energy | [Quick start (core)](#quick-start-core) |
|
|
37
|
+
| Use it in React | [Quick start (React)](#quick-start-react) |
|
|
38
|
+
| Style by level in CSS | [CSS usage](#css-usage) |
|
|
39
|
+
| Show or hide by level | [Presence annotation](#presence-annotation-which-energy-states-does-this-element-belong-to) |
|
|
40
|
+
| Time-box work, hold notifications | [Focus sessions and the notification gate](#focus-sessions-and-the-notification-gate) |
|
|
41
|
+
| Offer "not now" | [Deferral](#deferral-not-now) |
|
|
42
|
+
| Decide what may interrupt | [Inbound demand and autonomy](#inbound-demand-and-autonomy) |
|
|
43
|
+
| Keep state across reloads and tabs | [Persistence and reconciliation](#persistence-and-reconciliation) |
|
|
44
|
+
| Look up an export | [API map](#api-map) |
|
|
45
|
+
| Port this to another language | [Specification and conformance](#specification-and-conformance) |
|
|
46
|
+
|
|
47
|
+
Five levels, and they never change: `100` Peak, `75` Active, `50` Steady,
|
|
48
|
+
`25` Low, `0` Rest.
|
|
49
|
+
|
|
8
50
|
## Why this exists
|
|
9
51
|
|
|
10
52
|
Most tooling assumes equal capacity across a day. Real-world cognitive energy is variable and non-linear.
|
|
@@ -513,21 +555,81 @@ The engine orders concurrent writes by timestamp, logical revision, source prior
|
|
|
513
555
|
origin. This gives every context the same deterministic winner even when two writes share a wall
|
|
514
556
|
clock timestamp. Local writes advance the logical revision when the clock does not advance.
|
|
515
557
|
|
|
558
|
+
The rule is exported as `isPreferredEnergyState(candidate, current)` and specified normatively in
|
|
559
|
+
[SPEC.md §4](./SPEC.md). It is the hardest part of the model to reimplement correctly, so it is
|
|
560
|
+
readable, testable, and covered by conformance vectors on its own rather than buried in the engine.
|
|
561
|
+
|
|
516
562
|
`setLevel()` updates in-memory subscribers synchronously. Persistence runs in the background with
|
|
517
563
|
bounded exponential backoff. Call `await engine.flush()` when a workflow must wait for durable
|
|
518
564
|
storage before reporting completion. An initial `flush()` waits for hydration before writing the
|
|
519
565
|
default state, and rejects rather than overwriting unread storage if that hydration read failed.
|
|
520
566
|
|
|
567
|
+
## Specification and conformance
|
|
568
|
+
|
|
569
|
+
The model is specified independently of this implementation.
|
|
570
|
+
|
|
571
|
+
- **[SPEC.md](./SPEC.md)** — the normative model: levels, state, reconciliation, the strategy
|
|
572
|
+
contract, autonomy, inbound demand, the runtime invariants, and the accessibility requirements.
|
|
573
|
+
Language-independent, RFC 2119 wording.
|
|
574
|
+
- **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the interchange format, so
|
|
575
|
+
two processes (or two languages) can share one person's energy state.
|
|
576
|
+
- **[conformance.json](./conformance.json)** — every table and every decision above, as vectors.
|
|
577
|
+
Ships in the package.
|
|
578
|
+
- **[spec/conformance.schema.json](./spec/conformance.schema.json)** — the shape of that vector
|
|
579
|
+
file, so a port can tell a file it can trust from one whose structure moved under it. Ships too,
|
|
580
|
+
and the generator validates its own output against it.
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import conformance from '@kumbatio/energy-system/conformance.json' with { type: 'json' }
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
An implementation in another language passes by loading the vectors and replaying them; this
|
|
587
|
+
package's own `test/conformance.test.ts` does exactly that and is a reasonable model to copy.
|
|
588
|
+
|
|
589
|
+
The file is generated from the built library, so the vectors cannot drift from the behavior they
|
|
590
|
+
describe. `pnpm run build` regenerates it; `pnpm test` recomputes it and fails if what is committed
|
|
591
|
+
differs, without rewriting anything. The two are deliberately separate — a check that regenerates
|
|
592
|
+
first is comparing a file to itself.
|
|
593
|
+
|
|
594
|
+
Vectors cover the pure surface: tables, and functions of their arguments alone. The stateful
|
|
595
|
+
guarantees — defer-never-drop, session auto-expiry, persistence ordering — are normative in SPEC.md
|
|
596
|
+
and checked by this package's suite, because no vector can express _and it must never drop one_.
|
|
597
|
+
|
|
598
|
+
## Stability
|
|
599
|
+
|
|
600
|
+
`1.0.0` is an API freeze. From here the package follows semver strictly, and for this package that
|
|
601
|
+
means more than the type signatures:
|
|
602
|
+
|
|
603
|
+
- **A shipped strategy table's values are API.** Changing what `notificationStrategy` returns at
|
|
604
|
+
level 50 changes how every consumer behaves, so it is a major-version change — and a change to
|
|
605
|
+
[SPEC.md](./SPEC.md), not just to this library.
|
|
606
|
+
- **The reconciliation rule is API.** Two implementations that disagree about it cannot share state.
|
|
607
|
+
- **The conformance vectors are the contract in machine-readable form.** Within a major version,
|
|
608
|
+
existing vectors do not change meaning; new sections and new vectors may be added.
|
|
609
|
+
- Prose from `describe()` is _not_ covered. Wording is a product decision and may change in a patch.
|
|
610
|
+
- Adding a strategy, an option with a default, or a new export is a minor release.
|
|
611
|
+
|
|
521
612
|
## Development
|
|
522
613
|
|
|
614
|
+
```bash
|
|
615
|
+
pnpm run validate # format, lint, types, tests, packaging — what CI runs
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Individually:
|
|
619
|
+
|
|
523
620
|
```bash
|
|
524
621
|
pnpm run check-types
|
|
525
622
|
pnpm run lint
|
|
526
|
-
pnpm test
|
|
527
|
-
pnpm run build
|
|
623
|
+
pnpm test # compiles, then CHECKS the generated artifacts
|
|
624
|
+
pnpm run build # compiles, then REGENERATES them
|
|
528
625
|
pnpm run pack:dry-run
|
|
529
626
|
```
|
|
530
627
|
|
|
628
|
+
`build` is the only thing that writes `api-surface.json` and `conformance.json`.
|
|
629
|
+
If a change to the library moves either, `pnpm test` fails and tells you to run
|
|
630
|
+
`pnpm run build` and commit the result — that is the intended loop, not a
|
|
631
|
+
warning to work around.
|
|
632
|
+
|
|
531
633
|
## Notes
|
|
532
634
|
|
|
533
635
|
This package is framework-agnostic at its core. Platform-specific persistence
|
|
@@ -536,7 +638,7 @@ adapters (e.g., SQLite-backed desktop stores) should live in consuming apps.
|
|
|
536
638
|
## Who uses this
|
|
537
639
|
|
|
538
640
|
- **[Anasa](https://anasa.md)** — Kumbatio's local-first writing and thinking workspace, in public alpha. Runs its entire adaptive shell on the engine: custom settings-backed persistence, energy-gated AI surfaces, notification filtering, and task-complexity guidance.
|
|
539
|
-
- **[Meltemi](https://meltemi.app)** — an email client in private beta from [entro314 labs](https://github.com/entro314-labs) (the studio behind Kumbatio), built outside the Kumbatio product line. Uses the notification gate (defer, never drop), focus sessions, deferral ordering,
|
|
641
|
+
- **[Meltemi](https://meltemi.app)** — an email client in private beta from [entro314 labs](https://github.com/entro314-labs) (the studio behind Kumbatio), built outside the Kumbatio product line. Uses the notification gate (defer, never drop), focus sessions, deferral ordering, interaction forgiveness, and demand admission — integrated without the React adapter. Its demand binding is the reference one: originator tiers come from its VIP list, obligation is classified from RFC 3834 header evidence and its own sender lanes, acknowledgments go out as `Auto-Submitted: auto-replied` auto-replies, and the capture is a snooze to the horizon `deferralStrategy` picked.
|
|
540
642
|
- **[kumbat.io](https://kumbat.io)** — the site itself runs on this model; change the energy level there and watch the interface adapt.
|
|
541
643
|
|
|
542
644
|
The integration patterns these apps proved out are documented in the [Production Patterns guide](https://docs.kumbat.io/docs/energy-system/guides/production-patterns). If you ship something with `energy-system`, tell us: [hello@kumbat.io](mailto:hello@kumbat.io).
|