@deepwatch/dsh-live 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 oxbshw
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,87 @@
1
+ # @deepwatch/dsh-live
2
+
3
+ Live mode — cursors, gaps, clocks, reconnect and a bounded buffer
4
+
5
+ Part of **DeepWatch** — the agent workspace built on the official
6
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
7
+ and powered by [Watch Skill](https://github.com/oxbshw/watch-skill) for perception, evidence, memory and
8
+ independent verification.
9
+
10
+ > **Host plugin — runs beside the agent in the DSH process.**
11
+ > Composed by the bundle. Needed for live and browser sessions.
12
+
13
+ ## Exports
14
+
15
+ - `@deepwatch/dsh-live`
16
+ - `@deepwatch/dsh-live/capture`
17
+ - `@deepwatch/dsh-live/client`
18
+ - `@deepwatch/dsh-live/components`
19
+ - `@deepwatch/dsh-live/live-mode`
20
+ - `@deepwatch/dsh-live/sources-catalogue`
21
+ - `@deepwatch/dsh-live/synthetic-source`
22
+
23
+ ## Peers
24
+
25
+ Provided by the host rather than installed here:
26
+
27
+ - `@deepseek-ai/cordis@4.0.2`
28
+ - `react@^18.2.0`
29
+
30
+ ## Install
31
+
32
+ > **Not on npm yet.** Nothing exists under the `@deepwatch` scope. This
33
+ > package is published for the first time by the `deepwatch-v0.1.0`
34
+ > release; until then the command below resolves nothing, and
35
+ > [the workspace README](https://github.com/oxbshw/watch-skill/tree/main/workspace#readme) has the path
36
+ > that works from a checkout.
37
+
38
+ ```sh
39
+ npm install @deepwatch/dsh-live
40
+ ```
41
+
42
+ Rarely on its own. [`@deepwatch/dsh-bundle`](https://github.com/oxbshw/watch-skill/tree/main/workspace/packages/watch/bundle#readme)
43
+ composes this package with the rest of DeepWatch and is what a profile
44
+ normally depends on; installing this one directly is for embedding a
45
+ single piece in a composition you control.
46
+
47
+ ## Requirements
48
+
49
+ - Node `^22.19.0 || >=24.0.0`
50
+ - The peers above, supplied by the host composition
51
+
52
+ Core built with the `[loop]` extra, and a browser installed by `playwright install chromium`, for anything that drives a page.
53
+
54
+ ## Stability
55
+
56
+ `0.1.0` — a stable release.
57
+
58
+ Stable means tested, documented and supported — not 1.0. This is a
59
+ pre-1.0 line, and semantic versioning gives `0.x` no compatibility
60
+ guarantee across minor versions: **a `0.MINOR` bump may change or remove
61
+ surface, and a patch will not.** Depend on it with a tilde range
62
+ (`~0.1.0`) if you want that difference enforced by your lockfile
63
+ rather than by a changelog. The usual major-version promise starts at 1.0.
64
+
65
+ ## Side effects
66
+
67
+ Importing a module from this package evaluates no side effects, so a
68
+ bundler may drop what a build does not use. Mounting it in a host is a
69
+ separate matter: what it then reads or writes is governed by the
70
+ workspace boundary and the host's permissions, not by this flag.
71
+
72
+ ## Where this fits
73
+
74
+ One clock across streams, with cursors, declared gaps, a bounded buffer and a reconnect policy — so "what was on screen when that was said" has an answer rather than a guess.
75
+
76
+ The twenty packages and how they compose:
77
+ [the package map](https://github.com/oxbshw/watch-skill/blob/main/workspace/docs/packages.md).
78
+ Running DeepWatch, and the gates a change has to pass:
79
+ [the workspace README](https://github.com/oxbshw/watch-skill/tree/main/workspace#readme).
80
+
81
+ ## Attribution
82
+
83
+ Built on DeepSeek Harness · Powered by Watch Skill
84
+
85
+ DeepWatch and Watch Skill are independent projects and are not affiliated
86
+ with or endorsed by DeepSeek. MIT licensed; third-party notices are in
87
+ [THIRD_PARTY_NOTICES.md](https://github.com/oxbshw/watch-skill/blob/main/workspace/THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The live capture lifecycle.
3
+ *
4
+ * A session is a small state machine, and writing it as one is the point: every
5
+ * way a capture can end — stopped, cancelled, timed out, denied, failed, the
6
+ * source disappearing underneath it — is a named state with a named transition
7
+ * rather than an early return somewhere. The states that get skipped in ad-hoc
8
+ * implementations are exactly the ones that leak: a cancel during startup, a
9
+ * source vanishing mid-stream, a stop arriving twice.
10
+ *
11
+ * Three rules hold the design together.
12
+ *
13
+ * **Permission is never implied.** A session moves `idle → requested →
14
+ * granted` only through an explicit call that a user action caused. Nothing in
15
+ * construction, discovery or rendering asks for anything; a prompt on page load
16
+ * teaches people to allow without reading, and after that the prompt means
17
+ * nothing.
18
+ *
19
+ * **Observing is not operating.** The browser appears twice in the source
20
+ * catalogue because watching a page and acting on one carry different
21
+ * consequences. A single capability would grant the second while a person
22
+ * believed they were enabling the first.
23
+ *
24
+ * **Stopping always releases.** `stop`, `cancel`, `fail` and `timeout` all run
25
+ * the same teardown, exactly once, whatever order they arrive in. A capture
26
+ * that leaks a handle on the unhappy path leaks it on the path that actually
27
+ * happens.
28
+ *
29
+ * @module @deepwatch/dsh-live/capture
30
+ */
31
+ /** Where a session is. Every terminal state is reachable and named. */
32
+ export type CaptureState = 'idle' | 'requesting_permission' | 'starting' | 'active' | 'paused' | 'stopping' | 'stopped' | 'cancelled' | 'denied' | 'unavailable' | 'timed_out' | 'failed';
33
+ /** Whether the person has agreed, and whether they were ever asked. */
34
+ export type PermissionState = 'not_requested' | 'requested' | 'granted' | 'denied';
35
+ /** One thing the source produced, with the clock that makes it citable. */
36
+ export interface Observation {
37
+ readonly observationId: string;
38
+ /** ISO-8601. An observation without a time cannot be cited. */
39
+ readonly at: string;
40
+ readonly kind: 'frame' | 'audio' | 'text' | 'event';
41
+ /** What was observed. Verbatim; never a summary. */
42
+ readonly text: string;
43
+ /** Milliseconds from the session's start, for a relative timeline. */
44
+ readonly offsetMs: number;
45
+ }
46
+ /** The receipt a finished session leaves behind. */
47
+ export interface CaptureReceipt {
48
+ readonly sessionId: string;
49
+ readonly sourceId: string;
50
+ readonly runId: string | null;
51
+ readonly startedAt: string | null;
52
+ readonly endedAt: string | null;
53
+ readonly finalState: CaptureState;
54
+ readonly observationCount: number;
55
+ /** Why it ended, in words. Empty for an ordinary stop. */
56
+ readonly reason: string;
57
+ /**
58
+ * Evidence minted from this session, if any.
59
+ *
60
+ * Always empty here. A capture produces observations; only Watch Core turns
61
+ * an observation into evidence, and this module cannot and must not.
62
+ */
63
+ readonly evidenceIds: readonly string[];
64
+ }
65
+ /** A source's own view of itself, before anything has been started. */
66
+ export interface SourceAvailability {
67
+ readonly available: boolean;
68
+ /** Why not, when not. Shown to a person, so it has to be actionable. */
69
+ readonly reason: string;
70
+ }
71
+ /**
72
+ * What a real source has to provide.
73
+ *
74
+ * Deliberately tiny. An adapter that can be described in four methods can be
75
+ * tested deterministically with a fake, which is what lets the lifecycle above
76
+ * be exercised on a machine that has no camera.
77
+ */
78
+ export interface CaptureAdapter {
79
+ readonly sourceId: string;
80
+ /** Can this run here, now? Must not prompt and must not open anything. */
81
+ probe(): Promise<SourceAvailability> | SourceAvailability;
82
+ /** Ask the person. Called only from an explicit user action. */
83
+ requestPermission(): Promise<boolean> | boolean;
84
+ /** Begin producing. `emit` may be called until `stop` resolves. */
85
+ start(emit: (observation: Observation) => void): Promise<void> | void;
86
+ /** Release everything. Must be safe to call more than once. */
87
+ stop(): Promise<void> | void;
88
+ }
89
+ export interface CaptureOptions {
90
+ readonly sessionId: string;
91
+ readonly runId?: string | null;
92
+ /** Give up if the source has not started within this long. */
93
+ readonly startTimeoutMs?: number;
94
+ /** Injected so tests are not slow and not flaky. */
95
+ readonly now?: () => Date;
96
+ }
97
+ /** One live capture, from nothing to a receipt. */
98
+ export declare class CaptureSession {
99
+ #private;
100
+ readonly sessionId: string;
101
+ readonly sourceId: string;
102
+ readonly runId: string | null;
103
+ constructor(adapter: CaptureAdapter, options: CaptureOptions);
104
+ get state(): CaptureState;
105
+ get permission(): PermissionState;
106
+ get observations(): readonly Observation[];
107
+ get startedAt(): string | null;
108
+ get reason(): string;
109
+ /** True once the session can no longer change state. */
110
+ get finished(): boolean;
111
+ subscribe(listener: (session: CaptureSession) => void): () => void;
112
+ /**
113
+ * Ask whether the source can run here.
114
+ *
115
+ * Explicitly not a permission request. Probing must be safe to do while
116
+ * rendering a list of sources, so an adapter that prompted here would make
117
+ * merely opening the tab ask for the camera.
118
+ */
119
+ probe(): Promise<SourceAvailability>;
120
+ /**
121
+ * Ask the person for permission.
122
+ *
123
+ * Called only from an explicit user action — that is the contract, and the
124
+ * reason `start` refuses to do it implicitly.
125
+ */
126
+ requestPermission(): Promise<boolean>;
127
+ /**
128
+ * Begin capturing.
129
+ *
130
+ * Refuses without permission rather than requesting it. A start that silently
131
+ * prompts is a start that can be triggered by something other than a person,
132
+ * and the whole point of the boundary is that it cannot.
133
+ */
134
+ start(): Promise<boolean>;
135
+ /** Stop producing without ending the session. */
136
+ pause(): boolean;
137
+ resume(): boolean;
138
+ /** End normally. Safe to call twice; the second call is a no-op. */
139
+ stop(): Promise<CaptureReceipt>;
140
+ /**
141
+ * End before it began, or during startup.
142
+ *
143
+ * Distinct from `stop` because the states differ for a person: a cancelled
144
+ * capture produced nothing on purpose, a stopped one produced what it
145
+ * produced.
146
+ */
147
+ cancel(reason?: string): Promise<CaptureReceipt>;
148
+ /** The source went away underneath us. */
149
+ sourceLost(reason: string): Promise<CaptureReceipt>;
150
+ /** What happened, in a form that outlives the session object. */
151
+ receipt(): CaptureReceipt;
152
+ }
153
+ /** Build an observation with a clock, so it can be cited. */
154
+ export declare function observationAt(base: Date, now: Date, kind: Observation['kind'], text: string, index: number): Observation;
155
+ //# sourceMappingURL=capture.d.ts.map
package/lib/capture.js ADDED
@@ -0,0 +1,301 @@
1
+ /**
2
+ * The live capture lifecycle.
3
+ *
4
+ * A session is a small state machine, and writing it as one is the point: every
5
+ * way a capture can end — stopped, cancelled, timed out, denied, failed, the
6
+ * source disappearing underneath it — is a named state with a named transition
7
+ * rather than an early return somewhere. The states that get skipped in ad-hoc
8
+ * implementations are exactly the ones that leak: a cancel during startup, a
9
+ * source vanishing mid-stream, a stop arriving twice.
10
+ *
11
+ * Three rules hold the design together.
12
+ *
13
+ * **Permission is never implied.** A session moves `idle → requested →
14
+ * granted` only through an explicit call that a user action caused. Nothing in
15
+ * construction, discovery or rendering asks for anything; a prompt on page load
16
+ * teaches people to allow without reading, and after that the prompt means
17
+ * nothing.
18
+ *
19
+ * **Observing is not operating.** The browser appears twice in the source
20
+ * catalogue because watching a page and acting on one carry different
21
+ * consequences. A single capability would grant the second while a person
22
+ * believed they were enabling the first.
23
+ *
24
+ * **Stopping always releases.** `stop`, `cancel`, `fail` and `timeout` all run
25
+ * the same teardown, exactly once, whatever order they arrive in. A capture
26
+ * that leaks a handle on the unhappy path leaks it on the path that actually
27
+ * happens.
28
+ *
29
+ * @module @deepwatch/dsh-live/capture
30
+ */
31
+ const DEFAULT_START_TIMEOUT = 10_000;
32
+ /** One live capture, from nothing to a receipt. */
33
+ export class CaptureSession {
34
+ sessionId;
35
+ sourceId;
36
+ runId;
37
+ #adapter;
38
+ #state = 'idle';
39
+ #permission = 'not_requested';
40
+ #observations = [];
41
+ #startedAt = null;
42
+ #endedAt = null;
43
+ #reason = '';
44
+ #now;
45
+ #startTimeout;
46
+ #torndown = false;
47
+ #timer = null;
48
+ #listeners = new Set();
49
+ constructor(adapter, options) {
50
+ this.#adapter = adapter;
51
+ this.sessionId = options.sessionId;
52
+ this.sourceId = adapter.sourceId;
53
+ this.runId = options.runId ?? null;
54
+ this.#now = options.now ?? (() => new Date());
55
+ this.#startTimeout = options.startTimeoutMs ?? DEFAULT_START_TIMEOUT;
56
+ }
57
+ get state() { return this.#state; }
58
+ get permission() { return this.#permission; }
59
+ get observations() { return this.#observations; }
60
+ get startedAt() { return this.#startedAt?.toISOString() ?? null; }
61
+ get reason() { return this.#reason; }
62
+ /** True once the session can no longer change state. */
63
+ get finished() {
64
+ return ['stopped', 'cancelled', 'denied', 'unavailable', 'timed_out', 'failed'].includes(this.#state);
65
+ }
66
+ subscribe(listener) {
67
+ this.#listeners.add(listener);
68
+ return () => { this.#listeners.delete(listener); };
69
+ }
70
+ #announce() {
71
+ for (const listener of this.#listeners)
72
+ listener(this);
73
+ }
74
+ #settle(state, reason) {
75
+ if (this.finished)
76
+ return;
77
+ this.#state = state;
78
+ this.#reason = reason;
79
+ this.#endedAt = this.#now();
80
+ this.#announce();
81
+ }
82
+ /**
83
+ * Ask whether the source can run here.
84
+ *
85
+ * Explicitly not a permission request. Probing must be safe to do while
86
+ * rendering a list of sources, so an adapter that prompted here would make
87
+ * merely opening the tab ask for the camera.
88
+ */
89
+ async probe() {
90
+ const availability = await this.#adapter.probe();
91
+ if (!availability.available && this.#state === 'idle') {
92
+ this.#state = 'unavailable';
93
+ this.#reason = availability.reason;
94
+ this.#announce();
95
+ }
96
+ return availability;
97
+ }
98
+ /**
99
+ * Ask the person for permission.
100
+ *
101
+ * Called only from an explicit user action — that is the contract, and the
102
+ * reason `start` refuses to do it implicitly.
103
+ */
104
+ async requestPermission() {
105
+ if (this.finished)
106
+ return this.#permission === 'granted';
107
+ this.#state = 'requesting_permission';
108
+ this.#permission = 'requested';
109
+ this.#announce();
110
+ let granted = false;
111
+ try {
112
+ granted = await this.#adapter.requestPermission();
113
+ }
114
+ catch (error) {
115
+ this.#permission = 'denied';
116
+ this.#settle('failed', `The permission request failed: ${String(error)}`);
117
+ return false;
118
+ }
119
+ if (!granted) {
120
+ this.#permission = 'denied';
121
+ this.#settle('denied', 'Permission was refused. Nothing was captured.');
122
+ return false;
123
+ }
124
+ this.#permission = 'granted';
125
+ this.#state = 'idle';
126
+ this.#announce();
127
+ return true;
128
+ }
129
+ /**
130
+ * Begin capturing.
131
+ *
132
+ * Refuses without permission rather than requesting it. A start that silently
133
+ * prompts is a start that can be triggered by something other than a person,
134
+ * and the whole point of the boundary is that it cannot.
135
+ */
136
+ async start() {
137
+ if (this.finished)
138
+ return false;
139
+ if (this.#permission !== 'granted') {
140
+ this.#settle('denied', 'Start needs permission, and permission is only ever asked for by an explicit action.');
141
+ return false;
142
+ }
143
+ const availability = await this.#adapter.probe();
144
+ if (!availability.available) {
145
+ this.#settle('unavailable', availability.reason);
146
+ return false;
147
+ }
148
+ this.#state = 'starting';
149
+ this.#startedAt = this.#now();
150
+ this.#announce();
151
+ // A source that never starts must not hold the session open forever.
152
+ const timedOut = new Promise(resolve => {
153
+ this.#timer = setTimeout(() => { resolve('timeout'); }, this.#startTimeout);
154
+ });
155
+ try {
156
+ const outcome = await Promise.race([
157
+ Promise.resolve(this.#adapter.start(observation => { this.#record(observation); })).then(() => 'started'),
158
+ timedOut,
159
+ ]);
160
+ this.#clearTimer();
161
+ // A cancel that arrived while we were starting wins. Without this check
162
+ // the session would come back to life after the user stopped it.
163
+ //
164
+ // The adapter is stopped directly rather than through `#teardown`, and
165
+ // that distinction is the whole fix for a real leak: the cancel already
166
+ // ran teardown, so the once-guard is set — but it ran *before* the
167
+ // adapter allocated anything, so it stopped nothing. Whatever `start`
168
+ // just created would have been left running forever. The adapter
169
+ // contract requires `stop` to be safe more than once precisely so this
170
+ // case can be handled without tracking who allocated what.
171
+ if (this.finished) {
172
+ try {
173
+ await this.#adapter.stop();
174
+ }
175
+ catch {
176
+ // Nothing left to do: the session has already ended.
177
+ }
178
+ return false;
179
+ }
180
+ if (outcome === 'timeout') {
181
+ await this.#teardown();
182
+ this.#settle('timed_out', `The source did not start within ${String(this.#startTimeout)}ms.`);
183
+ return false;
184
+ }
185
+ }
186
+ catch (error) {
187
+ this.#clearTimer();
188
+ await this.#teardown();
189
+ this.#settle('failed', `The source failed to start: ${String(error)}`);
190
+ return false;
191
+ }
192
+ this.#state = 'active';
193
+ this.#announce();
194
+ return true;
195
+ }
196
+ /** Record an observation. Ignored unless the session is actually running. */
197
+ #record(observation) {
198
+ if (this.#state !== 'active')
199
+ return;
200
+ this.#observations.push(observation);
201
+ this.#announce();
202
+ }
203
+ /** Stop producing without ending the session. */
204
+ pause() {
205
+ if (this.#state !== 'active')
206
+ return false;
207
+ this.#state = 'paused';
208
+ this.#announce();
209
+ return true;
210
+ }
211
+ resume() {
212
+ if (this.#state !== 'paused')
213
+ return false;
214
+ this.#state = 'active';
215
+ this.#announce();
216
+ return true;
217
+ }
218
+ /** End normally. Safe to call twice; the second call is a no-op. */
219
+ async stop() {
220
+ if (!this.finished) {
221
+ this.#state = 'stopping';
222
+ this.#announce();
223
+ await this.#teardown();
224
+ this.#settle('stopped', '');
225
+ }
226
+ return this.receipt();
227
+ }
228
+ /**
229
+ * End before it began, or during startup.
230
+ *
231
+ * Distinct from `stop` because the states differ for a person: a cancelled
232
+ * capture produced nothing on purpose, a stopped one produced what it
233
+ * produced.
234
+ */
235
+ async cancel(reason = 'Cancelled.') {
236
+ if (!this.finished) {
237
+ await this.#teardown();
238
+ this.#settle('cancelled', reason);
239
+ }
240
+ return this.receipt();
241
+ }
242
+ /** The source went away underneath us. */
243
+ async sourceLost(reason) {
244
+ if (!this.finished) {
245
+ await this.#teardown();
246
+ this.#settle('unavailable', reason);
247
+ }
248
+ return this.receipt();
249
+ }
250
+ #clearTimer() {
251
+ if (this.#timer !== null) {
252
+ clearTimeout(this.#timer);
253
+ this.#timer = null;
254
+ }
255
+ }
256
+ /**
257
+ * Release everything, exactly once.
258
+ *
259
+ * Every ending routes through here, and the guard is what makes that safe: a
260
+ * stop racing a timeout would otherwise call the adapter's `stop` twice, and
261
+ * an adapter is entitled to assume it is torn down once.
262
+ */
263
+ async #teardown() {
264
+ this.#clearTimer();
265
+ if (this.#torndown)
266
+ return;
267
+ this.#torndown = true;
268
+ try {
269
+ await this.#adapter.stop();
270
+ }
271
+ catch {
272
+ // A teardown that throws must not prevent the session from ending; the
273
+ // alternative is a session stuck in `stopping` forever.
274
+ }
275
+ }
276
+ /** What happened, in a form that outlives the session object. */
277
+ receipt() {
278
+ return {
279
+ sessionId: this.sessionId,
280
+ sourceId: this.sourceId,
281
+ runId: this.runId,
282
+ startedAt: this.#startedAt?.toISOString() ?? null,
283
+ endedAt: this.#endedAt?.toISOString() ?? null,
284
+ finalState: this.#state,
285
+ observationCount: this.#observations.length,
286
+ reason: this.#reason,
287
+ evidenceIds: [],
288
+ };
289
+ }
290
+ }
291
+ /** Build an observation with a clock, so it can be cited. */
292
+ export function observationAt(base, now, kind, text, index) {
293
+ return {
294
+ observationId: `obs-${String(index)}`,
295
+ at: now.toISOString(),
296
+ kind,
297
+ text,
298
+ offsetMs: Math.max(0, now.getTime() - base.getTime()),
299
+ };
300
+ }
301
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The Live surface.
3
+ *
4
+ * The header is deliberately busy. A live view's job is to be trustworthy
5
+ * about time, and that means the clocks, the latency, the connection state and
6
+ * the continuity line are all present at once rather than behind a disclosure.
7
+ * The moment one of them is hidden, the surface starts implying a continuity
8
+ * it has not checked.
9
+ *
10
+ * @module @deepwatch/dsh-live/components
11
+ */
12
+ import type { ReactNode } from 'react';
13
+ import { type LiveEvent, type LiveSessionState } from '../session.js';
14
+ /** Props for {@link LiveHeader}. */
15
+ export interface LiveHeaderProps {
16
+ readonly state: LiveSessionState;
17
+ }
18
+ /**
19
+ * Session, media and wall clocks, side by side.
20
+ *
21
+ * Three readings rather than one, because they answer three questions and
22
+ * disagree constantly during a live observation. A single "time" field would
23
+ * be right about one of them and quietly wrong about the other two.
24
+ */
25
+ export declare function LiveHeader({ state }: LiveHeaderProps): ReactNode;
26
+ /** Props for {@link LiveEventRow}. */
27
+ export interface LiveEventRowProps {
28
+ readonly event: LiveEvent;
29
+ readonly onSelect: (event: LiveEvent) => void;
30
+ }
31
+ /**
32
+ * One observed event.
33
+ *
34
+ * A gap draws as a gap — dashed, glyphed and labelled — and is never
35
+ * collapsed into the events around it.
36
+ */
37
+ export declare function LiveEventRow({ event, onSelect }: LiveEventRowProps): ReactNode;
38
+ /** Props for {@link LiveSurface}. */
39
+ export interface LiveSurfaceProps {
40
+ readonly state: LiveSessionState;
41
+ readonly onStart: () => void;
42
+ readonly onStop: (finalize: boolean) => void;
43
+ readonly onAsk: (question: string) => void;
44
+ readonly onSelect: (event: LiveEvent) => void;
45
+ readonly onPin: () => void;
46
+ }
47
+ /**
48
+ * The Live mode body.
49
+ *
50
+ * `Resnapshot needed` is rendered as a banner rather than a toast. A transient
51
+ * notification for "your view of this is not continuous" is a notification
52
+ * that will be missed exactly when it matters.
53
+ */
54
+ export declare function LiveSurface(props: LiveSurfaceProps): ReactNode;
55
+ //# sourceMappingURL=components.d.ts.map