@directive-run/knowledge 0.2.0 → 0.4.2

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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/ai/ai-adapters.md +7 -7
  3. package/ai/ai-agents-streaming.md +8 -8
  4. package/ai/ai-budget-resilience.md +5 -5
  5. package/ai/ai-communication.md +1 -1
  6. package/ai/ai-guardrails-memory.md +7 -7
  7. package/ai/ai-mcp-rag.md +5 -5
  8. package/ai/ai-multi-agent.md +14 -14
  9. package/ai/ai-orchestrator.md +8 -8
  10. package/ai/ai-security.md +2 -2
  11. package/ai/ai-tasks.md +9 -9
  12. package/ai/ai-testing-evals.md +2 -2
  13. package/core/anti-patterns.md +39 -39
  14. package/core/constraints.md +15 -15
  15. package/core/core-patterns.md +9 -9
  16. package/core/error-boundaries.md +7 -7
  17. package/core/multi-module.md +16 -16
  18. package/core/naming.md +21 -21
  19. package/core/plugins.md +14 -14
  20. package/core/react-adapter.md +13 -13
  21. package/core/resolvers.md +14 -14
  22. package/core/schema-types.md +22 -22
  23. package/core/system-api.md +16 -16
  24. package/core/testing.md +5 -5
  25. package/core/time-travel.md +20 -20
  26. package/dist/index.cjs +6 -105
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.js +7 -97
  29. package/dist/index.js.map +1 -1
  30. package/examples/ab-testing.ts +18 -90
  31. package/examples/ai-checkpoint.ts +68 -87
  32. package/examples/ai-guardrails.ts +20 -70
  33. package/examples/auth-flow.ts +2 -2
  34. package/examples/batch-resolver.ts +19 -59
  35. package/examples/contact-form.ts +220 -69
  36. package/examples/counter.ts +77 -95
  37. package/examples/dashboard-loader.ts +37 -55
  38. package/examples/debounce-constraints.ts +0 -2
  39. package/examples/dynamic-modules.ts +17 -20
  40. package/examples/error-boundaries.ts +30 -81
  41. package/examples/newsletter.ts +22 -49
  42. package/examples/notifications.ts +24 -23
  43. package/examples/optimistic-updates.ts +36 -41
  44. package/examples/pagination.ts +2 -2
  45. package/examples/permissions.ts +22 -32
  46. package/examples/provider-routing.ts +26 -83
  47. package/examples/shopping-cart.ts +8 -8
  48. package/examples/sudoku.ts +55 -62
  49. package/examples/theme-locale.ts +4 -7
  50. package/examples/time-machine.ts +12 -90
  51. package/examples/topic-guard.ts +30 -38
  52. package/examples/url-sync.ts +8 -8
  53. package/examples/websocket.ts +5 -5
  54. package/package.json +3 -3
@@ -70,7 +70,7 @@ export const dashboardLoaderSchema = {
70
70
  preferencesFailRate: t.number(),
71
71
  permissionsFailRate: t.number(),
72
72
  loadRequested: t.boolean(),
73
- eventLog: t.object<EventLogEntry[]>(),
73
+ eventLog: t.array<EventLogEntry>(),
74
74
  },
75
75
  derivations: {
76
76
  loadedCount: t.number(),
@@ -106,7 +106,7 @@ function addLogEntry(
106
106
  resource: string,
107
107
  detail: string,
108
108
  ): void {
109
- const log = [...(facts.eventLog as EventLogEntry[])];
109
+ const log = [...facts.eventLog];
110
110
  log.push({ timestamp: Date.now(), event, resource, detail });
111
111
  facts.eventLog = log;
112
112
  }
@@ -181,9 +181,9 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
181
181
  },
182
182
 
