@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 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, and interaction forgiveness — integrated without the React adapter.
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).