@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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kumbat.io/spec/conformance.schema.json",
|
|
4
|
+
"title": "EnergyConformance",
|
|
5
|
+
"description": "The shape of conformance.json: the executable half of SPEC.md. A port in any language passes by loading that file and asserting its own output against each vector. This schema is what lets that port know the file it loaded is the file it expects — vectors that silently changed shape are worse than vectors that failed to load. scripts/extract-conformance.ts validates its own output against this schema before writing, so the two cannot drift apart unnoticed.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"version",
|
|
10
|
+
"timezone",
|
|
11
|
+
"note",
|
|
12
|
+
"levels",
|
|
13
|
+
"cycle",
|
|
14
|
+
"strategies",
|
|
15
|
+
"presence",
|
|
16
|
+
"decisions",
|
|
17
|
+
"deferral",
|
|
18
|
+
"reconciliation",
|
|
19
|
+
"metrics",
|
|
20
|
+
"externalLevelMapping"
|
|
21
|
+
],
|
|
22
|
+
"$defs": {
|
|
23
|
+
"level": {
|
|
24
|
+
"description": "The five discrete capacities. Never a continuum.",
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"enum": [0, 25, 50, 75, 100]
|
|
27
|
+
},
|
|
28
|
+
"presence": {
|
|
29
|
+
"description": "What a projected element does at a level. `unmounted` is a host/adapter choice rather than a table value, so it does not appear in these vectors.",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"enum": ["visible", "muted", "hidden", "unmounted"]
|
|
32
|
+
},
|
|
33
|
+
"strategyTable": {
|
|
34
|
+
"description": "One strategy's resolved config at every level. Keys are strings because JSON object keys are strings, and all five must be present: a gap in a port's table falls through silently at exactly the level it mishandles.",
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"required": ["0", "25", "50", "75", "100"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"0": { "type": "object", "minProperties": 1 },
|
|
40
|
+
"25": { "type": "object", "minProperties": 1 },
|
|
41
|
+
"50": { "type": "object", "minProperties": 1 },
|
|
42
|
+
"75": { "type": "object", "minProperties": 1 },
|
|
43
|
+
"100": { "type": "object", "minProperties": 1 }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"presenceRow": {
|
|
47
|
+
"description": "What each level resolves to for one helper threshold.",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["0", "25", "50", "75", "100"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"0": { "$ref": "#/$defs/presence" },
|
|
53
|
+
"25": { "$ref": "#/$defs/presence" },
|
|
54
|
+
"50": { "$ref": "#/$defs/presence" },
|
|
55
|
+
"75": { "$ref": "#/$defs/presence" },
|
|
56
|
+
"100": { "$ref": "#/$defs/presence" }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"presenceTable": {
|
|
60
|
+
"description": "Keyed by the helper's threshold level, then by the current level.",
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"required": ["0", "25", "50", "75", "100"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"0": { "$ref": "#/$defs/presenceRow" },
|
|
66
|
+
"25": { "$ref": "#/$defs/presenceRow" },
|
|
67
|
+
"50": { "$ref": "#/$defs/presenceRow" },
|
|
68
|
+
"75": { "$ref": "#/$defs/presenceRow" },
|
|
69
|
+
"100": { "$ref": "#/$defs/presenceRow" }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"instant": {
|
|
73
|
+
"description": "An epoch-millisecond instant paired with its ISO rendering, so a port can compare either without re-deriving one from the other.",
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"required": ["epochMs", "iso"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"epochMs": { "type": "integer", "minimum": 0 },
|
|
79
|
+
"iso": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"properties": {
|
|
87
|
+
"$schema": {
|
|
88
|
+
"description": "Reference to this schema, relative to the package root so it resolves inside the published tarball as well as in the repository.",
|
|
89
|
+
"type": "string",
|
|
90
|
+
"const": "./spec/conformance.schema.json"
|
|
91
|
+
},
|
|
92
|
+
"version": {
|
|
93
|
+
"description": "The package version these vectors were generated from. Always equal to api-surface.json's version.",
|
|
94
|
+
"type": "string",
|
|
95
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+"
|
|
96
|
+
},
|
|
97
|
+
"timezone": {
|
|
98
|
+
"description": "The zone the deferral vectors were computed in. The presets are local-time by design, so the vectors only mean anything alongside the zone that produced them.",
|
|
99
|
+
"const": "UTC"
|
|
100
|
+
},
|
|
101
|
+
"note": { "type": "string", "minLength": 1 },
|
|
102
|
+
|
|
103
|
+
"levels": {
|
|
104
|
+
"description": "Level definitions, including the cognitive profile a port must reproduce.",
|
|
105
|
+
"type": "array",
|
|
106
|
+
"minItems": 5,
|
|
107
|
+
"maxItems": 5,
|
|
108
|
+
"items": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"required": ["value", "key", "label", "description", "cognitiveProfile"],
|
|
112
|
+
"properties": {
|
|
113
|
+
"value": { "$ref": "#/$defs/level" },
|
|
114
|
+
"key": { "type": "string", "minLength": 1 },
|
|
115
|
+
"label": { "type": "string", "minLength": 1 },
|
|
116
|
+
"description": { "type": "string", "minLength": 1 },
|
|
117
|
+
"cognitiveProfile": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": [
|
|
121
|
+
"decisionCapacity",
|
|
122
|
+
"focusDuration",
|
|
123
|
+
"taskComplexity",
|
|
124
|
+
"interruptionTolerance"
|
|
125
|
+
],
|
|
126
|
+
"properties": {
|
|
127
|
+
"decisionCapacity": { "type": "string", "minLength": 1 },
|
|
128
|
+
"focusDuration": { "type": "string", "minLength": 1 },
|
|
129
|
+
"taskComplexity": { "type": "string", "minLength": 1 },
|
|
130
|
+
"interruptionTolerance": { "type": "string", "minLength": 1 }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
"cycle": {
|
|
138
|
+
"description": "100 -> 75 -> 50 -> 25 -> 0 -> 100.",
|
|
139
|
+
"type": "array",
|
|
140
|
+
"minItems": 5,
|
|
141
|
+
"maxItems": 5,
|
|
142
|
+
"items": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"additionalProperties": false,
|
|
145
|
+
"required": ["from", "to"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"from": { "$ref": "#/$defs/level" },
|
|
148
|
+
"to": { "$ref": "#/$defs/level" }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
"strategies": {
|
|
154
|
+
"description": "Every built-in strategy's resolved config at every level. Config shapes differ per strategy; their field-level meaning is fixed by SPEC.md and re-asserted by replaying each table through the public API in test/conformance.test.ts.",
|
|
155
|
+
"type": "object",
|
|
156
|
+
"additionalProperties": false,
|
|
157
|
+
"required": [
|
|
158
|
+
"ui-visibility",
|
|
159
|
+
"notifications",
|
|
160
|
+
"task-complexity",
|
|
161
|
+
"interaction-forgiveness",
|
|
162
|
+
"deferral",
|
|
163
|
+
"autonomy",
|
|
164
|
+
"demand-admission"
|
|
165
|
+
],
|
|
166
|
+
"properties": {
|
|
167
|
+
"ui-visibility": { "$ref": "#/$defs/strategyTable" },
|
|
168
|
+
"notifications": { "$ref": "#/$defs/strategyTable" },
|
|
169
|
+
"task-complexity": { "$ref": "#/$defs/strategyTable" },
|
|
170
|
+
"interaction-forgiveness": { "$ref": "#/$defs/strategyTable" },
|
|
171
|
+
"deferral": { "$ref": "#/$defs/strategyTable" },
|
|
172
|
+
"autonomy": { "$ref": "#/$defs/strategyTable" },
|
|
173
|
+
"demand-admission": { "$ref": "#/$defs/strategyTable" }
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
"presence": {
|
|
178
|
+
"description": "Presence helpers, which are pure maps over the level set.",
|
|
179
|
+
"type": "object",
|
|
180
|
+
"additionalProperties": false,
|
|
181
|
+
"required": ["atOrAbove", "atOrAboveMuted", "atOrBelow"],
|
|
182
|
+
"properties": {
|
|
183
|
+
"atOrAbove": { "$ref": "#/$defs/presenceTable" },
|
|
184
|
+
"atOrAboveMuted": { "$ref": "#/$defs/presenceTable" },
|
|
185
|
+
"atOrBelow": { "$ref": "#/$defs/presenceTable" }
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
"decisions": {
|
|
190
|
+
"description": "The full decision matrix of both pure gating functions.",
|
|
191
|
+
"type": "object",
|
|
192
|
+
"additionalProperties": false,
|
|
193
|
+
"required": ["notification", "demand"],
|
|
194
|
+
"properties": {
|
|
195
|
+
"notification": {
|
|
196
|
+
"type": "array",
|
|
197
|
+
"minItems": 1,
|
|
198
|
+
"items": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"additionalProperties": false,
|
|
201
|
+
"required": ["level", "priority", "suppressed", "outcome"],
|
|
202
|
+
"properties": {
|
|
203
|
+
"level": { "$ref": "#/$defs/level" },
|
|
204
|
+
"priority": { "type": "string", "enum": ["normal", "high", "critical"] },
|
|
205
|
+
"suppressed": { "type": "boolean" },
|
|
206
|
+
"outcome": { "type": "string", "enum": ["delivered", "batched", "deferred"] }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"demand": {
|
|
211
|
+
"type": "array",
|
|
212
|
+
"minItems": 1,
|
|
213
|
+
"items": {
|
|
214
|
+
"type": "object",
|
|
215
|
+
"additionalProperties": false,
|
|
216
|
+
"required": ["level", "originatorTier", "bearsObligation", "confidence", "outcome"],
|
|
217
|
+
"properties": {
|
|
218
|
+
"level": { "$ref": "#/$defs/level" },
|
|
219
|
+
"originatorTier": { "type": "string", "enum": ["exempt", "known", "unknown"] },
|
|
220
|
+
"bearsObligation": { "type": "boolean" },
|
|
221
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
222
|
+
"outcome": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"additionalProperties": false,
|
|
225
|
+
"required": ["admission", "acknowledgment", "reason"],
|
|
226
|
+
"properties": {
|
|
227
|
+
"admission": { "type": "string", "enum": ["live", "acknowledge", "silent"] },
|
|
228
|
+
"acknowledgment": {
|
|
229
|
+
"description": "null when the policy admits or drops without acknowledging.",
|
|
230
|
+
"type": ["object", "null"],
|
|
231
|
+
"additionalProperties": false,
|
|
232
|
+
"required": ["detail", "allowGeneratedContent"],
|
|
233
|
+
"properties": {
|
|
234
|
+
"detail": { "type": "string", "enum": ["full", "brief", "minimal"] },
|
|
235
|
+
"allowGeneratedContent": { "type": "boolean" }
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"reason": {
|
|
239
|
+
"type": "string",
|
|
240
|
+
"enum": [
|
|
241
|
+
"exempt-originator",
|
|
242
|
+
"tier-admitted",
|
|
243
|
+
"acknowledged",
|
|
244
|
+
"no-obligation",
|
|
245
|
+
"below-confidence"
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
"deferral": {
|
|
257
|
+
"description": "Deferral arithmetic, resolved against fixed reference instants. Local-time by design, hence the `timezone` field above.",
|
|
258
|
+
"type": "object",
|
|
259
|
+
"additionalProperties": false,
|
|
260
|
+
"required": ["morningHour", "eveningHour", "references"],
|
|
261
|
+
"properties": {
|
|
262
|
+
"morningHour": { "type": "integer", "minimum": 0, "maximum": 23 },
|
|
263
|
+
"eveningHour": { "type": "integer", "minimum": 0, "maximum": 23 },
|
|
264
|
+
"references": {
|
|
265
|
+
"type": "array",
|
|
266
|
+
"minItems": 1,
|
|
267
|
+
"items": {
|
|
268
|
+
"type": "object",
|
|
269
|
+
"additionalProperties": false,
|
|
270
|
+
"required": ["label", "iso", "epochMs", "resolved"],
|
|
271
|
+
"properties": {
|
|
272
|
+
"label": { "type": "string", "minLength": 1 },
|
|
273
|
+
"iso": {
|
|
274
|
+
"type": "string",
|
|
275
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$"
|
|
276
|
+
},
|
|
277
|
+
"epochMs": { "type": "integer", "minimum": 0 },
|
|
278
|
+
"resolved": {
|
|
279
|
+
"description": "Every shipped preset, resolved against this reference instant.",
|
|
280
|
+
"type": "object",
|
|
281
|
+
"additionalProperties": false,
|
|
282
|
+
"required": [
|
|
283
|
+
"in-1-hour",
|
|
284
|
+
"this-evening",
|
|
285
|
+
"tomorrow-morning",
|
|
286
|
+
"next-workday",
|
|
287
|
+
"next-monday"
|
|
288
|
+
],
|
|
289
|
+
"properties": {
|
|
290
|
+
"in-1-hour": { "$ref": "#/$defs/instant" },
|
|
291
|
+
"this-evening": { "$ref": "#/$defs/instant" },
|
|
292
|
+
"tomorrow-morning": { "$ref": "#/$defs/instant" },
|
|
293
|
+
"next-workday": { "$ref": "#/$defs/instant" },
|
|
294
|
+
"next-monday": { "$ref": "#/$defs/instant" }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
"reconciliation": {
|
|
304
|
+
"description": "The ordering rule two independent producers converge on. Each pair is stated in both directions: a rule that is not antisymmetric does not converge.",
|
|
305
|
+
"type": "object",
|
|
306
|
+
"additionalProperties": false,
|
|
307
|
+
"required": ["sourcePriority", "pairs"],
|
|
308
|
+
"properties": {
|
|
309
|
+
"sourcePriority": {
|
|
310
|
+
"type": "object",
|
|
311
|
+
"additionalProperties": false,
|
|
312
|
+
"required": ["manual", "scheduled", "inferred"],
|
|
313
|
+
"properties": {
|
|
314
|
+
"manual": { "const": 3 },
|
|
315
|
+
"scheduled": { "const": 2 },
|
|
316
|
+
"inferred": { "const": 1 }
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"pairs": {
|
|
320
|
+
"type": "array",
|
|
321
|
+
"minItems": 1,
|
|
322
|
+
"items": {
|
|
323
|
+
"type": "object",
|
|
324
|
+
"additionalProperties": false,
|
|
325
|
+
"required": ["rule", "candidate", "current", "expected", "expectedReversed"],
|
|
326
|
+
"properties": {
|
|
327
|
+
"rule": { "type": "string", "minLength": 1 },
|
|
328
|
+
"candidate": { "$ref": "https://kumbat.io/spec/energy-state.schema.json" },
|
|
329
|
+
"current": { "$ref": "https://kumbat.io/spec/energy-state.schema.json" },
|
|
330
|
+
"expected": { "type": "boolean" },
|
|
331
|
+
"expectedReversed": { "type": "boolean" }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
"metrics": {
|
|
339
|
+
"description": "Derived metrics at a fixed state age, so a port checks its arithmetic rather than its clock.",
|
|
340
|
+
"type": "array",
|
|
341
|
+
"minItems": 5,
|
|
342
|
+
"maxItems": 5,
|
|
343
|
+
"items": {
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": false,
|
|
346
|
+
"required": ["level", "stateTimestamp", "now", "metrics"],
|
|
347
|
+
"properties": {
|
|
348
|
+
"level": { "$ref": "#/$defs/level" },
|
|
349
|
+
"stateTimestamp": { "type": "integer", "minimum": 0 },
|
|
350
|
+
"now": { "type": "integer", "minimum": 0 },
|
|
351
|
+
"metrics": {
|
|
352
|
+
"type": "object",
|
|
353
|
+
"additionalProperties": false,
|
|
354
|
+
"required": [
|
|
355
|
+
"stateAgeMs",
|
|
356
|
+
"stateAgeMinutes",
|
|
357
|
+
"expectedProductivityWindowMinutes",
|
|
358
|
+
"suggestedBreakIntervalMinutes",
|
|
359
|
+
"recommendedTaskComplexity",
|
|
360
|
+
"sustainable"
|
|
361
|
+
],
|
|
362
|
+
"properties": {
|
|
363
|
+
"stateAgeMs": { "type": "integer", "minimum": 0 },
|
|
364
|
+
"stateAgeMinutes": { "type": "integer", "minimum": 0 },
|
|
365
|
+
"expectedProductivityWindowMinutes": { "type": "integer", "minimum": 0 },
|
|
366
|
+
"suggestedBreakIntervalMinutes": { "type": "integer", "minimum": 0 },
|
|
367
|
+
"recommendedTaskComplexity": { "type": "string", "minLength": 1 },
|
|
368
|
+
"sustainable": { "type": "boolean" },
|
|
369
|
+
"recoveryHintMinutes": {
|
|
370
|
+
"description": "Present only at the levels that have a recovery hint (50 and below); absent, not null, where there is nothing to suggest.",
|
|
371
|
+
"type": "integer",
|
|
372
|
+
"minimum": 0
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
"externalLevelMapping": {
|
|
381
|
+
"description": "Percentages an external model might hand over, including both midpoints — 12.5 and 37.5 sit exactly between two levels, which is where rounding implementations disagree.",
|
|
382
|
+
"type": "array",
|
|
383
|
+
"minItems": 1,
|
|
384
|
+
"items": {
|
|
385
|
+
"type": "object",
|
|
386
|
+
"additionalProperties": false,
|
|
387
|
+
"required": ["input", "level"],
|
|
388
|
+
"properties": {
|
|
389
|
+
"input": { "type": "number" },
|
|
390
|
+
"level": { "$ref": "#/$defs/level" }
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kumbat.io/spec/energy-state.schema.json",
|
|
4
|
+
"title": "EnergyState",
|
|
5
|
+
"description": "One producer's point-in-time claim about a person's cognitive capacity. This is the interchange format: the shape an implementation writes to shared storage, sends over a wire, or hands to another process. Two implementations that agree on this schema and on the reconciliation rule in SPEC.md converge on the same current state without further coordination.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["level", "timestamp", "source", "revision", "origin"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"level": {
|
|
11
|
+
"description": "The declared capacity. Five discrete values, never a continuum: a slider invites precision nobody has about their own state, and five is the most a depleted person should have to choose between.",
|
|
12
|
+
"type": "integer",
|
|
13
|
+
"enum": [0, 25, 50, 75, 100]
|
|
14
|
+
},
|
|
15
|
+
"timestamp": {
|
|
16
|
+
"description": "When this state was produced, in milliseconds since the Unix epoch. The primary ordering key. 0 is reserved for the unproduced sentinel (see SPEC.md).",
|
|
17
|
+
"type": "integer",
|
|
18
|
+
"minimum": 0
|
|
19
|
+
},
|
|
20
|
+
"source": {
|
|
21
|
+
"description": "How the level was arrived at. Load-bearing during reconciliation: manual outranks scheduled outranks inferred, so nothing the system worked out on its own overwrites what the person said.",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["manual", "scheduled", "inferred"]
|
|
24
|
+
},
|
|
25
|
+
"revision": {
|
|
26
|
+
"description": "Logical sequence number among states sharing one timestamp. Two writes inside a clock tick are not simultaneous, and this is what keeps them ordered.",
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": 0,
|
|
29
|
+
"maximum": 9007199254740991
|
|
30
|
+
},
|
|
31
|
+
"origin": {
|
|
32
|
+
"description": "Stable identity of the producer — an engine instance, a device, a process. Must be unique per producer and stable across that producer's writes. The final tiebreaker compares it lexicographically, so a producer that reuses one identity for different state breaks convergence.",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"examples": [
|
|
38
|
+
{
|
|
39
|
+
"level": 50,
|
|
40
|
+
"timestamp": 1786060800000,
|
|
41
|
+
"source": "manual",
|
|
42
|
+
"revision": 0,
|
|
43
|
+
"origin": "a4f1c2e8-0f3b-4a91-9f2d-6c1b2e5d8a70"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"level": 100,
|
|
47
|
+
"timestamp": 0,
|
|
48
|
+
"source": "manual",
|
|
49
|
+
"revision": 0,
|
|
50
|
+
"origin": "0-initial"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
package/src/energy.css
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* applyEnergyLevel() sets the same properties as inline styles, which take
|
|
19
19
|
* precedence but resolve to identical values, so the JS and CSS-only paths
|
|
20
20
|
* always agree.
|
|
21
|
+
*
|
|
22
|
+
* The single exception is the accessibility block at the end of this file, which
|
|
23
|
+
* declares `!important` deliberately so a user's contrast preference outranks
|
|
24
|
+
* both paths. See the comment there.
|
|
21
25
|
*/
|
|
22
26
|
|
|
23
27
|
/* ── Custom properties per level (mirror of uiVisibilityStrategy) ── */
|
|
@@ -129,6 +133,8 @@
|
|
|
129
133
|
opacity: var(--energy-chrome-opacity-hover, 0.8);
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
/* `font-size` scales relative to the parent, so this expects ONE .energy-content
|
|
137
|
+
* per tree. Nesting them compounds the scale (1.1 inside 1.1 is 1.21). */
|
|
132
138
|
[data-energy-level='0'] .energy-content {
|
|
133
139
|
max-width: var(--energy-content-max-width, 75ch);
|
|
134
140
|
margin-inline: auto;
|
|
@@ -138,6 +144,10 @@
|
|
|
138
144
|
cursor: default !important;
|
|
139
145
|
}
|
|
140
146
|
|
|
147
|
+
/* A cursor is a HINT, and one only a pointer user receives. It changes nothing
|
|
148
|
+
* about what is focusable or clickable, so an app that means "read only" at Rest
|
|
149
|
+
* must also disable the controls — otherwise a keyboard user gets no signal at
|
|
150
|
+
* all and a pointer user gets a signal that turns out to be false. */
|
|
141
151
|
[data-energy-level='0'] .energy-content * {
|
|
142
152
|
cursor: default !important;
|
|
143
153
|
}
|
|
@@ -202,3 +212,70 @@
|
|
|
202
212
|
transition: none;
|
|
203
213
|
}
|
|
204
214
|
}
|
|
215
|
+
|
|
216
|
+
/* ── Contrast obligations ──
|
|
217
|
+
*
|
|
218
|
+
* Be honest about what the resting values are: chrome sits at 10% opacity at
|
|
219
|
+
* level 25 and 5% at level 0, and neither meets WCAG 1.4.11 (Non-text Contrast,
|
|
220
|
+
* 3:1 for UI components) in its resting state. That is a deliberate design
|
|
221
|
+
* default for a receding interface, not a conformance claim — and it is why
|
|
222
|
+
* every value here is a custom property an app can raise in one line.
|
|
223
|
+
*
|
|
224
|
+
* Two populations must not be left to do that themselves:
|
|
225
|
+
*
|
|
226
|
+
* `prefers-contrast: more` is a person explicitly asking the OS for more
|
|
227
|
+
* contrast. Fading their chrome to 5% is answering the opposite of the question.
|
|
228
|
+
* Chrome still recedes with energy, but never below a legible floor.
|
|
229
|
+
*
|
|
230
|
+
* `forced-colors: active` (Windows High Contrast and friends) overrides colors
|
|
231
|
+
* but NOT opacity, so faded chrome stays faded while everything around it snaps
|
|
232
|
+
* to a system palette — the fade is no longer a soft de-emphasis, it is the only
|
|
233
|
+
* thing still hiding content. Opacity comes off entirely.
|
|
234
|
+
*
|
|
235
|
+
* Both blocks declare `!important`, which is load-bearing rather than a shortcut.
|
|
236
|
+
* `applyEnergyLevel()` writes these same custom properties as INLINE styles, and
|
|
237
|
+
* an inline declaration outranks an ordinary rule — so without this, the JS path
|
|
238
|
+
* would silently defeat the user's stated contrast preference while the CSS-only
|
|
239
|
+
* path honoured it. An important author declaration beats a normal inline one,
|
|
240
|
+
* which is exactly the ordering wanted here: a preference the person set outranks
|
|
241
|
+
* a value the program computed. */
|
|
242
|
+
@media (prefers-contrast: more) {
|
|
243
|
+
[data-energy-level='75'] {
|
|
244
|
+
--energy-chrome-opacity: 0.9 !important;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
[data-energy-level='50'] {
|
|
248
|
+
--energy-chrome-opacity: 0.8 !important;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
[data-energy-level='25'] {
|
|
252
|
+
--energy-chrome-opacity: 0.7 !important;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[data-energy-level='0'] {
|
|
256
|
+
--energy-chrome-opacity: 0.7 !important;
|
|
257
|
+
--energy-chrome-opacity-hover: 1 !important;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
[data-energy-level] {
|
|
261
|
+
--energy-muted-opacity: 0.85 !important;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media (forced-colors: active) {
|
|
266
|
+
[data-energy-level] {
|
|
267
|
+
--energy-chrome-opacity: 1 !important;
|
|
268
|
+
--energy-chrome-opacity-hover: 1 !important;
|
|
269
|
+
--energy-muted-opacity: 1 !important;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
[data-energy-level] .energy-chrome {
|
|
273
|
+
opacity: 1 !important;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* `hidden` presence stays hidden: it is display, not contrast, and the
|
|
277
|
+
* element is meant to be absent from the tree at this level. */
|
|
278
|
+
[data-energy-presence='muted'] {
|
|
279
|
+
opacity: 1 !important;
|
|
280
|
+
}
|
|
281
|
+
}
|