183
183
  combinedStatus: (facts, derive) => {
184
- const loaded = derive.loadedCount as number;
185
- const anyErr = derive.anyError as boolean;
186
- const anyLoad = derive.anyLoading as boolean;
184
+ const loaded = derive.loadedCount;
185
+ const anyErr = derive.anyError;
186
+ const anyLoad = derive.anyLoading;
187
187
  const allIdle = [
188
188
  facts.profile,
189
189
  facts.preferences,
@@ -216,7 +216,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
216
216
  },
217
217
 
218
218
  canStart: (facts) => {
219
- const id = (facts.userId as string).trim();
219
+ const id = facts.userId.trim();
220
220
  const allIdle = [
221
221
  facts.profile,
222
222
  facts.preferences,
@@ -237,7 +237,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
237
237
  },
238
238
 
239
239
  start: (facts) => {
240
- const id = (facts.userId as string).trim();
240
+ const id = facts.userId.trim();
241
241
  if (id.length === 0) {
242
242
  return;
243
243
  }
@@ -293,54 +293,42 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
293
293
  needsProfile: {
294
294
  priority: 100,
295
295
  when: (facts) => {
296
- const id = (facts.userId as string).trim();
297
- const profile = facts.profile as ResourceState<Profile>;
296
+ const id = facts.userId.trim();
297
+ const profile = facts.profile;
298
298
 
299
- return (
300
- (facts.loadRequested as boolean) &&
301
- id !== "" &&
302
- profile.status === "idle"
303
- );
299
+ return facts.loadRequested && id !== "" && profile.status === "idle";
304
300
  },
305
301
  require: (facts) => ({
306
302
  type: "FETCH_PROFILE",
307
- userId: (facts.userId as string).trim(),
303
+ userId: facts.userId.trim(),
308
304
  }),
309
305
  },
310
306
 
311
307
  needsPreferences: {
312
308
  priority: 90,
313
309
  when: (facts) => {
314
- const id = (facts.userId as string).trim();
315
- const prefs = facts.preferences as ResourceState<Preferences>;
310
+ const id = facts.userId.trim();
311
+ const prefs = facts.preferences;
316
312
 
317
- return (
318
- (facts.loadRequested as boolean) &&
319
- id !== "" &&
320
- prefs.status === "idle"
321
- );
313
+ return facts.loadRequested && id !== "" && prefs.status === "idle";
322
314
  },
323
315
  require: (facts) => ({
324
316
  type: "FETCH_PREFERENCES",
325
- userId: (facts.userId as string).trim(),
317
+ userId: facts.userId.trim(),
326
318
  }),
327
319
  },
328
320
 
329
321
  needsPermissions: {
330
322
  priority: 80,
331
323
  when: (facts) => {
332
- const id = (facts.userId as string).trim();
333
- const perms = facts.permissions as ResourceState<Permissions>;
324
+ const id = facts.userId.trim();
325
+ const perms = facts.permissions;
334
326
 
335
- return (
336
- (facts.loadRequested as boolean) &&
337
- id !== "" &&
338
- perms.status === "idle"
339
- );
327
+ return facts.loadRequested && id !== "" && perms.status === "idle";
340
328
  },
341
329
  require: (facts) => ({
342
330
  type: "FETCH_PERMISSIONS",
343
- userId: (facts.userId as string).trim(),
331
+ userId: facts.userId.trim(),
344
332
  }),
345
333
  },
346
334
  },
@@ -355,7 +343,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
355
343
  retry: { attempts: 3, backoff: "exponential" },
356
344
  timeout: 10000,
357
345
  resolve: async (req, context) => {
358
- const prev = context.facts.profile as ResourceState<Profile>;
346
+ const prev = context.facts.profile;
359
347
  context.facts.profile = {
360
348
  ...prev,
361
349
  status: "loading",
@@ -372,24 +360,22 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
372
360
  try {
373
361
  const data = await fetchMockProfile(
374
362
  req.userId,
375
- context.facts.profileDelay as number,
376
- context.facts.profileFailRate as number,
363
+ context.facts.profileDelay,
364
+ context.facts.profileFailRate,
377
365
  );
378
366
  context.facts.profile = {
379
367
  data,
380
368
  status: "success",
381
369
  error: null,
382
- attempts: (context.facts.profile as ResourceState<Profile>)
383
- .attempts,
384
- startedAt: (context.facts.profile as ResourceState<Profile>)
385
- .startedAt,
370
+ attempts: context.facts.profile.attempts,
371
+ startedAt: context.facts.profile.startedAt,
386
372
  completedAt: Date.now(),
387
373
  };
388
374
  addLogEntry(context.facts, "success", "profile", data.name);
389
375
  } catch (err) {
390
376
  const msg = err instanceof Error ? err.message : "Unknown error";
391
377
  context.facts.profile = {
392
- ...(context.facts.profile as ResourceState<Profile>),
378
+ ...context.facts.profile,
393
379
  status: "error",
394
380
  error: msg,
395
381
  completedAt: Date.now(),
@@ -404,7 +390,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
404
390
  requirement: "FETCH_PREFERENCES",
405
391
  retry: { attempts: 2, backoff: "exponential" },
406
392
  resolve: async (req, context) => {
407
- const prev = context.facts.preferences as ResourceState<Preferences>;
393
+ const prev = context.facts.preferences;
408
394
  context.facts.preferences = {
409
395
  ...prev,
410
396
  status: "loading",
@@ -421,17 +407,15 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
421
407
  try {
422
408
  const data = await fetchMockPreferences(
423
409
  req.userId,
424
- context.facts.preferencesDelay as number,
425
- context.facts.preferencesFailRate as number,
410
+ context.facts.preferencesDelay,
411
+ context.facts.preferencesFailRate,
426
412
  );
427
413
  context.facts.preferences = {
428
414
  data,
429
415
  status: "success",
430
416
  error: null,
431
- attempts: (context.facts.preferences as ResourceState<Preferences>)
432
- .attempts,
433
- startedAt: (context.facts.preferences as ResourceState<Preferences>)
434
- .startedAt,
417
+ attempts: context.facts.preferences.attempts,
418
+ startedAt: context.facts.preferences.startedAt,
435
419
  completedAt: Date.now(),
436
420
  };
437
421
  addLogEntry(
@@ -443,7 +427,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
443
427
  } catch (err) {
444
428
  const msg = err instanceof Error ? err.message : "Unknown error";
445
429
  context.facts.preferences = {
446
- ...(context.facts.preferences as ResourceState<Preferences>),
430
+ ...context.facts.preferences,
447
431
  status: "error",
448
432
  error: msg,
449
433
  completedAt: Date.now(),
@@ -459,7 +443,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
459
443
  retry: { attempts: 3, backoff: "exponential" },
460
444
  timeout: 15000,
461
445
  resolve: async (req, context) => {
462
- const prev = context.facts.permissions as ResourceState<Permissions>;
446
+ const prev = context.facts.permissions;
463
447
  context.facts.permissions = {
464
448
  ...prev,
465
449
  status: "loading",
@@ -476,17 +460,15 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
476
460
  try {
477
461
  const data = await fetchMockPermissions(
478
462
  req.userId,
479
- context.facts.permissionsDelay as number,
480
- context.facts.permissionsFailRate as number,
463
+ context.facts.permissionsDelay,
464
+ context.facts.permissionsFailRate,
481
465
  );
482
466
  context.facts.permissions = {
483
467
  data,
484
468
  status: "success",
485
469
  error: null,
486
- attempts: (context.facts.permissions as ResourceState<Permissions>)
487
- .attempts,
488
- startedAt: (context.facts.permissions as ResourceState<Permissions>)
489
- .startedAt,
470
+ attempts: context.facts.permissions.attempts,
471
+ startedAt: context.facts.permissions.startedAt,
490
472
  completedAt: Date.now(),
491
473
  };
492
474
  addLogEntry(
@@ -498,7 +480,7 @@ export const dashboardLoaderModule = createModule("dashboard-loader", {
498
480
  } catch (err) {
499
481
  const msg = err instanceof Error ? err.message : "Unknown error";
500
482
  context.facts.permissions = {
501
- ...(context.facts.permissions as ResourceState<Permissions>),
483
+ ...context.facts.permissions,
502
484
  status: "error",
503
485
  error: msg,
504
486
  completedAt: Date.now(),
@@ -14,8 +14,6 @@
14
14
  import { createSystem } from "@directive-run/core";
15
15
  import { devtoolsPlugin } from "@directive-run/core/plugins";
16
16
  import {
17
- type EventLogEntry,
18
- type SearchResult,
19
17
  debounceSearchModule,
20
18
  debounceSearchSchema,
21
19
  } from "./debounce-search.js";
@@ -28,7 +28,7 @@ export interface EventLogEntry {
28
28
  // ============================================================================
29
29
 
30
30
  function addLogEntry(facts: any, event: string, detail: string): void {
31
- const log = [...(facts.eventLog as EventLogEntry[])];
31
+ const log = [...facts.eventLog];
32
32
  log.push({ timestamp: Date.now(), event, detail });
33
33
  if (log.length > 50) {
34
34
  log.splice(0, log.length - 50);
@@ -42,8 +42,8 @@ function addLogEntry(facts: any, event: string, detail: string): void {
42
42
 
43
43
  export const dashboardSchema = {
44
44
  facts: {
45
- loadedModules: t.object<string[]>(),
46
- eventLog: t.object<EventLogEntry[]>(),
45
+ loadedModules: t.array<string>(),
46
+ eventLog: t.array<EventLogEntry>(),
47
47
  },
48
48
  derivations: {
49
49
  loadedCount: t.number(),
@@ -63,12 +63,12 @@ export const dashboardModule = createModule("dashboard", {
63
63
  },
64
64
 
65
65
  derive: {
66
- loadedCount: (facts) => (facts.loadedModules as string[]).length,
66
+ loadedCount: (facts) => facts.loadedModules.length,
67
67
  },
68
68
 
69
69
  events: {
70
70
  moduleLoaded: (facts, { name }) => {
71
- facts.loadedModules = [...(facts.loadedModules as string[]), name];
71
+ facts.loadedModules = [...facts.loadedModules, name];
72
72
  addLogEntry(facts, "loaded", `Loaded "${name}" module`);
73
73
  },
74
74
  },
@@ -105,18 +105,15 @@ export const counterModule = createModule("counter", {
105
105
  },
106
106
 
107
107
  derive: {
108
- isNearMax: (facts) => (facts.count as number) >= 90,
108
+ isNearMax: (facts) => facts.count >= 90,
109
109
  },
110
110
 
111
111
  events: {
112
112
  increment: (facts) => {
113
- facts.count = (facts.count as number) + (facts.step as number);
113
+ facts.count = facts.count + facts.step;
114
114
  },
115
115
  decrement: (facts) => {
116
- facts.count = Math.max(
117
- 0,
118
- (facts.count as number) - (facts.step as number),
119
- );
116
+ facts.count = Math.max(0, facts.count - facts.step);
120
117
  },
121
118
  setStep: (facts, { value }) => {
122
119
  facts.step = value;
@@ -126,7 +123,7 @@ export const counterModule = createModule("counter", {
126
123
  constraints: {
127
124
  overflow: {
128
125
  priority: 100,
129
- when: (facts) => (facts.count as number) >= 100,
126
+ when: (facts) => facts.count >= 100,
130
127
  require: () => ({ type: "COUNTER_RESET" }),
131
128
  },
132
129
  },
@@ -183,13 +180,13 @@ export const weatherModule = createModule("weather", {
183
180
 
184
181
  derive: {
185
182
  summary: (facts) => {
186
- if ((facts.city as string) === "") {
183
+ if (facts.city === "") {
187
184
  return "";
188
185
  }
189
186
 
190
187
  return `${facts.temperature}\u00B0F, ${facts.condition}`;
191
188
  },
192
- hasFetched: (facts) => (facts.lastFetchedCity as string) !== "",
189
+ hasFetched: (facts) => facts.lastFetchedCity !== "",
193
190
  },
194
191
 
195
192
  events: {
@@ -205,12 +202,12 @@ export const weatherModule = createModule("weather", {
205
202
  needsFetch: {
206
203
  priority: 100,
207
204
  when: (facts) =>
208
- (facts.city as string).length >= 2 &&
205
+ facts.city.length >= 2 &&
209
206
  facts.city !== facts.lastFetchedCity &&
210
- !(facts.isLoading as boolean),
207
+ !facts.isLoading,
211
208
  require: (facts) => ({
212
209
  type: "FETCH_WEATHER",
213
- city: facts.city as string,
210
+ city: facts.city,
214
211
  }),
215
212
  },
216
213
  },
@@ -226,7 +223,7 @@ export const weatherModule = createModule("weather", {
226
223
  const data = await mockFetchWeather(req.city, 800);
227
224
 
228
225
  // Stale check: only apply if city still matches
229
- if ((context.facts.city as string) === req.city) {
226
+ if (context.facts.city === req.city) {
230
227
  context.facts.temperature = data.temperature;
231
228
  context.facts.condition = data.condition;
232
229
  context.facts.humidity = data.humidity;
@@ -269,7 +266,7 @@ export const diceModule = createModule("dice", {
269
266
  },
270
267
 
271
268
  derive: {
272
- total: (facts) => (facts.die1 as number) + (facts.die2 as number),
269
+ total: (facts) => facts.die1 + facts.die2,
273
270
  isDoubles: (facts) => facts.die1 === facts.die2,
274
271
  },
275
272
 
@@ -277,7 +274,7 @@ export const diceModule = createModule("dice", {
277
274
  roll: (facts) => {
278
275
  facts.die1 = Math.floor(Math.random() * 6) + 1;
279
276
  facts.die2 = Math.floor(Math.random() * 6) + 1;
280
- facts.rollCount = (facts.rollCount as number) + 1;
277
+ facts.rollCount = facts.rollCount + 1;
281
278
  },
282
279
  },
283
280
  });
@@ -1,9 +1,9 @@
1
1
  // Example: error-boundaries
2
- // Source: examples/error-boundaries/src/main.ts
3
- // Extracted for AI rules — DOM wiring stripped
2
+ // Source: examples/error-boundaries/src/module.ts
3
+ // Pure module fileno DOM wiring
4
4
 
5
5
  /**
6
- * Resilient API Dashboard — Error Boundaries, Retry, Circuit Breaker, Performance
6
+ * Resilient API Dashboard — Module Definition
7
7
  *
8
8
  * 3 simulated API services with configurable failure rates. Users inject errors
9
9
  * and watch recovery strategies, circuit breaker state transitions, retry-later
@@ -17,17 +17,18 @@ import {
17
17
  createSystem,
18
18
  t,
19
19
  } from "@directive-run/core";
20
- import { devtoolsPlugin, performancePlugin } from "@directive-run/core/plugins";
21
20
  import {
22
21
  type CircuitState,
23
22
  createCircuitBreaker,
23
+ devtoolsPlugin,
24
+ performancePlugin,
24
25
  } from "@directive-run/core/plugins";
25
26
 
26
27
  // ============================================================================
27
28
  // Types
28
29
  // ============================================================================
29
30
 
30
- interface ServiceState {
31
+ export interface ServiceState {
31
32
  name: string;
32
33
  status: "idle" | "loading" | "success" | "error";
33
34
  lastResult: string;
@@ -36,7 +37,7 @@ interface ServiceState {
36
37
  lastError: string;
37
38
  }
38
39
 
39
- interface TimelineEntry {
40
+ export interface TimelineEntry {
40
41
  time: number;
41
42
  event: string;
42
43
  detail: string;
@@ -44,12 +45,12 @@ interface TimelineEntry {
44
45
  }
45
46
 
46
47
  // ============================================================================
47
- // Circuit Breakers (one per service)
48
+ // Timeline
48
49
  // ============================================================================
49
50
 
50
- const timeline: TimelineEntry[] = [];
51
+ export const timeline: TimelineEntry[] = [];
51
52
 
52
- function addTimeline(
53
+ export function addTimeline(
53
54
  event: string,
54
55
  detail: string,
55
56
  type: TimelineEntry["type"],
@@ -60,13 +61,18 @@ function addTimeline(
60
61
  }
61
62
  }
62
63
 
63
- const circuitBreakers = {
64
+ // ============================================================================
65
+ // Circuit Breakers (one per service)
66
+ // ============================================================================
67
+
68
+ export const circuitBreakers = {
64
69
  users: createCircuitBreaker({
65
70
  name: "users-api",
66
71
  failureThreshold: 3,
67
72
  recoveryTimeMs: 5000,
68
73
  halfOpenMaxRequests: 2,
69
74
  onStateChange: (from, to) => {
75
+ addTimeline("circuit", `users: ${from} → ${to}`, "circuit");
70
76
  },
71
77
  }),
72
78
  orders: createCircuitBreaker({
@@ -75,6 +81,7 @@ const circuitBreakers = {
75
81
  recoveryTimeMs: 5000,
76
82
  halfOpenMaxRequests: 2,
77
83
  onStateChange: (from, to) => {
84
+ addTimeline("circuit", `orders: ${from} → ${to}`, "circuit");
78
85
  },
79
86
  }),
80
87
  analytics: createCircuitBreaker({
@@ -83,6 +90,7 @@ const circuitBreakers = {
83
90
  recoveryTimeMs: 5000,
84
91
  halfOpenMaxRequests: 2,
85
92
  onStateChange: (from, to) => {
93
+ addTimeline("circuit", `analytics: ${from} → ${to}`, "circuit");
86
94
  },
87
95
  }),
88
96
  };
@@ -91,7 +99,7 @@ const circuitBreakers = {
91
99
  // Schema
92
100
  // ============================================================================
93
101
 
94
- const schema = {
102
+ export const schema = {
95
103
  facts: {
96
104
  usersService: t.object<ServiceState>(),
97
105
  ordersService: t.object<ServiceState>(),
@@ -290,15 +298,16 @@ const dashboardModule = createModule("dashboard", {
290
298
  });
291
299
 
292
300
  // Success
293
- const current = context.facts[serviceKey] as ServiceState;
301
+ const current = context.facts[serviceKey];
294
302
  context.facts[serviceKey] = {
295
303
  ...current,
296
304
  status: "success",
297
305
  lastResult: `Loaded at ${new Date().toLocaleTimeString()}`,
298
306
  successCount: current.successCount + 1,
299
307
  };
308
+ addTimeline("success", `${service} fetched`, "success");
300
309
  } catch (error) {
301
- const current = context.facts[serviceKey] as ServiceState;
310
+ const current = context.facts[serviceKey];
302
311
  const msg = error instanceof Error ? error.message : String(error);
303
312
  context.facts[serviceKey] = {
304
313
  ...current,
@@ -307,6 +316,7 @@ const dashboardModule = createModule("dashboard", {
307
316
  errorCount: current.errorCount + 1,
308
317
  };
309
318
  context.facts.totalErrors = context.facts.totalErrors + 1;
319
+ addTimeline("error", `${service}: ${msg.slice(0, 60)}`, "error");
310
320
 
311
321
  // Re-throw so the error boundary handles recovery
312
322
  throw error;
@@ -320,8 +330,9 @@ const dashboardModule = createModule("dashboard", {
320
330
  // Performance Plugin
321
331
  // ============================================================================
322
332
 
323
- const perf = performancePlugin({
333
+ export const perf = performancePlugin({
324
334
  onSlowResolver: (id, ms) => {
335
+ addTimeline("perf", `slow resolver: ${id} (${Math.round(ms)}ms)`, "info");
325
336
  },
326
337
  });
327
338
 
@@ -331,12 +342,13 @@ const perf = performancePlugin({
331
342
 
332
343
  let currentStrategy: RecoveryStrategy = "retry-later";
333
344
 
334
- const system = createSystem({
345
+ export const system = createSystem({
335
346
  module: dashboardModule,
336
347
  debug: { runHistory: true },
337
348
  plugins: [perf, devtoolsPlugin({ name: "error-boundaries" })],
338
349
  errorBoundary: {
339
350
  onResolverError: (_error, resolver) => {
351
+ addTimeline(
340
352
  "recovery",
341
353
  `${resolver}: strategy=${currentStrategy}`,
342
354
  "recovery",
@@ -352,79 +364,16 @@ const system = createSystem({
352
364
  backoffMultiplier: 2,
353
365
  },
354
366
  onError: (error) => {
367
+ addTimeline("error", `boundary: ${error.message.slice(0, 60)}`, "error");
355
368
  },
356
369
  },
357
370
  });
358
- system.start();
359
371
 
360
372
  // Track strategy changes to update error boundary (via re-dispatch)
361
373
  system.subscribe(["strategy"], () => {
362
- const newStrategy = system.facts.strategy as RecoveryStrategy;
374
+ const newStrategy = system.facts.strategy;
363
375
  if (newStrategy !== currentStrategy) {
364
376
  currentStrategy = newStrategy;
377
+ addTimeline("recovery", `strategy → ${newStrategy}`, "recovery");
365
378
  }
366
379
  });
367
-
368
- // ============================================================================
369
- // DOM References
370
- // ============================================================================
371
-
372
- // Service cards
373
-
374
- // Sliders
375
- "eb-users-failrate",
376
- "eb-orders-failrate",
377
- "eb-analytics-failrate",
378
-
379
- // Strategy dropdown
380
- "eb-strategy",
381
-
382
- // Timeline
383
-
384
- // ============================================================================
385
- // Render
386
- // ============================================================================
387
-
388
- function escapeHtml(text: string): string {
389
-
390
- return div.innerHTML;
391
- }
392
-
393
- service: ServiceState,
394
- ): void {
395
- if (service.lastError) {
396
- } else {
397
- }
398
- }
399
-
400
-
401
- // ============================================================================
402
- // Subscribe
403
- // ============================================================================
404
-
405
- const allKeys = [
406
- ...Object.keys(schema.facts),
407
- ...Object.keys(schema.derivations),
408
- ];
409
- system.subscribe(allKeys, render);
410
-
411
- // Periodic refresh for circuit breaker state transitions + retry queue
412
- setInterval(() => {
413
- render();
414
- }, 1000);
415
-
416
- // ============================================================================
417
- // Controls
418
- // ============================================================================
419
-
420
- // Fetch buttons
421
-
422
- // Strategy selector
423
-
424
- // Sliders
425
-
426
- // ============================================================================
427
- // Initial Render
428
- // ============================================================================
429
-
430
- render();