@aotui/mobile-ai-native 0.1.0-alpha.0 → 0.1.0-alpha.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 (61) hide show
  1. package/GUIDE.md +79 -109
  2. package/LICENSE +201 -0
  3. package/README.md +106 -40
  4. package/dist/core/action/createActionRuntime.d.ts +7 -7
  5. package/dist/core/action/createActionRuntime.js +71 -18
  6. package/dist/core/action/defineAction.d.ts +4 -5
  7. package/dist/core/action/defineViewTypeTool.d.ts +5 -0
  8. package/dist/core/action/defineViewTypeTool.js +3 -0
  9. package/dist/core/effect/types.d.ts +21 -1
  10. package/dist/core/ref/ref-index.d.ts +1 -1
  11. package/dist/core/snapshot/createSnapshotBundle.d.ts +7 -4
  12. package/dist/core/snapshot/createSnapshotBundle.js +110 -3
  13. package/dist/core/snapshot/createSnapshotRegistry.d.ts +4 -0
  14. package/dist/core/snapshot/createSnapshotRegistry.js +52 -0
  15. package/dist/core/state/createStore.d.ts +1 -1
  16. package/dist/core/trace/createTraceStore.d.ts +2 -0
  17. package/dist/core/trace/createTraceStore.js +38 -0
  18. package/dist/core/trace/types.d.ts +21 -0
  19. package/dist/core/trace/types.js +1 -0
  20. package/dist/core/types.d.ts +61 -10
  21. package/dist/demo/inbox/InboxGUI.js +5 -3
  22. package/dist/demo/inbox/InboxTUI.d.ts +3 -1
  23. package/dist/demo/inbox/InboxTUI.js +75 -9
  24. package/dist/demo/inbox/actions.d.ts +3 -3
  25. package/dist/demo/inbox/actions.js +15 -4
  26. package/dist/demo/inbox/createInboxApp.d.ts +3 -6
  27. package/dist/demo/inbox/createInboxApp.js +11 -11
  28. package/dist/demo/inbox/effects.d.ts +4 -6
  29. package/dist/demo/inbox/effects.js +5 -0
  30. package/dist/demo/inbox/state.d.ts +3 -0
  31. package/dist/demo/inbox/state.js +16 -0
  32. package/dist/index.d.ts +25 -10
  33. package/dist/index.js +20 -10
  34. package/dist/projection/gui/AppProvider.d.ts +8 -12
  35. package/dist/projection/gui/AppProvider.js +77 -2
  36. package/dist/projection/gui/hooks.d.ts +2 -5
  37. package/dist/projection/gui/hooks.js +3 -12
  38. package/dist/projection/react/AppRuntimeProvider.d.ts +10 -0
  39. package/dist/projection/react/AppRuntimeProvider.js +15 -0
  40. package/dist/projection/react/createReactAppRuntime.d.ts +35 -0
  41. package/dist/projection/react/createReactAppRuntime.js +80 -0
  42. package/dist/projection/react/hooks.d.ts +8 -0
  43. package/dist/projection/react/hooks.js +18 -0
  44. package/dist/projection/tui/View.d.ts +8 -0
  45. package/dist/projection/tui/View.js +5 -0
  46. package/dist/projection/tui/createSnapshotAssembler.d.ts +2 -0
  47. package/dist/projection/tui/createSnapshotAssembler.js +14 -0
  48. package/dist/projection/tui/renderSnapshotDocument.d.ts +2 -0
  49. package/dist/projection/tui/renderSnapshotDocument.js +3 -0
  50. package/dist/projection/tui/renderTUI.d.ts +2 -2
  51. package/dist/projection/tui/renderTUI.js +15 -6
  52. package/dist/projection/tui/renderViewFragment.d.ts +10 -0
  53. package/dist/projection/tui/renderViewFragment.js +15 -0
  54. package/dist/ref/RefContext.d.ts +1 -1
  55. package/dist/ref/useArrayRef.js +1 -1
  56. package/dist/ref/useDataRef.js +1 -1
  57. package/dist/tool/createToolBridge.d.ts +4 -9
  58. package/dist/tool/createToolBridge.js +55 -11
  59. package/dist/version.d.ts +1 -0
  60. package/dist/version.js +1 -0
  61. package/package.json +8 -9
