@kumbatio/energy-system 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominikos Pritis / Kumbatio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # @kumbatio/energy-system
2
+
3
+ Framework-agnostic TypeScript library for building **energy-aware applications**.
4
+
5
+ Instead of adapting software to clock time, adapt behavior to current cognitive capacity.
6
+ `energy-system` models energy as explicit state and resolves strategies from that state.
7
+
8
+ ## Why this exists
9
+
10
+ Most tooling assumes equal capacity across a day. Real-world cognitive energy is variable and non-linear.
11
+
12
+ The core model is:
13
+
14
+ - **Work Hours (wh):** total time present
15
+ - **Productive Hours (ph):** focused subset of that time
16
+ - **Stuff Done (sd):** measurable output
17
+
18
+ And the constraint is always `ph ≤ wh`.
19
+
20
+ In other words: extending time does not linearly increase productive output.
21
+ This library gives applications a structured way to adapt to energy state instead of raw time.
22
+
23
+ ## Features
24
+
25
+ - 5-level energy model: `100 | 75 | 50 | 25 | 0`
26
+ - Rich immutable state object: `level`, `timestamp`, `source`
27
+ - Framework-agnostic core engine
28
+ - Strategy system for behavior adaptation
29
+ - Built-in strategies:
30
+ - UI visibility
31
+ - Notification filtering
32
+ - Task complexity guidance
33
+ - DOM adapter (`data-energy-level` + CSS variables)
34
+ - React provider, hooks, and headless render component
35
+ - Persistence adapters (`localStorage`, in-memory)
36
+ - Deterministic clock support for testing/simulation
37
+ - Optional external persistence observation (`observe`)
38
+ - Derived metrics helper (`getEnergyMetrics`)
39
+ - Compatibility helpers for non-native external level models
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pnpm add @kumbatio/energy-system
45
+ ```
46
+
47
+ React integration is optional and provided via `@kumbatio/energy-system/react`.
48
+
49
+ ## Quick start (core)
50
+
51
+ ```ts
52
+ import {
53
+ createEnergyEngine,
54
+ uiVisibilityStrategy,
55
+ notificationStrategy,
56
+ } from '@kumbatio/energy-system'
57
+ import { localStoragePersistence } from '@kumbatio/energy-system/persistence'
58
+
59
+ const engine = createEnergyEngine({
60
+ initialLevel: 75,
61
+ persistence: localStoragePersistence(),
62
+ })
63
+
64
+ engine.setLevel(50)
65
+
66
+ const uiConfig = engine.resolve(uiVisibilityStrategy)
67
+ const notifConfig = engine.resolve(notificationStrategy)
68
+ ```
69
+
70
+ ## Quick start (React)
71
+
72
+ ```tsx
73
+ import {
74
+ EnergyProvider,
75
+ useEnergyLevel,
76
+ useEnergyState,
77
+ useStrategy,
78
+ } from '@kumbatio/energy-system/react'
79
+ import { uiVisibilityStrategy } from '@kumbatio/energy-system'
80
+
81
+ function Screen() {
82
+ const [level, setLevel] = useEnergyLevel()
83
+ const state = useEnergyState()
84
+ const ui = useStrategy(uiVisibilityStrategy)
85
+
86
+ return (
87
+ <div>
88
+ <button onClick={() => setLevel(level === 100 ? 75 : 100)}>
89
+ Energy: {state.level}
90
+ </button>
91
+ {ui.sidebar && <aside>Sidebar</aside>}
92
+ </div>
93
+ )
94
+ }
95
+
96
+ export function App() {
97
+ return (
98
+ <EnergyProvider defaultLevel={100}>
99
+ <Screen />
100
+ </EnergyProvider>
101
+ )
102
+ }
103
+ ```
104
+
105
+ ## Quick start (DOM)
106
+
107
+ ```ts
108
+ import {
109
+ applyEnergyLevel,
110
+ observeEnergyLevel,
111
+ } from '@kumbatio/energy-system/dom'
112
+
113
+ applyEnergyLevel(50)
114
+
115
+ const cleanup = observeEnergyLevel((state, prev) => {
116
+ console.log(`Energy: ${prev.level} -> ${state.level}`)
117
+ })
118
+ ```
119
+
120
+ ## CSS usage
121
+
122
+ Import the reference stylesheet:
123
+
124
+ ```ts
125
+ import '@kumbatio/energy-system/css'
126
+ ```
127
+
128
+ Then use classes like:
129
+
130
+ - `.energy-chrome`
131
+ - `.energy-sidebar`
132
+ - `.energy-tab-bar`
133
+ - `.energy-status-bar`
134
+ - `.energy-toolbar`
135
+ - `.energy-content`
136
+
137
+ ## API map
138
+
139
+ ### Core package
140
+
141
+ - `createEnergyEngine(options?)`
142
+ - `getEnergyLevels()`, `getEnergyLevel(level)`
143
+ - `cycleEnergyLevel(level)`, `isEnergyLevel(value)`, `isEnergySource(value)`
144
+ - `createExternalLevelCompatibility(options)`
145
+ - `cycleDiscreteLevel(current, levels, fallback)`
146
+ - `mapToNearestDiscreteLevel(value, levels, fallback)`
147
+ - `mapToNearestEnergyLevel(value)`
148
+ - Strategies: `uiVisibilityStrategy`, `notificationStrategy`, `taskComplexityStrategy`
149
+ - Types: `EnergyLevel`, `EnergyState`, `AdaptationStrategy`, etc.
150
+
151
+ ### `@kumbatio/energy-system/react`
152
+
153
+ - `EnergyProvider`
154
+ - `useEnergyState()`
155
+ - `useEnergyLevel()`
156
+ - `useEnergyLevelCycler()`
157
+ - `useStrategy(strategy)`
158
+ - `useEnergyGate(minLevel)`
159
+ - `EnergyIndicator`
160
+
161
+ ### `@kumbatio/energy-system/persistence`
162
+
163
+ - `localStoragePersistence(key?)`
164
+ - `memoryPersistence(initial?)`
165
+
166
+ ### `@kumbatio/energy-system/dom`
167
+
168
+ - `applyEnergyLevel(level, root?)`
169
+ - `readEnergyLevel(root?)`
170
+ - `observeEnergyLevel(listener, root?)`
171
+
172
+ ### Additional APIs
173
+
174
+ - `createEnergyEngine({ clock })` - inject a deterministic time source
175
+ - `engine.dispose()` - release engine-owned observation/subscription resources
176
+ - `EnergyPersistence.observe(onState)` - subscribe to external state changes
177
+ - `getEnergyMetrics(state, now?)` - derive productivity/break/task guidance metrics
178
+
179
+ ## Migrating from legacy scales to native package levels
180
+
181
+ The package model is fixed to `100 | 75 | 50 | 25 | 0`.
182
+ If your existing app uses a different discrete scale (for example `100 | 66 | 33 | 0`),
183
+ use compatibility helpers during migration.
184
+
185
+ ```ts
186
+ import {
187
+ createExternalLevelCompatibility,
188
+ cycleEnergyLevel,
189
+ } from '@kumbatio/energy-system'
190
+
191
+ const legacy = createExternalLevelCompatibility({
192
+ levels: [100, 66, 33, 0] as const,
193
+ toEnergyLevel: {
194
+ 100: 100,
195
+ 66: 50,
196
+ 33: 25,
197
+ 0: 0,
198
+ },
199
+ fallbackLevel: 100,
200
+ })
201
+
202
+ // Read legacy persisted values -> native package level
203
+ const nativeLevel = legacy.toEnergyLevel(66) // 50
204
+
205
+ // Keep old control cycle order while internally applying native levels
206
+ const nextNativeLevel = legacy.cycleMappedEnergyLevel(33) // maps next legacy level to native
207
+
208
+ // Once migration is complete, use native cycling directly
209
+ const next = cycleEnergyLevel(nativeLevel)
210
+ ```
211
+
212
+ Recommended migration sequence:
213
+
214
+ 1. **Read** legacy values through `createExternalLevelCompatibility(...).toEnergyLevel(...)`.
215
+ 2. **Write** and persist native package levels (`100 | 75 | 50 | 25 | 0`).
216
+ 3. **Switch UI controls** to native `cycleEnergyLevel`.
217
+ 4. **Remove compatibility mapping** after persisted data is fully normalized.
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ pnpm run check-types
223
+ pnpm run build
224
+ pnpm run pack:dry-run
225
+ ```
226
+
227
+ ## Notes
228
+
229
+ This package is framework-agnostic at its core. Platform-specific persistence
230
+ adapters (e.g., SQLite-backed desktop stores) should live in consuming apps.
231
+
232
+ ## Kumbatio
233
+
234
+ `energy-system` is the infrastructure layer of [Kumbatio](https://kumbat.io) — an ecosystem of open-source, neuroinclusive software built from lived experience with ADHD and depression. The position behind it, in one line: **energy ≠ time**, and software should adapt to real cognitive capacity instead of assuming a default brain.
235
+
236
+ - The full argument: [kumbat.io/manifesto](https://kumbat.io/manifesto) — agree? [Sign it](https://kumbat.io/endorse)
237
+ - Where this library is going: [ROADMAP.md](./ROADMAP.md)
238
+ - How to help: [CONTRIBUTING.md](./CONTRIBUTING.md)
239
+ - Live demo: [kumbat.io](https://kumbat.io) adapts its entire interface with this model — move the energy control and watch
240
+
241
+ ## License
242
+
243
+ [MIT](./LICENSE)
244
+
245
+ ---
246
+
247
+ _energy-system supports self-management and workflow adaptation. It is not a medical device, diagnosis tool, or treatment._
@@ -0,0 +1,49 @@
1
+ import type { EnergyLevel } from './types.js';
2
+ /**
3
+ * Cycle through any discrete numeric level list.
4
+ */
5
+ export declare function cycleDiscreteLevel<TLevel extends number>(current: number, levels: readonly TLevel[], fallback: TLevel): TLevel;
6
+ /**
7
+ * Map an arbitrary number to the nearest available discrete level.
8
+ */
9
+ export declare function mapToNearestDiscreteLevel<TLevel extends number>(value: number, levels: readonly TLevel[], fallback: TLevel): TLevel;
10
+ /**
11
+ * Map any number to the closest native package energy level.
12
+ */
13
+ export declare function mapToNearestEnergyLevel(value: number): EnergyLevel;
14
+ export interface ExternalLevelCompatibilityOptions<TExternal extends number> {
15
+ /**
16
+ * External level cycle order (e.g. [100, 66, 33, 0]).
17
+ */
18
+ levels: readonly TExternal[];
19
+ /**
20
+ * Mapping from external level values to native package levels.
21
+ */
22
+ toEnergyLevel: Readonly<Record<TExternal, EnergyLevel>>;
23
+ /**
24
+ * Fallback external level when input is unknown.
25
+ */
26
+ fallbackLevel: TExternal;
27
+ /**
28
+ * Fallback native level when mapping is invalid or missing.
29
+ * Defaults to the mapped value of fallbackLevel.
30
+ */
31
+ fallbackEnergyLevel?: EnergyLevel;
32
+ }
33
+ export interface ExternalLevelCompatibility<TExternal extends number> {
34
+ levels: readonly TExternal[];
35
+ fallbackLevel: TExternal;
36
+ fallbackEnergyLevel: EnergyLevel;
37
+ toEnergyLevel: (externalLevel: TExternal | number) => EnergyLevel;
38
+ fromEnergyLevel: (level: EnergyLevel) => TExternal;
39
+ cycleExternalLevel: (current: TExternal | number) => TExternal;
40
+ cycleMappedEnergyLevel: (current: TExternal | number) => EnergyLevel;
41
+ }
42
+ /**
43
+ * Build a compatibility bridge for systems that use non-native level values.
44
+ *
45
+ * This is useful during migrations (e.g. legacy 4-level models) while keeping
46
+ * the package's native fixed 5-level model unchanged.
47
+ */
48
+ export declare function createExternalLevelCompatibility<TExternal extends number>(options: ExternalLevelCompatibilityOptions<TExternal>): ExternalLevelCompatibility<TExternal>;
49
+ //# sourceMappingURL=compat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAM7C;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAS,MAAM,EACtD,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,SAAS,MAAM,EAC7D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,EAAE,MAAM,GACf,MAAM,CAiBR;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAElE;AAED,MAAM,WAAW,iCAAiC,CAAC,SAAS,SAAS,MAAM;IACzE;;OAEG;IACH,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;IAC5B;;OAEG;IACH,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAA;IACvD;;OAEG;IACH,aAAa,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,WAAW,CAAA;CAClC;AAED,MAAM,WAAW,0BAA0B,CAAC,SAAS,SAAS,MAAM;IAClE,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;IAC5B,aAAa,EAAE,SAAS,CAAA;IACxB,mBAAmB,EAAE,WAAW,CAAA;IAChC,aAAa,EAAE,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,KAAK,WAAW,CAAA;IACjE,eAAe,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAA;IAClD,kBAAkB,EAAE,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,KAAK,SAAS,CAAA;IAC9D,sBAAsB,EAAE,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,KAAK,WAAW,CAAA;CACrE;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,SAAS,MAAM,EACvE,OAAO,EAAE,iCAAiC,CAAC,SAAS,CAAC,GACpD,0BAA0B,CAAC,SAAS,CAAC,CAuFvC"}
package/dist/compat.js ADDED
@@ -0,0 +1,109 @@
1
+ import { isEnergyLevel } from './levels.js';
2
+ const NATIVE_ENERGY_LEVELS = [100, 75, 50, 25, 0];
3
+ const hasOwn = (target, key) => Object.hasOwn(target, key);
4
+ /**
5
+ * Cycle through any discrete numeric level list.
6
+ */
7
+ export function cycleDiscreteLevel(current, levels, fallback) {
8
+ const index = levels.findIndex((level) => level === current);
9
+ if (index === -1)
10
+ return fallback;
11
+ return levels[(index + 1) % levels.length] ?? fallback;
12
+ }
13
+ /**
14
+ * Map an arbitrary number to the nearest available discrete level.
15
+ */
16
+ export function mapToNearestDiscreteLevel(value, levels, fallback) {
17
+ if (levels.length === 0 || Number.isNaN(value)) {
18
+ return fallback;
19
+ }
20
+ let nearest = levels[0] ?? fallback;
21
+ let nearestDistance = Math.abs(nearest - value);
22
+ for (const level of levels) {
23
+ const distance = Math.abs(level - value);
24
+ if (distance < nearestDistance) {
25
+ nearest = level;
26
+ nearestDistance = distance;
27
+ }
28
+ }
29
+ return nearest;
30
+ }
31
+ /**
32
+ * Map any number to the closest native package energy level.
33
+ */
34
+ export function mapToNearestEnergyLevel(value) {
35
+ return mapToNearestDiscreteLevel(value, NATIVE_ENERGY_LEVELS, 100);
36
+ }
37
+ /**
38
+ * Build a compatibility bridge for systems that use non-native level values.
39
+ *
40
+ * This is useful during migrations (e.g. legacy 4-level models) while keeping
41
+ * the package's native fixed 5-level model unchanged.
42
+ */
43
+ export function createExternalLevelCompatibility(options) {
44
+ const { levels, toEnergyLevel, fallbackLevel } = options;
45
+ const normalizedLevels = Object.freeze([...levels]);
46
+ if (normalizedLevels.length === 0) {
47
+ throw new Error('External compatibility requires at least one level value');
48
+ }
49
+ if (!normalizedLevels.includes(fallbackLevel)) {
50
+ throw new Error('fallbackLevel must be present in levels');
51
+ }
52
+ const normalizedMap = new Map();
53
+ for (const externalLevel of normalizedLevels) {
54
+ if (!hasOwn(toEnergyLevel, externalLevel)) {
55
+ throw new Error(`Missing toEnergyLevel mapping for external level: ${externalLevel}`);
56
+ }
57
+ const mapped = toEnergyLevel[externalLevel];
58
+ if (!isEnergyLevel(mapped)) {
59
+ throw new Error(`Invalid native energy level mapping for external level ${externalLevel}: ${String(mapped)}`);
60
+ }
61
+ normalizedMap.set(externalLevel, mapped);
62
+ }
63
+ const fallbackEnergyLevel = options.fallbackEnergyLevel ?? normalizedMap.get(fallbackLevel) ?? 100;
64
+ const reverseMap = new Map();
65
+ for (const externalLevel of normalizedLevels) {
66
+ const mapped = normalizedMap.get(externalLevel);
67
+ if (!mapped)
68
+ continue;
69
+ if (!reverseMap.has(mapped)) {
70
+ reverseMap.set(mapped, externalLevel);
71
+ }
72
+ }
73
+ const toNativeLevel = (externalLevel) => {
74
+ if (hasOwn(toEnergyLevel, externalLevel)) {
75
+ const mapped = toEnergyLevel[externalLevel];
76
+ return isEnergyLevel(mapped) ? mapped : fallbackEnergyLevel;
77
+ }
78
+ const nearestExternal = mapToNearestDiscreteLevel(externalLevel, normalizedLevels, fallbackLevel);
79
+ return normalizedMap.get(nearestExternal) ?? fallbackEnergyLevel;
80
+ };
81
+ const fromNativeLevel = (level) => {
82
+ const exact = reverseMap.get(level);
83
+ if (exact !== undefined) {
84
+ return exact;
85
+ }
86
+ let nearest = fallbackLevel;
87
+ let nearestDistance = Number.POSITIVE_INFINITY;
88
+ for (const externalLevel of normalizedLevels) {
89
+ const mapped = normalizedMap.get(externalLevel) ?? fallbackEnergyLevel;
90
+ const distance = Math.abs(mapped - level);
91
+ if (distance < nearestDistance) {
92
+ nearest = externalLevel;
93
+ nearestDistance = distance;
94
+ }
95
+ }
96
+ return nearest;
97
+ };
98
+ const cycleExternal = (current) => cycleDiscreteLevel(current, normalizedLevels, fallbackLevel);
99
+ return Object.freeze({
100
+ levels: normalizedLevels,
101
+ fallbackLevel,
102
+ fallbackEnergyLevel,
103
+ toEnergyLevel: toNativeLevel,
104
+ fromEnergyLevel: fromNativeLevel,
105
+ cycleExternalLevel: cycleExternal,
106
+ cycleMappedEnergyLevel: (current) => toNativeLevel(cycleExternal(current)),
107
+ });
108
+ }
109
+ //# sourceMappingURL=compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.js","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,MAAM,oBAAoB,GAA2B,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,GAAgB,EAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAExF;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,MAAyB,EACzB,QAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,CAAA;IAC5D,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAA;IACjC,OAAO,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAA;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAa,EACb,MAAyB,EACzB,QAAgB;IAEhB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,IAAI,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;IACnC,IAAI,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,CAAA;IAE/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAA;QACxC,IAAI,QAAQ,GAAG,eAAe,EAAE,CAAC;YAC/B,OAAO,GAAG,KAAK,CAAA;YACf,eAAe,GAAG,QAAQ,CAAA;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,yBAAyB,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,CAAC,CAAA;AACpE,CAAC;AAgCD;;;;;GAKG;AACH,MAAM,UAAU,gCAAgC,CAC9C,OAAqD;IAErD,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,OAAO,CAAA;IACxD,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAA;IAEnD,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;IAC7E,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,GAAG,EAA0B,CAAA;IAEvD,KAAK,MAAM,aAAa,IAAI,gBAAgB,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,qDAAqD,aAAa,EAAE,CAAC,CAAA;QACvF,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;QAC3C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,0DAA0D,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE,CAC7F,CAAA;QACH,CAAC;QAED,aAAa,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,GAAG,CAAA;IAElG,MAAM,UAAU,GAAG,IAAI,GAAG,EAA0B,CAAA;IACpD,KAAK,MAAM,aAAa,IAAI,gBAAgB,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QAC/C,IAAI,CAAC,MAAM;YAAE,SAAQ;QACrB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;QACvC,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,CAAC,aAAiC,EAAe,EAAE;QACvE,IAAI,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,aAAa,CAAC,aAA0B,CAAC,CAAA;YACxD,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAA;QAC7D,CAAC;QAED,MAAM,eAAe,GAAG,yBAAyB,CAC/C,aAAa,EACb,gBAAgB,EAChB,aAAa,CACd,CAAA;QACD,OAAO,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,mBAAmB,CAAA;IAClE,CAAC,CAAA;IAED,MAAM,eAAe,GAAG,CAAC,KAAkB,EAAa,EAAE;QACxD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,OAAO,GAAG,aAAa,CAAA;QAC3B,IAAI,eAAe,GAAG,MAAM,CAAC,iBAAiB,CAAA;QAE9C,KAAK,MAAM,aAAa,IAAI,gBAAgB,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,mBAAmB,CAAA;YACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,CAAA;YACzC,IAAI,QAAQ,GAAG,eAAe,EAAE,CAAC;gBAC/B,OAAO,GAAG,aAAa,CAAA;gBACvB,eAAe,GAAG,QAAQ,CAAA;YAC5B,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,CAAC,OAA2B,EAAa,EAAE,CAC/D,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;IAE9D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,MAAM,EAAE,gBAAgB;QACxB,aAAa;QACb,mBAAmB;QACnB,aAAa,EAAE,aAAa;QAC5B,eAAe,EAAE,eAAe;QAChC,kBAAkB,EAAE,aAAa;QACjC,sBAAsB,EAAE,CAAC,OAA2B,EAAE,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;KAC/F,CAAC,CAAA;AACJ,CAAC"}
package/dist/dom.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { EnergyChangeListener, EnergyLevel } from './types.js';
2
+ /**
3
+ * Apply energy level to a root element.
4
+ * Sets `data-energy-level` attribute and CSS custom properties
5
+ * derived from the UI visibility strategy.
6
+ */
7
+ export declare function applyEnergyLevel(level: EnergyLevel, root?: HTMLElement): void;
8
+ /**
9
+ * Read the current energy level from a root element's data attribute.
10
+ * Returns 100 if no valid level is set.
11
+ */
12
+ export declare function readEnergyLevel(root?: HTMLElement): EnergyLevel;
13
+ /**
14
+ * Observe energy level changes on a root element via MutationObserver.
15
+ * Calls back with EnergyState (timestamp will be observation time, source 'inferred').
16
+ * Returns a cleanup function to disconnect the observer.
17
+ */
18
+ export declare function observeEnergyLevel(callback: EnergyChangeListener, root?: HTMLElement): () => void;
19
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAwBnE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAU7E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAI/D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,IAAI,CA0BjG"}
package/dist/dom.js ADDED
@@ -0,0 +1,72 @@
1
+ import { createEnergyState, isEnergyLevel } from './levels.js';
2
+ import { uiVisibilityStrategy } from './strategies.js';
3
+ const ATTR = 'energyLevel';
4
+ function resolveRoot(root) {
5
+ if (root) {
6
+ return root;
7
+ }
8
+ if (typeof document === 'undefined' || !document.body) {
9
+ throw new Error('Energy DOM APIs require a browser document or an explicit root element');
10
+ }
11
+ return document.body;
12
+ }
13
+ function normalizeEnergyLevel(level) {
14
+ if (!isEnergyLevel(level)) {
15
+ throw new Error(`Invalid energy level: ${String(level)}`);
16
+ }
17
+ return level;
18
+ }
19
+ /**
20
+ * Apply energy level to a root element.
21
+ * Sets `data-energy-level` attribute and CSS custom properties
22
+ * derived from the UI visibility strategy.
23
+ */
24
+ export function applyEnergyLevel(level, root) {
25
+ const target = resolveRoot(root);
26
+ const normalizedLevel = normalizeEnergyLevel(level);
27
+ const config = uiVisibilityStrategy.resolve(normalizedLevel);
28
+ target.dataset[ATTR] = normalizedLevel.toString();
29
+ target.style.setProperty('--energy-chrome-opacity', config.chromeOpacity.toString());
30
+ target.style.setProperty('--energy-chrome-opacity-hover', config.chromeOpacityHover.toString());
31
+ target.style.setProperty('--energy-content-max-width', config.contentMaxWidth);
32
+ target.style.setProperty('--energy-content-font-scale', config.contentFontScale.toString());
33
+ }
34
+ /**
35
+ * Read the current energy level from a root element's data attribute.
36
+ * Returns 100 if no valid level is set.
37
+ */
38
+ export function readEnergyLevel(root) {
39
+ const target = resolveRoot(root);
40
+ const raw = Number(target.dataset[ATTR]);
41
+ return isEnergyLevel(raw) ? raw : 100;
42
+ }
43
+ /**
44
+ * Observe energy level changes on a root element via MutationObserver.
45
+ * Calls back with EnergyState (timestamp will be observation time, source 'inferred').
46
+ * Returns a cleanup function to disconnect the observer.
47
+ */
48
+ export function observeEnergyLevel(callback, root) {
49
+ const target = resolveRoot(root);
50
+ let prevLevel = readEnergyLevel(target);
51
+ const observer = new MutationObserver((mutations) => {
52
+ for (const mutation of mutations) {
53
+ if (mutation.type === 'attributes' && mutation.attributeName === 'data-energy-level') {
54
+ const currentLevel = readEnergyLevel(target);
55
+ if (currentLevel !== prevLevel) {
56
+ const prev = createEnergyState(prevLevel, 'inferred', Date.now());
57
+ const current = createEnergyState(currentLevel, 'inferred', Date.now());
58
+ prevLevel = currentLevel;
59
+ callback(current, prev);
60
+ }
61
+ }
62
+ }
63
+ });
64
+ observer.observe(target, {
65
+ attributes: true,
66
+ attributeFilter: ['data-energy-level'],
67
+ });
68
+ return () => {
69
+ observer.disconnect();
70
+ };
71
+ }
72
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { AdaptationStrategy, EnergyClock, EnergyChangeListener, EnergyLevel, EnergyPersistence, EnergySource, EnergyState } from './types.js';
2
+ export interface EnergyEngineOptions {
3
+ initialLevel?: EnergyLevel;
4
+ persistence?: EnergyPersistence;
5
+ onChange?: EnergyChangeListener;
6
+ /** Deterministic time source for tests/simulations */
7
+ clock?: EnergyClock | (() => number);
8
+ }
9
+ export interface EnergyEngine {
10
+ /** Get current energy state */
11
+ getState(): EnergyState;
12
+ /** Set energy level with optional source */
13
+ setLevel(level: EnergyLevel, source?: EnergySource): void;
14
+ /** Cycle to next energy level */
15
+ cycleLevel(): void;
16
+ /** Subscribe to state changes. Returns unsubscribe function. */
17
+ subscribe(listener: EnergyChangeListener): () => void;
18
+ /** Resolve a strategy against current energy state */
19
+ resolve<T>(strategy: AdaptationStrategy<T>): T;
20
+ /** Load persisted state (called automatically, but can be called manually) */
21
+ hydrate(): Promise<void>;
22
+ /** Release engine-owned subscriptions/resources */
23
+ dispose(): void;
24
+ }
25
+ export declare function createEnergyEngine(options?: EnergyEngineOptions): EnergyEngine;
26
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B,sDAAsD;IACtD,KAAK,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,QAAQ,IAAI,WAAW,CAAA;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;IACzD,iCAAiC;IACjC,UAAU,IAAI,IAAI,CAAA;IAClB,gEAAgE;IAChE,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAA;IACrD,sDAAsD;IACtD,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC9C,8EAA8E;IAC9E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,mDAAmD;IACnD,OAAO,IAAI,IAAI,CAAA;CAChB;AAqDD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAmMlF"}