@kumbatio/energy-system 0.5.4 → 1.0.0
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 +111 -0
- package/README.md +139 -2
- package/SPEC.md +439 -0
- package/api-surface.json +39 -1
- package/conformance.json +3176 -0
- package/dist/demand.d.ts +88 -0
- package/dist/demand.d.ts.map +1 -0
- package/dist/demand.js +166 -0
- package/dist/demand.js.map +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -32
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.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/dist/strategies.d.ts +26 -0
- package/dist/strategies.d.ts.map +1 -1
- package/dist/strategies.js +42 -0
- package/dist/strategies.js.map +1 -1
- package/package.json +17 -10
- package/spec/energy-state.schema.json +53 -0
- package/src/energy.css +77 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
## [1.0.0]
|
|
16
|
+
|
|
17
|
+
The API is frozen. Everything below documents what that commitment now covers.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **[SPEC.md](./SPEC.md)** — the model specified independently of this
|
|
22
|
+
implementation: levels, energy state, reconciliation, the strategy contract,
|
|
23
|
+
autonomy, inbound demand, the runtime invariants, and the accessibility
|
|
24
|
+
requirements. Language-independent and RFC 2119-worded, so an implementation in
|
|
25
|
+
another language is an implementation of the same model rather than a port of
|
|
26
|
+
this one.
|
|
27
|
+
- **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the
|
|
28
|
+
interchange format. Sharing one person's energy state across processes or
|
|
29
|
+
languages is a wire-format problem, and this is the wire format.
|
|
30
|
+
- **[conformance.json](./conformance.json)** — 252 vectors plus every strategy
|
|
31
|
+
table, generated from the built library on each build and shipped in the
|
|
32
|
+
package. An implementation passes by loading and replaying them; a stale file
|
|
33
|
+
fails this package's own build.
|
|
34
|
+
- `isPreferredEnergyState(candidate, current)` — the reconciliation rule, which
|
|
35
|
+
was internal to the engine. It is the hardest part of the model to reimplement
|
|
36
|
+
correctly, so it is now readable, testable, and covered by vectors on its own.
|
|
37
|
+
- Accessibility handling in the reference stylesheet for
|
|
38
|
+
`prefers-contrast: more` and `forced-colors: active`. Both declare
|
|
39
|
+
`!important` deliberately: `applyEnergyLevel()` writes the same custom
|
|
40
|
+
properties inline, and a preference the person set must outrank a value the
|
|
41
|
+
program computed.
|
|
42
|
+
- Coverage across every one of the 20 level transitions, in both directions, and
|
|
43
|
+
across strategy composition — including the model's directional invariants
|
|
44
|
+
(protection never decreases and automation never gains discretion as capacity
|
|
45
|
+
falls), which a future edit to one table would otherwise break silently.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- The reference stylesheet is explicit that its resting chrome opacities at Low
|
|
50
|
+
and Rest do not meet WCAG 1.4.11, that this is a design default rather than a
|
|
51
|
+
conformance claim, and that every value is an overridable custom property.
|
|
52
|
+
Saying so is more useful than quietly shipping numbers that imply otherwise.
|
|
53
|
+
- `readOnlyCursor` is documented as a hint that reaches pointer users only, and
|
|
54
|
+
which must be paired with actually disabling controls if "read only" is meant.
|
|
55
|
+
|
|
56
|
+
## [0.6.0]
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- `autonomyStrategy` / `AutonomyConfig` — what automation may do unattended:
|
|
61
|
+
confidence threshold, whether wording may be composed, and how many steps may
|
|
62
|
+
chain. The mirror of interaction forgiveness, which protects against the
|
|
63
|
+
_user's_ mistakes at low energy where this protects against the _agent's_.
|
|
64
|
+
What narrows as energy falls is discretion, not action: at Rest the threshold
|
|
65
|
+
is 1, admitting only certainty, and a single templated step is still allowed.
|
|
66
|
+
- `demandAdmissionStrategy` and the pure `resolveDemandOutcome` — an
|
|
67
|
+
energy-resolved policy for arrivals that ask something of the person: reach
|
|
68
|
+
them now, acknowledge and queue, or queue silently. Originator tiers, the
|
|
69
|
+
exempt-tier invariant, and escalation to silence when a classification is not
|
|
70
|
+
confident enough to speak. Policy only; the effects it implies leave the
|
|
71
|
+
process and cannot be made transactional by an in-process runtime, so the
|
|
72
|
+
orchestration belongs to the consuming app.
|
|
73
|
+
|
|
74
|
+
## [0.5.4]
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- `createEnergyOrigin` exported from the index.
|
|
79
|
+
|
|
80
|
+
## [0.4.0]
|
|
81
|
+
|
|
82
|
+
Patterns studied in a field ADHD app and reimplemented as first-class, tested
|
|
83
|
+
primitives — including guarantees against the two failure modes observed in the
|
|
84
|
+
wild: suppressed reminders destroyed instead of deferred, and focus suppression
|
|
85
|
+
that never auto-expired.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- Presence annotation: `defineEnergyPresence`, `presenceAtOrAbove`,
|
|
90
|
+
`presenceAtOrBelow`, `createPresenceStrategy`, `useEnergyPresence`,
|
|
91
|
+
`<EnergyGate>`, and the CSS-only `data-energy-min` / `data-energy-max` path.
|
|
92
|
+
- Notification gate: runtime enforcement of `NotificationConfig` with a
|
|
93
|
+
defer-not-drop guarantee.
|
|
94
|
+
- Focus sessions: time-boxed suppression with auto-expiry events and break nudges.
|
|
95
|
+
- Deferral presets with energy-aware ordering (`deferralStrategy`).
|
|
96
|
+
- Interaction forgiveness strategy.
|
|
97
|
+
- `prefers-reduced-motion` handling for the stylesheet's own transitions.
|
|
98
|
+
|
|
99
|
+
## [0.1.0]
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- Renamed to `@kumbatio/energy-system`; the previous package name is deprecated.
|
|
104
|
+
|
|
105
|
+
## [0.0.x]
|
|
106
|
+
|
|
107
|
+
Foundations: the 5-level model with immutable revisioned state, the
|
|
108
|
+
framework-agnostic engine with strategy resolution, the first three built-in
|
|
109
|
+
strategies, the DOM adapter, the React provider and hooks, persistence adapters
|
|
110
|
+
with external observation, deterministic clocks, derived metrics, and legacy
|
|
111
|
+
level compatibility mapping.
|
package/README.md
CHANGED
|
@@ -32,6 +32,10 @@ This library gives applications a structured way to adapt to energy state instea
|
|
|
32
32
|
- Task complexity guidance
|
|
33
33
|
- Interaction forgiveness (undo windows, destructive-action confirmation)
|
|
34
34
|
- Energy-aware deferral ordering
|
|
35
|
+
- Autonomy (how much automation may do unattended)
|
|
36
|
+
- **Inbound demand admission**: an energy-resolved policy for arrivals that ask
|
|
37
|
+
something of the user — reach them now, acknowledge and queue, or queue
|
|
38
|
+
silently (`demandAdmissionStrategy`, `resolveDemandOutcome`)
|
|
35
39
|
- **Presence annotation**: declare which energy levels a component/view belongs
|
|
36
40
|
to (`defineEnergyPresence`, `presenceAtOrAbove`, `<EnergyGate>`, `data-energy-min`)
|
|
37
41
|
- **Focus sessions**: time-boxed suppression windows with auto-expiry and break nudges
|
|
@@ -254,6 +258,93 @@ const { defaultPresetId, orderedPresetIds } = engine.resolve(deferralStrategy)
|
|
|
254
258
|
const resurfaceAt = resolveDeferral(presets, defaultPresetId) // epoch ms
|
|
255
259
|
```
|
|
256
260
|
|
|
261
|
+
## Inbound demand and autonomy
|
|
262
|
+
|
|
263
|
+
**Inbound demand** is anything arriving from outside that asks for the user's
|
|
264
|
+
attention or action: an email, a document comment, a review request, a task
|
|
265
|
+
assignment, a collaboration invite.
|
|
266
|
+
|
|
267
|
+
Every triage system in general use is organised around properties of the
|
|
268
|
+
_message_ — who sent it, how urgent it claims to be, what category it fits.
|
|
269
|
+
None is organised around the state of the _recipient_, which is the thing that
|
|
270
|
+
actually decides whether an arrival is a small task or a crushing weight. This
|
|
271
|
+
is that variable, applied to the queue.
|
|
272
|
+
|
|
273
|
+
`demandAdmissionStrategy` resolves the policy; `resolveDemandOutcome` applies
|
|
274
|
+
it. Both are pure. The library performs no effects here — acknowledging an
|
|
275
|
+
originator means sending mail, posting a comment, or updating a status chip
|
|
276
|
+
depending on the app, and those are irreversible in ways an in-process runtime
|
|
277
|
+
cannot make transactional. The orchestration, with its ordering, retries, and
|
|
278
|
+
deduplication, belongs to the app.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import {
|
|
282
|
+
autonomyStrategy,
|
|
283
|
+
deferralStrategy,
|
|
284
|
+
demandAdmissionStrategy,
|
|
285
|
+
resolveDemandOutcome,
|
|
286
|
+
} from '@kumbatio/energy-system'
|
|
287
|
+
|
|
288
|
+
const outcome = resolveDemandOutcome(
|
|
289
|
+
engine.resolve(demandAdmissionStrategy),
|
|
290
|
+
engine.resolve(autonomyStrategy),
|
|
291
|
+
{
|
|
292
|
+
originatorTier: 'unknown', // 'exempt' | 'known' | 'unknown', assigned by your app
|
|
293
|
+
bearsObligation: true, // does this ask something of the user?
|
|
294
|
+
confidence: 0.9, // how sure are you? a deterministic rule reports 1
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
switch (outcome.admission) {
|
|
299
|
+
case 'live':
|
|
300
|
+
return inbox.deliver(message)
|
|
301
|
+
case 'acknowledge':
|
|
302
|
+
// One act, never two. An acknowledgment without a capture is a promise
|
|
303
|
+
// nobody kept; a capture without an acknowledgment leaves the originator in
|
|
304
|
+
// silence. Capture first — it is the reversible half.
|
|
305
|
+
await tasks.capture(message, engine.resolve(deferralStrategy).defaultPresetId)
|
|
306
|
+
return replies.acknowledge(message, outcome.acknowledgment)
|
|
307
|
+
case 'silent':
|
|
308
|
+
return tasks.capture(message, engine.resolve(deferralStrategy).defaultPresetId)
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Two rules are worth stating outright, because each blocks a specific failure:
|
|
313
|
+
|
|
314
|
+
1. **The exempt tier is never handled by machine.** At every level, an exempt
|
|
315
|
+
originator is admitted live. This is also what defuses the gaming risk: an
|
|
316
|
+
originator who learns an acknowledgment means "deprioritised" and escalates
|
|
317
|
+
elsewhere only succeeds if their escalation is one the user cannot ignore —
|
|
318
|
+
which is what makes them exempt.
|
|
319
|
+
2. **Acknowledgments report state, never intent.** "Received and queued,
|
|
320
|
+
current response horizon early next week" is a fact. "I'll get back to you
|
|
321
|
+
soon" is a promise the user's Tuesday self has to keep. The horizon comes
|
|
322
|
+
from `deferralStrategy`, so the queue and the acknowledgment cannot disagree.
|
|
323
|
+
|
|
324
|
+
Disclosure is the app's job in the app's own medium — an `Auto-Submitted:
|
|
325
|
+
auto-replied` header, an "auto-queued" badge, a system-attributed status. An
|
|
326
|
+
automated action toward a third party must be identifiable as automated.
|
|
327
|
+
|
|
328
|
+
### Autonomy
|
|
329
|
+
|
|
330
|
+
`autonomyStrategy` is the mirror of `interactionForgivenessStrategy`:
|
|
331
|
+
forgiveness protects against the _user's_ mistakes at low energy, autonomy
|
|
332
|
+
against the _agent's_. It is useful to any consumer with agentic surfaces, with
|
|
333
|
+
or without demand admission.
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
const { confidenceThreshold, allowGeneratedContent, maxUnattendedSteps } =
|
|
337
|
+
engine.resolve(autonomyStrategy)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
What narrows as energy falls is _discretion_, not action. At rest the
|
|
341
|
+
confidence threshold is `1`, which admits only certainty — rule-based actions,
|
|
342
|
+
never a judgment call — and `maxUnattendedSteps` is `1`. Automation may still
|
|
343
|
+
take a single, certain, template-only step, which is exactly the shape of an
|
|
344
|
+
out-of-office reply. It may not chain steps or improvise wording. The system
|
|
345
|
+
acts for the user precisely when they are least able to supervise it, so the
|
|
346
|
+
worst day is the wrong day for it to get creative.
|
|
347
|
+
|
|
257
348
|
## Quick start (DOM)
|
|
258
349
|
|
|
259
350
|
```ts
|
|
@@ -308,7 +399,8 @@ animating presence changes should do the same.
|
|
|
308
399
|
- `mapToNearestDiscreteLevel(value, levels, fallback)`
|
|
309
400
|
- `mapToNearestEnergyLevel(value)`
|
|
310
401
|
- Strategies: `uiVisibilityStrategy`, `notificationStrategy`,
|
|
311
|
-
`taskComplexityStrategy`, `interactionForgivenessStrategy`, `deferralStrategy
|
|
402
|
+
`taskComplexityStrategy`, `interactionForgivenessStrategy`, `deferralStrategy`,
|
|
403
|
+
`autonomyStrategy`, `demandAdmissionStrategy`
|
|
312
404
|
- Presence: `defineEnergyPresence(spec)`, `presenceAtOrAbove(min, below?)`,
|
|
313
405
|
`presenceAtOrBelow(max, above?)`, `resolveEnergyPresence(map, level)`,
|
|
314
406
|
`isPresenceVisible(presence)`, `isEnergyPresence(value)`,
|
|
@@ -320,6 +412,8 @@ animating presence changes should do the same.
|
|
|
320
412
|
`isNotificationPriority(value)`
|
|
321
413
|
- Deferral: `createDeferralPresets(options?)`, `resolveDeferral(presets, id, now?)`,
|
|
322
414
|
`DEFERRAL_PRESET_IDS`
|
|
415
|
+
- Demand admission: `resolveDemandOutcome(config, autonomy, demand)`,
|
|
416
|
+
`isOriginatorTier(value)`
|
|
323
417
|
- Types: `EnergyLevel`, `EnergyState`, `EnergyPresence`, `EnergyPresenceMap`,
|
|
324
418
|
`AdaptationStrategy`, `FocusSession`, `NotificationDelivery`, etc.
|
|
325
419
|
|
|
@@ -419,11 +513,54 @@ The engine orders concurrent writes by timestamp, logical revision, source prior
|
|
|
419
513
|
origin. This gives every context the same deterministic winner even when two writes share a wall
|
|
420
514
|
clock timestamp. Local writes advance the logical revision when the clock does not advance.
|
|
421
515
|
|
|
516
|
+
The rule is exported as `isPreferredEnergyState(candidate, current)` and specified normatively in
|
|
517
|
+
[SPEC.md §4](./SPEC.md). It is the hardest part of the model to reimplement correctly, so it is
|
|
518
|
+
readable, testable, and covered by conformance vectors on its own rather than buried in the engine.
|
|
519
|
+
|
|
422
520
|
`setLevel()` updates in-memory subscribers synchronously. Persistence runs in the background with
|
|
423
521
|
bounded exponential backoff. Call `await engine.flush()` when a workflow must wait for durable
|
|
424
522
|
storage before reporting completion. An initial `flush()` waits for hydration before writing the
|
|
425
523
|
default state, and rejects rather than overwriting unread storage if that hydration read failed.
|
|
426
524
|
|
|
525
|
+
## Specification and conformance
|
|
526
|
+
|
|
527
|
+
The model is specified independently of this implementation.
|
|
528
|
+
|
|
529
|
+
- **[SPEC.md](./SPEC.md)** — the normative model: levels, state, reconciliation, the strategy
|
|
530
|
+
contract, autonomy, inbound demand, the runtime invariants, and the accessibility requirements.
|
|
531
|
+
Language-independent, RFC 2119 wording.
|
|
532
|
+
- **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the interchange format, so
|
|
533
|
+
two processes (or two languages) can share one person's energy state.
|
|
534
|
+
- **[conformance.json](./conformance.json)** — every table and every decision above, as vectors.
|
|
535
|
+
Ships in the package.
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import conformance from '@kumbatio/energy-system/conformance.json' with { type: 'json' }
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
An implementation in another language passes by loading the vectors and replaying them; this
|
|
542
|
+
package's own `test/conformance.test.ts` does exactly that and is a reasonable model to copy. The
|
|
543
|
+
file is generated from the built library, so the vectors cannot drift from the behavior they
|
|
544
|
+
describe — and a stale `conformance.json` fails the build.
|
|
545
|
+
|
|
546
|
+
Vectors cover the pure surface: tables, and functions of their arguments alone. The stateful
|
|
547
|
+
guarantees — defer-never-drop, session auto-expiry, persistence ordering — are normative in SPEC.md
|
|
548
|
+
and checked by this package's suite, because no vector can express _and it must never drop one_.
|
|
549
|
+
|
|
550
|
+
## Stability
|
|
551
|
+
|
|
552
|
+
`1.0.0` is an API freeze. From here the package follows semver strictly, and for this package that
|
|
553
|
+
means more than the type signatures:
|
|
554
|
+
|
|
555
|
+
- **A shipped strategy table's values are API.** Changing what `notificationStrategy` returns at
|
|
556
|
+
level 50 changes how every consumer behaves, so it is a major-version change — and a change to
|
|
557
|
+
[SPEC.md](./SPEC.md), not just to this library.
|
|
558
|
+
- **The reconciliation rule is API.** Two implementations that disagree about it cannot share state.
|
|
559
|
+
- **The conformance vectors are the contract in machine-readable form.** Within a major version,
|
|
560
|
+
existing vectors do not change meaning; new sections and new vectors may be added.
|
|
561
|
+
- Prose from `describe()` is _not_ covered. Wording is a product decision and may change in a patch.
|
|
562
|
+
- Adding a strategy, an option with a default, or a new export is a minor release.
|
|
563
|
+
|
|
427
564
|
## Development
|
|
428
565
|
|
|
429
566
|
```bash
|
|
@@ -442,7 +579,7 @@ adapters (e.g., SQLite-backed desktop stores) should live in consuming apps.
|
|
|
442
579
|
## Who uses this
|
|
443
580
|
|
|
444
581
|
- **[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.
|
|
445
|
-
- **[Meltemi](https://meltemi.app)** — an email client in private beta, built outside the Kumbatio product line. Uses the notification gate (defer, never drop), focus sessions, deferral ordering,
|
|
582
|
+
- **[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.
|
|
446
583
|
- **[kumbat.io](https://kumbat.io)** — the site itself runs on this model; change the energy level there and watch the interface adapt.
|
|
447
584
|
|
|
448
585
|
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).
|