@@ -1,13 +1,13 @@
1
- import type { ActionDefinition } from "./defineAction";
2
- import type { ActionResult, Store, ToolDefinition } from "../types";
1
+ import type { ActionDefinition } from "./defineAction.js";
2
+ import type { EffectMap } from "../effect/types.js";
3
+ import type { ActionResult, Store, ToolDefinition, TraceStore } from "../types.js";
3
4
  export declare function createActionRuntime<State, Event>(config: {
4
5
  store: Store<State, Event>;
5
6
  actions: Array<ActionDefinition<State, Event, any>>;
6
- effects?: Record<string, (ctx: {
7
- getState(): State;
8
- emit(event: Event): void;
9
- }, input: any) => Promise<void> | void>;
7
+ traceStore?: TraceStore;
8
+ effects?: EffectMap<State, Event>;
9
+ getRelevantViewTypes?: () => readonly string[];
10
10
  }): {
11
11
  executeAction: (name: string, input: Record<string, unknown>) => Promise<ActionResult>;
12
- listVisibleTools: () => ToolDefinition[];
12
+ listVisibleTools: (relevantViewTypes?: readonly string[]) => ToolDefinition[];
13
13
  };
@@ -1,11 +1,22 @@
1
+ import { createTraceStore } from "../trace/createTraceStore.js";
1
2
  export function createActionRuntime(config) {
2
3
  const actionsByName = new Map(config.actions.map((action) => [action.name, action]));
3
- const trace = {
4
- start(_summary) { },
5
- update(_summary) { },
6
- success(_summary) { },
7
- fail(_summary) { },
8
- };
4
+ const traceStore = config.traceStore ?? createTraceStore();
5
+ function isActionRelevant(action) {
6
+ const scopedAction = action;
7
+ const relevantViewTypes = config.getRelevantViewTypes?.() ?? [];
8
+ if (!scopedAction.viewType) {
9
+ return true;
10
+ }
11
+ return relevantViewTypes.includes(scopedAction.viewType);
12
+ }
13
+ function recordTrace(actionName, status, summary) {
14
+ traceStore.record({
15
+ actionName,
16
+ status,
17
+ summary,
18
+ });
19
+ }
9
20
  async function executeAction(name, input) {
10
21
  const action = actionsByName.get(name);
11
22
  if (!action) {
@@ -17,7 +28,8 @@ export function createActionRuntime(config) {
17
28
  },
18
29
  };
19
30
  }
20
- if (!action.visibility(config.store.getState())) {
31
+ if (!isActionRelevant(action) ||
32
+ !action.visibility(config.store.getState())) {
21
33
  return {
22
34
  success: false,
23
35
  error: {
@@ -36,31 +48,72 @@ export function createActionRuntime(config) {
36
48
  },
37
49
  };
38
50
  }
51
+ let latestSummary = `Started action ${name}`;
52
+ recordTrace(name, "started", latestSummary);
53
+ const trace = {
54
+ update(summary) {
55
+ latestSummary = summary;
56
+ recordTrace(name, "updated", summary);
57
+ },
58
+ };
39
59
  const ctx = {
40
60
  getState: () => config.store.getState(),
41
61
  emit: (event) => config.store.emit(event),
42
- runEffect: async (name, input) => {
62
+ runEffect: (name, input) => {
43
63
  const effect = config.effects?.[name];
44
64
  if (!effect) {
45
- return;
65
+ return Promise.resolve(undefined);
46
66
  }
47
- await effect({
67
+ return Promise.resolve(effect({
48
68
  getState: () => config.store.getState(),
49
69
  emit: (event) => config.store.emit(event),
50
- }, input);
70
+ trace,
71
+ }, input));
51
72
  },
52
73
  trace,
53
74
  };
54
- return await action.handler(ctx, parsed.data);
75
+ try {
76
+ const result = await action.handler(ctx, parsed.data);
77
+ if (result.success) {
78
+ latestSummary = result.message ?? latestSummary;
79
+ recordTrace(name, "succeeded", latestSummary);
80
+ }
81
+ else {
82
+ latestSummary =
83
+ result.error?.message ?? result.message ?? `Action ${name} failed`;
84
+ recordTrace(name, "failed", latestSummary);
85
+ }
86
+ return result;
87
+ }
88
+ catch (error) {
89
+ latestSummary =
90
+ error instanceof Error ? error.message : `Action ${name} failed`;
91
+ recordTrace(name, "failed", latestSummary);
92
+ throw error;
93
+ }
55
94
  }
56
- function listVisibleTools() {
95
+ function listVisibleTools(relevantViewTypes = config.getRelevantViewTypes?.() ?? []) {
57
96
  const state = config.store.getState();
97
+ const relevantViewTypeSet = new Set(relevantViewTypes);
58
98
  return config.actions
59
- .filter((action) => action.visibility(state))
60
- .map((action) => ({
61
- name: action.name,
62
- description: action.description,
63
- }));
99
+ .filter((action) => {
100
+ const scopedAction = action;
101
+ const isViewTypeRelevant = !scopedAction.viewType ||
102
+ relevantViewTypeSet.has(scopedAction.viewType);
103
+ return isViewTypeRelevant && action.visibility(state);
104
+ })
105
+ .map((action) => {
106
+ const scopedAction = action;
107
+ return {
108
+ name: action.name,
109
+ description: action.description,
110
+ inputSchema: action.schema,
111
+ meta: action.meta ?? {},
112
+ ...(scopedAction.viewType
113
+ ? { viewType: scopedAction.viewType }
114
+ : {}),
115
+ };
116
+ });
64
117
  }
65
118
  return {
66
119
  executeAction,
@@ -1,20 +1,19 @@
1
1
  import type { ZodType } from "zod";
2
- import type { ActionResult } from "../types";
2
+ import type { EffectResult } from "../effect/types.js";
3
+ import type { ActionResult, ToolMetadata } from "../types.js";
3
4
  export type ActionContext<State, Event> = {
4
5
  getState(): State;
5
6
  emit(event: Event): void;
6
- runEffect(name: string, input: unknown): Promise<void>;
7
+ runEffect(name: string, input: unknown): Promise<EffectResult>;
7
8
  trace: {
8
- start(summary: string): void;
9
9
  update(summary: string): void;
10
- success(summary?: string): void;
11
- fail(summary: string): void;
12
10
  };
13
11
  };
14
12
  export type ActionDefinition<State, Event, Input> = {
15
13
  name: string;
16
14
  description: string;
17
15
  schema: ZodType<Input>;
16
+ meta?: ToolMetadata;
18
17
  visibility(state: State): boolean;
19
18
  handler(ctx: ActionContext<State, Event>, input: Input): Promise<ActionResult> | ActionResult;
20
19
  };
@@ -0,0 +1,5 @@
1
+ import type { ActionDefinition } from "./defineAction.js";
2
+ export type ViewTypeToolActionDefinition<State, Event, Input> = ActionDefinition<State, Event, Input> & {
3
+ readonly viewType: string;
4
+ };
5
+ export declare function defineViewTypeTool<State, Event, Input>(tool: ViewTypeToolActionDefinition<State, Event, Input>): ViewTypeToolActionDefinition<State, Event, Input>;
@@ -0,0 +1,3 @@
1
+ export function defineViewTypeTool(tool) {
2
+ return tool;
3
+ }
@@ -1 +1,21 @@
1
- export type EffectMap = Record<string, (input: unknown) => Promise<void> | void>;
1
+ export type EffectTrace = {
2
+ update(summary: string): void;
3
+ };
4
+ export type EffectFailure = {
5
+ success: false;
6
+ error: {
7
+ code: string;
8
+ message: string;
9
+ };
10
+ };
11
+ export type EffectSuccess = {
12
+ success: true;
13
+ };
14
+ export type EffectResult = EffectSuccess | EffectFailure | void;
15
+ export type EffectContext<State, Event> = {
16
+ getState(): State;
17
+ emit(event: Event): void;
18
+ trace: EffectTrace;
19
+ };
20
+ export type EffectHandler<State, Event, Input> = (ctx: EffectContext<State, Event>, input: Input) => Promise<EffectResult> | EffectResult;
21
+ export type EffectMap<State, Event> = Record<string, EffectHandler<State, Event, any>>;
@@ -1,4 +1,4 @@
1
- import type { RefIndexEntry } from "../types";
1
+ import type { RefIndexEntry } from "../types.js";
2
2
  export declare function createRefCollector(): {
3
3
  register(refId: string, entry: RefIndexEntry): void;
4
4
  snapshot(): Record<string, RefIndexEntry>;
@@ -1,6 +1,9 @@
1
- import type { SnapshotBundle, ToolDefinition, RefIndexEntry } from "../types";
1
+ import type { SnapshotBundle, ToolDefinition, RefIndexEntry, ViewFragment } from "../types.js";
2
+ export declare function hardenSnapshotBundle(snapshot: SnapshotBundle): SnapshotBundle;
2
3
  export declare function createSnapshotBundle(input: {
3
- tui: string;
4
- refIndex: Record<string, RefIndexEntry>;
5
- visibleTools: ToolDefinition[];
4
+ markup?: string;
5
+ views?: readonly ViewFragment[];
6
+ tui?: string;
7
+ refIndex: Readonly<Record<string, RefIndexEntry>>;
8
+ visibleTools: readonly ToolDefinition[];
6
9
  }): SnapshotBundle;
@@ -1,12 +1,119 @@
1
1
  let snapshotCounter = 0;
2
+ function isPlainObject(value) {
3
+ if (typeof value !== "object" || value === null) {
4
+ return false;
5
+ }
6
+ const prototype = Object.getPrototypeOf(value);
7
+ return prototype === Object.prototype || prototype === null;
8
+ }
9
+ function cloneReadonlyValue(value, seen = new WeakMap()) {
10
+ if (value === null) {
11
+ return null;
12
+ }
13
+ const valueType = typeof value;
14
+ if (valueType === "string" ||
15
+ valueType === "boolean") {
16
+ return value;
17
+ }
18
+ if (valueType === "number") {
19
+ if (!Number.isFinite(value)) {
20
+ throw new Error("Snapshot values must be plain JSON-like data; received a non-finite number.");
21
+ }
22
+ return value;
23
+ }
24
+ if (valueType === "undefined" ||
25
+ valueType === "function" ||
26
+ valueType === "symbol" ||
27
+ valueType === "bigint") {
28
+ throw new Error("Snapshot values must be plain JSON-like data; received an unsupported primitive value.");
29
+ }
30
+ if (Array.isArray(value)) {
31
+ if (seen.has(value)) {
32
+ return seen.get(value);
33
+ }
34
+ const clone = [];
35
+ seen.set(value, clone);
36
+ for (const item of value) {
37
+ clone.push(cloneReadonlyValue(item, seen));
38
+ }
39
+ return Object.freeze(clone);
40
+ }
41
+ if (isPlainObject(value)) {
42
+ if (seen.has(value)) {
43
+ return seen.get(value);
44
+ }
45
+ const clone = Object.create(null);
46
+ seen.set(value, clone);
47
+ for (const [key, nestedValue] of Object.entries(value)) {
48
+ clone[key] = cloneReadonlyValue(nestedValue, seen);
49
+ }
50
+ return Object.freeze(clone);
51
+ }
52
+ if (typeof value === "object" && value !== null) {
53
+ throw new Error("Snapshot values must be plain JSON-like data; received a non-plain object.");
54
+ }
55
+ return value;
56
+ }
57
+ function hardenRefIndex(refIndex) {
58
+ const clonedEntries = Object.create(null);
59
+ for (const [refId, entry] of Object.entries(refIndex)) {
60
+ clonedEntries[refId] = Object.freeze({
61
+ type: entry.type,
62
+ value: cloneReadonlyValue(entry.value),
63
+ });
64
+ }
65
+ return Object.freeze(clonedEntries);
66
+ }
67
+ function hardenVisibleTools(visibleTools) {
68
+ const clonedTools = visibleTools.map((tool) => Object.freeze({
69
+ name: tool.name,
70
+ description: tool.description,
71
+ inputSchema: tool.inputSchema,
72
+ meta: cloneReadonlyValue(tool.meta),
73
+ ...(tool.viewType ? { viewType: tool.viewType } : {}),
74
+ }));
75
+ return Object.freeze(clonedTools);
76
+ }
77
+ function validateSnapshotCoherence(snapshot) {
78
+ if (snapshot.views.length === 0) {
79
+ return;
80
+ }
81
+ const derivedMarkup = snapshot.views.map((view) => view.markup).join("");
82
+ if (snapshot.markup !== derivedMarkup) {
83
+ throw new Error("Snapshot markup must match the provided view fragments from the same render tick.");
84
+ }
85
+ }
86
+ export function hardenSnapshotBundle(snapshot) {
87
+ validateSnapshotCoherence(snapshot);
88
+ return Object.freeze({
89
+ snapshotId: snapshot.snapshotId,
90
+ generatedAt: snapshot.generatedAt,
91
+ markup: snapshot.markup,
92
+ views: Object.freeze(snapshot.views.map((view) => Object.freeze({
93
+ id: view.id,
94
+ type: view.type,
95
+ name: view.name,
96
+ markup: view.markup,
97
+ }))),
98
+ tui: snapshot.tui,
99
+ refIndex: hardenRefIndex(snapshot.refIndex),
100
+ visibleTools: hardenVisibleTools(snapshot.visibleTools),
101
+ });
102
+ }
2
103
  export function createSnapshotBundle(input) {
3
104
  const generatedAt = Date.now();
4
105
  snapshotCounter += 1;
5
- return {
106
+ const views = input.views ?? [];
107
+ const derivedMarkup = views.length > 0 ? views.map((view) => view.markup).join("") : undefined;
108
+ const markup = input.markup ?? derivedMarkup ?? input.tui ?? "";
109
+ const tui = input.tui ?? markup;
110
+ return hardenSnapshotBundle({
6
111
  snapshotId: `snap_${generatedAt}_${snapshotCounter}`,
7
112
  generatedAt,
8
- tui: input.tui,
113
+ markup,
114
+ views,
115
+ tui,
9
116
  refIndex: input.refIndex,
10
117
  visibleTools: input.visibleTools,
11
- };
118
+ });
12
119
  }
@@ -0,0 +1,4 @@
1
+ import type { SnapshotRegistry } from "../types.js";
2
+ export declare function createSnapshotRegistry(config?: {
3
+ maxEntries?: number;
4
+ }): SnapshotRegistry;
@@ -0,0 +1,52 @@
1
+ import { hardenSnapshotBundle } from "./createSnapshotBundle.js";
2
+ function evictOldSnapshots(snapshots, maxEntries) {
3
+ while (snapshots.size > maxEntries) {
4
+ const oldestSnapshotId = snapshots.keys().next().value;
5
+ if (!oldestSnapshotId) {
6
+ return;
7
+ }
8
+ snapshots.delete(oldestSnapshotId);
9
+ }
10
+ }
11
+ export function createSnapshotRegistry(config) {
12
+ const maxEntries = Math.max(1, config?.maxEntries ?? 2);
13
+ const snapshots = new Map();
14
+ const seenSnapshotIds = new Set();
15
+ function createEntry(snapshot, status) {
16
+ return Object.freeze({
17
+ snapshot,
18
+ status,
19
+ });
20
+ }
21
+ return {
22
+ create(snapshot) {
23
+ const hardenedSnapshot = hardenSnapshotBundle(snapshot);
24
+ if (seenSnapshotIds.has(hardenedSnapshot.snapshotId)) {
25
+ throw new Error(`Snapshot ${hardenedSnapshot.snapshotId} is already registered`);
26
+ }
27
+ seenSnapshotIds.add(hardenedSnapshot.snapshotId);
28
+ snapshots.set(hardenedSnapshot.snapshotId, createEntry(hardenedSnapshot, "active"));
29
+ evictOldSnapshots(snapshots, maxEntries);
30
+ return hardenedSnapshot;
31
+ },
32
+ lookup(snapshotId) {
33
+ return snapshots.get(snapshotId);
34
+ },
35
+ markStale(snapshotId) {
36
+ const entry = snapshots.get(snapshotId);
37
+ if (!entry) {
38
+ return;
39
+ }
40
+ snapshots.set(snapshotId, createEntry(entry.snapshot, "stale"));
41
+ },
42
+ markAllStale() {
43
+ for (const snapshotId of snapshots.keys()) {
44
+ const entry = snapshots.get(snapshotId);
45
+ if (!entry || entry.status === "stale") {
46
+ continue;
47
+ }
48
+ snapshots.set(snapshotId, createEntry(entry.snapshot, "stale"));
49
+ }
50
+ },
51
+ };
52
+ }
@@ -1,4 +1,4 @@
1
- import type { StateReducer, Store } from "../types";
1
+ import type { StateReducer, Store } from "../types.js";
2
2
  export declare function createStore<State, Event>(config: {
3
3
  initialState: State;
4
4
  reduce: StateReducer<State, Event>;
@@ -0,0 +1,2 @@
1
+ import type { TraceStore } from "./types.js";
2
+ export declare function createTraceStore(): TraceStore;
@@ -0,0 +1,38 @@
1
+ export function createTraceStore() {
2
+ let nextTraceId = 1;
3
+ let state = {
4
+ entries: [],
5
+ recent: null,
6
+ };
7
+ const listeners = new Set();
8
+ function notify() {
9
+ listeners.forEach((listener) => listener());
10
+ }
11
+ function record(entry) {
12
+ const record = {
13
+ id: `trace_${nextTraceId++}`,
14
+ actionName: entry.actionName,
15
+ status: entry.status,
16
+ summary: entry.summary,
17
+ recordedAt: Date.now(),
18
+ };
19
+ state = {
20
+ entries: [...state.entries, record],
21
+ recent: record,
22
+ };
23
+ notify();
24
+ return record;
25
+ }
26
+ return {
27
+ getState() {
28
+ return state;
29
+ },
30
+ subscribe(listener) {
31
+ listeners.add(listener);
32
+ return () => {
33
+ listeners.delete(listener);
34
+ };
35
+ },
36
+ record,
37
+ };
38
+ }
@@ -0,0 +1,21 @@
1
+ export type TraceStatus = "started" | "updated" | "succeeded" | "failed";
2
+ export type TraceRecord = {
3
+ id: string;
4
+ actionName: string;
5
+ status: TraceStatus;
6
+ summary: string;
7
+ recordedAt: number;
8
+ };
9
+ export type TraceState = {
10
+ entries: TraceRecord[];
11
+ recent: TraceRecord | null;
12
+ };
13
+ export type TraceStore = {
14
+ getState(): TraceState;
15
+ subscribe(listener: () => void): () => void;
16
+ record(entry: {
17
+ actionName: string;
18
+ status: TraceStatus;
19
+ summary: string;
20
+ }): TraceRecord;
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,26 +1,77 @@
1
+ import type { ComponentChild } from "preact";
2
+ import type { ZodTypeAny } from "zod";
3
+ export type { TraceRecord, TraceState, TraceStatus, TraceStore, } from "./trace/types.js";
1
4
  export type StateReducer<State, Event> = (state: State, event: Event) => State;
5
+ export type StoreListener = () => void;
6
+ export type StoreUnsubscribe = () => void;
2
7
  export type Store<State, Event> = {
3
8
  getState(): State;
4
9
  emit(event: Event): void;
5
- subscribe(listener: () => void): () => void;
10
+ subscribe(listener: StoreListener): StoreUnsubscribe;
6
11
  };
7
12
  export type RefIndexEntry = {
8
- type: string;
9
- value: unknown;
13
+ readonly type: string;
14
+ readonly value: unknown;
10
15
  };
16
+ export type ToolMetadata = Readonly<Record<string, unknown>>;
11
17
  export type ToolDefinition = {
12
- name: string;
13
- description: string;
18
+ readonly name: string;
19
+ readonly description: string;
20
+ readonly inputSchema: ZodTypeAny;
21
+ readonly meta: ToolMetadata;
22
+ readonly viewType?: string;
23
+ };
24
+ export type ViewTypeToolDefinition = ToolDefinition & {
25
+ readonly viewType: string;
26
+ };
27
+ export type ViewFragment = {
28
+ readonly id: string;
29
+ readonly type: string;
30
+ readonly name: string;
31
+ readonly markup: string;
32
+ };
33
+ export type StaticViewCatalogEntry = {
34
+ readonly type: string;
35
+ readonly description: string;
36
+ readonly enterFrom?: readonly string[];
37
+ readonly actions: readonly string[];
38
+ };
39
+ export type MountedViewDescriptor<State = unknown> = {
40
+ readonly id: string;
41
+ readonly type: string;
42
+ readonly name: string;
43
+ readonly render: (state: State) => ComponentChild;
44
+ };
45
+ export type SnapshotAssemblerInput<State = unknown> = {
46
+ readonly rootView: ViewFragment;
47
+ readonly mountedViews: readonly ViewFragment[];
48
+ readonly refIndex: Readonly<Record<string, RefIndexEntry>>;
49
+ readonly visibleTools: readonly ToolDefinition[];
50
+ readonly tui?: string;
14
51
  };
15
52
  export type SnapshotBundle = {
16
- snapshotId: string;
17
- generatedAt: number;
18
- tui: string;
19
- refIndex: Record<string, RefIndexEntry>;
20
- visibleTools: ToolDefinition[];
53
+ readonly snapshotId: string;
54
+ readonly generatedAt: number;
55
+ readonly markup: string;
56
+ readonly views: readonly ViewFragment[];
57
+ readonly tui: string;
58
+ readonly refIndex: Readonly<Record<string, RefIndexEntry>>;
59
+ readonly visibleTools: readonly ToolDefinition[];
60
+ };
61
+ export type SnapshotStatus = "active" | "stale";
62
+ export type SnapshotRegistryEntry = {
63
+ readonly snapshot: SnapshotBundle;
64
+ readonly status: SnapshotStatus;
65
+ };
66
+ export type SnapshotRegistry = {
67
+ create(snapshot: SnapshotBundle): SnapshotBundle;
68
+ lookup(snapshotId: string): SnapshotRegistryEntry | undefined;
69
+ markStale(snapshotId: string): void;
70
+ markAllStale?(): void;
21
71
  };
22
72
  export type ActionResult<T = unknown> = {
23
73
  success: boolean;
74
+ mutated?: boolean;
24
75
  message?: string;
25
76
  data?: T;
26
77
  error?: {
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  /** @jsxImportSource preact */
3
- import { useAppState } from "../../projection/gui/hooks";
3
+ import { useRuntimeState } from "../../projection/react/hooks.js";
4
4
  export function InboxGUI() {
5
- const { state } = useAppState();
6
- return (_jsxs("gui-screen", { children: [_jsx("gui-trace", { children: state.shell.recentTrace ?? "" }), _jsx("gui-opened", { children: state.inbox.openedMessageId ?? "" }), state.inbox.items.map((item) => (_jsx("gui-item", { children: item.subject }, item.id)))] }));
5
+ const recentTrace = useRuntimeState((state) => state.shell.recentTrace ?? "");
6
+ const openedMessageId = useRuntimeState((state) => state.inbox.openedMessageId ?? "");
7
+ const items = useRuntimeState((state) => state.inbox.items);
8
+ return (_jsxs("gui-screen", { children: [_jsx("gui-trace", { children: recentTrace }), _jsx("gui-opened", { children: openedMessageId }), items.map((item) => (_jsx("gui-item", { children: item.subject }, item.id)))] }));
7
9
  }
@@ -1 +1,3 @@
1
- export declare function InboxTUI(): import("preact").JSX.Element;
1
+ import type { ToolDefinition } from "../../core/types.js";
2
+ import type { InboxState } from "./state.js";
3
+ export declare function createInboxSnapshotBundle(state: InboxState, visibleTools: readonly ToolDefinition[]): import("../..").SnapshotBundle;