@blario/react-native 0.1.5 → 0.1.7
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/README.md +8 -3
- package/dist/BlarProvider.d.ts +6 -0
- package/dist/actions/select.d.ts +1 -1
- package/dist/actions/text-input.d.ts +1 -0
- package/dist/agent/compose.d.ts +1 -0
- package/dist/agent-mode/keyboard-guard.d.ts +13 -0
- package/dist/snapshot/capture.d.ts +1 -0
- package/dist/snapshot/visibility.d.ts +1 -1
- package/dist/snapshot/wrappers.d.ts +0 -2
- package/dist/telemetry/contain.d.ts +5 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/BlarProvider.tsx +68 -28
- package/src/actions/executor.ts +6 -3
- package/src/actions/select.ts +16 -2
- package/src/actions/text-input.ts +30 -1
- package/src/agent/compose.ts +2 -0
- package/src/agent/controller.ts +3 -1
- package/src/agent-mode/keyboard-guard.ts +35 -0
- package/src/agent-mode/timers.ts +3 -1
- package/src/agent-mode/ui/AgentDock.tsx +8 -7
- package/src/agent-mode/ui/AgentLayer.tsx +2 -1
- package/src/agent-mode/ui/AgentLayerHost.tsx +2 -1
- package/src/agent-mode/ui/AgentPanel.tsx +4 -3
- package/src/agent-mode/ui/AgentTap.tsx +3 -2
- package/src/agent-mode/ui/SpotlightLayer.tsx +4 -3
- package/src/agent-mode/ui/StatusCard.tsx +3 -2
- package/src/agent-mode/ui/TouchShield.tsx +2 -1
- package/src/backend/foreground.ts +11 -6
- package/src/backend/health.ts +10 -6
- package/src/backend/presence.ts +12 -8
- package/src/backend/session-socket.ts +25 -18
- package/src/blar.ts +8 -7
- package/src/chat/ChatSheet.tsx +32 -11
- package/src/chat/ChatView.tsx +12 -10
- package/src/chat/Launcher.tsx +2 -1
- package/src/chat/widget-bundle.generated.ts +2 -2
- package/src/nudge/NudgeBubble.tsx +6 -5
- package/src/snapshot/capture.ts +8 -6
- package/src/snapshot/render.ts +1 -1
- package/src/snapshot/visibility.ts +11 -10
- package/src/snapshot/wrappers.ts +0 -13
- package/src/telemetry/contain.ts +28 -0
- package/src/telemetry/sdk-reporter.ts +3 -0
- package/src/transport/relay-transport.ts +13 -9
- package/src/version.ts +1 -1
|
@@ -5,6 +5,7 @@ import type { LauncherPlacement } from '../chat/Launcher.tsx';
|
|
|
5
5
|
import type { UiStrings } from '../ui/strings.ts';
|
|
6
6
|
import { useInsets } from '../ui/useInsets.ts';
|
|
7
7
|
import { initialsOf, type NudgeView } from './nudge.ts';
|
|
8
|
+
import { contain } from '../telemetry/contain.ts';
|
|
8
9
|
|
|
9
10
|
export interface NudgeBubbleProps {
|
|
10
11
|
nudge: NudgeView;
|
|
@@ -32,27 +33,27 @@ export function NudgeBubble({ nudge, strings, color, placement, launcherVisible,
|
|
|
32
33
|
{nudge.sender === null ? null : <Text style={styles.name}>{nudge.sender.name}</Text>}
|
|
33
34
|
{nudge.sender?.subtitle === null || nudge.sender === null ? null : <Text style={styles.subtitle}>{nudge.sender.subtitle}</Text>}
|
|
34
35
|
</View>
|
|
35
|
-
<Pressable accessibilityLabel={strings.nudgeDismiss} hitSlop={10} onPress={onDecline}>
|
|
36
|
+
<Pressable accessibilityLabel={strings.nudgeDismiss} hitSlop={10} onPress={contain('NudgeBubble.decline', onDecline)}>
|
|
36
37
|
<Text style={styles.close}>×</Text>
|
|
37
38
|
</Pressable>
|
|
38
39
|
</View>
|
|
39
40
|
<Text style={styles.message}>{nudge.bubbleMessage}</Text>
|
|
40
41
|
{nudge.progress === null ? null : <ProgressBar total={nudge.progress.total} completed={nudge.progress.completed} color={color} />}
|
|
41
42
|
{nudge.continuation ? (
|
|
42
|
-
<Pressable accessibilityRole="button" style={[styles.primary, { backgroundColor: color }]} onPress={onAccept}>
|
|
43
|
+
<Pressable accessibilityRole="button" style={[styles.primary, { backgroundColor: color }]} onPress={contain('NudgeBubble.accept', onAccept)}>
|
|
43
44
|
<Text style={styles.primaryText}>{nudge.ctaLabel ?? strings.nudgeAccept}</Text>
|
|
44
45
|
</Pressable>
|
|
45
46
|
) : (
|
|
46
47
|
<View>
|
|
47
48
|
<Text style={styles.prompt}>{strings.nudgePrompt}</Text>
|
|
48
49
|
<View style={styles.actions}>
|
|
49
|
-
<Pressable accessibilityRole="button" style={[styles.primary, styles.action, { backgroundColor: color }]} onPress={onAccept}>
|
|
50
|
+
<Pressable accessibilityRole="button" style={[styles.primary, styles.action, { backgroundColor: color }]} onPress={contain('NudgeBubble.accept', onAccept)}>
|
|
50
51
|
<Text style={styles.primaryText}>{strings.nudgeAccept}</Text>
|
|
51
52
|
</Pressable>
|
|
52
|
-
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={onDecline}>
|
|
53
|
+
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={contain('NudgeBubble.decline', onDecline)}>
|
|
53
54
|
<Text style={styles.secondaryText}>{strings.nudgeDecline}</Text>
|
|
54
55
|
</Pressable>
|
|
55
|
-
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={onLater}>
|
|
56
|
+
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={contain('NudgeBubble.later', onLater)}>
|
|
56
57
|
<Text style={styles.secondaryText}>{strings.nudgeLater}</Text>
|
|
57
58
|
</Pressable>
|
|
58
59
|
</View>
|
package/src/snapshot/capture.ts
CHANGED
|
@@ -12,8 +12,8 @@ import { describeScrollDirections } from './scroll.ts';
|
|
|
12
12
|
import { groupIntoLines, limitCharacters, type PositionedText } from './text-layout.ts';
|
|
13
13
|
import { orderForReading } from './ordering.ts';
|
|
14
14
|
import { buildView } from './view.ts';
|
|
15
|
-
import { classifyVisibility, keepInTextTree, measureScrollGeometries, type ScrollGeometries } from './visibility.ts';
|
|
16
|
-
import { dropWrapperDuplicates,
|
|
15
|
+
import { classifyVisibility, keepInTextTree, markOffscreenText, measureScrollGeometries, type ScrollGeometries } from './visibility.ts';
|
|
16
|
+
import { dropWrapperDuplicates, type MeasuredCandidate } from './wrappers.ts';
|
|
17
17
|
|
|
18
18
|
export interface Snapshot {
|
|
19
19
|
screen: string;
|
|
@@ -27,6 +27,7 @@ export interface SnapshotDependencies {
|
|
|
27
27
|
getAnchorFiber: () => Fiber | null;
|
|
28
28
|
registry: ElementRegistry;
|
|
29
29
|
getViewport: () => Size;
|
|
30
|
+
getWindow: () => Size;
|
|
30
31
|
getExtraRoutes: () => string[];
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -77,7 +78,7 @@ export class SnapshotCapturer {
|
|
|
77
78
|
return null;
|
|
78
79
|
}
|
|
79
80
|
const visibility = classifyVisibility(rect, viewport, block.scrollContainers, geometries);
|
|
80
|
-
return keepInTextTree(visibility, rect, viewport, options.viewportOnly) ? { rect, text: block.text, heading: block.heading } : null;
|
|
81
|
+
return keepInTextTree(visibility, rect, viewport, options.viewportOnly) ? { rect, text: markOffscreenText(visibility, block.text), heading: block.heading } : null;
|
|
81
82
|
}),
|
|
82
83
|
);
|
|
83
84
|
const lines = limitCharacters(groupIntoLines(measured.filter((entry): entry is PositionedText => entry !== null)), options.maxChars);
|
|
@@ -91,8 +92,9 @@ export class SnapshotCapturer {
|
|
|
91
92
|
return root === null ? [] : collect(root).focusedScreens;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// The whole window, not the part above the keyboard: what the keyboard covers is still pressable.
|
|
94
96
|
private async measureVisible(kept: Candidate[], geometries: ScrollGeometries): Promise<MeasuredCandidate[]> {
|
|
95
|
-
const viewport = this.dependencies.
|
|
97
|
+
const viewport = this.dependencies.getWindow();
|
|
96
98
|
const measured = await Promise.all(
|
|
97
99
|
kept.map(async (candidate): Promise<MeasuredCandidate | null> => {
|
|
98
100
|
const rect = await measureHost(candidate.host);
|
|
@@ -104,10 +106,10 @@ export class SnapshotCapturer {
|
|
|
104
106
|
return null;
|
|
105
107
|
}
|
|
106
108
|
const offscreen = visibility.kind === 'offscreen' ? visibility : null;
|
|
107
|
-
return { candidate, rect, offscreen: offscreen?.direction ?? null
|
|
109
|
+
return { candidate, rect, offscreen: offscreen?.direction ?? null };
|
|
108
110
|
}),
|
|
109
111
|
);
|
|
110
|
-
return
|
|
112
|
+
return measured.filter((entry): entry is MeasuredCandidate => entry !== null);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
private assemble(
|
package/src/snapshot/render.ts
CHANGED
|
@@ -17,7 +17,7 @@ function flag(name: string, enabled: boolean): Attribute {
|
|
|
17
17
|
function renderAttributes(attributes: Attribute[]): string {
|
|
18
18
|
return attributes
|
|
19
19
|
.filter((attribute): attribute is readonly [string, string] => attribute[1] !== null)
|
|
20
|
-
.map(([name, value]) => (value === '' ? name : `${name}=${clip(value, ATTRIBUTE_LIMIT)}`))
|
|
20
|
+
.map(([name, value]) => (value === '' ? name : `${name}=${name === 'list' ? value : clip(value, ATTRIBUTE_LIMIT)}`))
|
|
21
21
|
.filter((rendered) => rendered.length > 0)
|
|
22
22
|
.join(' ');
|
|
23
23
|
}
|
|
@@ -16,7 +16,7 @@ export type Offscreen = 'above' | 'below' | 'left' | 'right';
|
|
|
16
16
|
|
|
17
17
|
export type Visibility =
|
|
18
18
|
| { kind: 'visible' }
|
|
19
|
-
| { kind: 'offscreen'; direction: Offscreen
|
|
19
|
+
| { kind: 'offscreen'; direction: Offscreen }
|
|
20
20
|
| { kind: 'hidden' };
|
|
21
21
|
|
|
22
22
|
const VISIBLE: Visibility = { kind: 'visible' };
|
|
@@ -25,13 +25,9 @@ const HIDDEN: Visibility = { kind: 'hidden' };
|
|
|
25
25
|
function offscreenAgainst(rect: Rect, geometry: ScrollGeometry): Visibility {
|
|
26
26
|
const { viewport, horizontal } = geometry;
|
|
27
27
|
if (horizontal) {
|
|
28
|
-
return rect.x >= viewport.x + viewport.width
|
|
29
|
-
? { kind: 'offscreen', direction: 'right', distance: rect.x - (viewport.x + viewport.width) }
|
|
30
|
-
: { kind: 'offscreen', direction: 'left', distance: viewport.x - (rect.x + rect.width) };
|
|
28
|
+
return { kind: 'offscreen', direction: rect.x >= viewport.x + viewport.width ? 'right' : 'left' };
|
|
31
29
|
}
|
|
32
|
-
return rect.y >= viewport.y + viewport.height
|
|
33
|
-
? { kind: 'offscreen', direction: 'below', distance: rect.y - (viewport.y + viewport.height) }
|
|
34
|
-
: { kind: 'offscreen', direction: 'above', distance: viewport.y - (rect.y + rect.height) };
|
|
30
|
+
return { kind: 'offscreen', direction: rect.y >= viewport.y + viewport.height ? 'below' : 'above' };
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export function classifyVisibility(rect: Rect, window: Size, containers: Fiber[], geometries: ScrollGeometries): Visibility {
|
|
@@ -52,10 +48,15 @@ function isBeyondWindowEdge(rect: Rect, window: Size): boolean {
|
|
|
52
48
|
return rect.x >= window.width || rect.x + rect.width <= 0;
|
|
53
49
|
}
|
|
54
50
|
|
|
51
|
+
const OFFSCREEN_MARK = '[off-screen]';
|
|
52
|
+
|
|
53
|
+
// Text off to the side can be another page of a pager or another day of a calendar, so it can't read as what's on screen.
|
|
54
|
+
export function markOffscreenText(visibility: Visibility, text: string): string {
|
|
55
|
+
const sideways = visibility.kind === 'offscreen' && (visibility.direction === 'left' || visibility.direction === 'right');
|
|
56
|
+
return sideways ? `${OFFSCREEN_MARK} ${text}` : text;
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
export function keepInTextTree(visibility: Visibility, rect: Rect, window: Size, viewportOnly: boolean): boolean {
|
|
56
|
-
if (visibility.kind === 'offscreen' && (visibility.direction === 'left' || visibility.direction === 'right')) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
60
|
if (visibility.kind === 'hidden' && isBeyondWindowEdge(rect, window)) {
|
|
60
61
|
return false;
|
|
61
62
|
}
|
package/src/snapshot/wrappers.ts
CHANGED
|
@@ -7,7 +7,6 @@ export interface MeasuredCandidate {
|
|
|
7
7
|
candidate: Candidate;
|
|
8
8
|
rect: Rect;
|
|
9
9
|
offscreen: Offscreen | null;
|
|
10
|
-
distance: number;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
const RECT_TOLERANCE = 1;
|
|
@@ -56,15 +55,3 @@ export function dropWrapperDuplicates(measured: MeasuredCandidate[]): MeasuredCa
|
|
|
56
55
|
}
|
|
57
56
|
return measured.filter((entry) => !wrappers.has(entry));
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
const OFFSCREEN_LIMIT = 30;
|
|
61
|
-
|
|
62
|
-
export function limitOffscreen(measured: MeasuredCandidate[]): MeasuredCandidate[] {
|
|
63
|
-
// Every option of a wheel stays listed: one dropped here could never be selected.
|
|
64
|
-
const hidden = measured.filter((entry) => entry.offscreen !== null && entry.candidate.kind !== 'scroll' && entry.candidate.wheelOption !== true);
|
|
65
|
-
if (hidden.length <= OFFSCREEN_LIMIT) {
|
|
66
|
-
return measured;
|
|
67
|
-
}
|
|
68
|
-
const dropped = new Set(hidden.sort((left, right) => left.distance - right.distance).slice(OFFSCREEN_LIMIT));
|
|
69
|
-
return measured.filter((entry) => !dropped.has(entry));
|
|
70
|
-
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// A callback's error has nothing above it: React's boundaries never see it and, in a release build, React Native's
|
|
2
|
+
// global handler ends the host app. So every way into the SDK that nothing else catches goes through here.
|
|
3
|
+
type Report = (context: string, error: unknown) => void;
|
|
4
|
+
|
|
5
|
+
let report: Report = () => undefined;
|
|
6
|
+
|
|
7
|
+
export function setContainmentReporter(reporter: Report): void {
|
|
8
|
+
report = reporter;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function attempt<T>(context: string, run: () => T): T | undefined {
|
|
12
|
+
try {
|
|
13
|
+
return run();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
try {
|
|
16
|
+
report(context, error);
|
|
17
|
+
} catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function contain<Args extends unknown[]>(context: string, callback: (...args: Args) => unknown): (...args: Args) => void {
|
|
25
|
+
return (...args) => {
|
|
26
|
+
attempt(context, () => callback(...args));
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -2,6 +2,7 @@ import { readAppInfo } from '../backend/app-info.ts';
|
|
|
2
2
|
import { SDK_VERSION } from '../version.ts';
|
|
3
3
|
import { SENTRY_DSN, SENTRY_ENVIRONMENT } from './build-info.generated.ts';
|
|
4
4
|
import { Reporter, type ReportIdentity } from './reporter.ts';
|
|
5
|
+
import { setContainmentReporter } from './contain.ts';
|
|
5
6
|
|
|
6
7
|
const HEX_DIGITS = 16;
|
|
7
8
|
|
|
@@ -24,6 +25,8 @@ export function reportSdkError(context: string, error?: unknown): void {
|
|
|
24
25
|
reporter.report(context, error);
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
setContainmentReporter(reportSdkError);
|
|
29
|
+
|
|
27
30
|
export function recordSdkBreadcrumb(category: string, message: string): void {
|
|
28
31
|
reporter.addBreadcrumb(category, message);
|
|
29
32
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OutboundFrame } from '../agent/frames.ts';
|
|
2
2
|
import { normalizeRelayUrl } from './connection-options.ts';
|
|
3
3
|
import type { AgentTransport } from './transport.ts';
|
|
4
|
+
import { contain } from '../telemetry/contain.ts';
|
|
4
5
|
|
|
5
6
|
const RECONNECT_BASE_MS = 1000;
|
|
6
7
|
const RECONNECT_MAX_MS = 5000;
|
|
@@ -56,15 +57,15 @@ export class RelayTransport implements AgentTransport {
|
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
this.socket = socket;
|
|
59
|
-
socket.onopen = () => {
|
|
60
|
+
socket.onopen = contain('Relay.open', () => {
|
|
60
61
|
this.attempts = 0;
|
|
61
|
-
};
|
|
62
|
-
socket.onmessage = (event) => {
|
|
62
|
+
});
|
|
63
|
+
socket.onmessage = contain('Relay.message', (event: { data?: unknown }) => {
|
|
63
64
|
if (typeof event.data === 'string') {
|
|
64
65
|
this.listener?.(event.data);
|
|
65
66
|
}
|
|
66
|
-
};
|
|
67
|
-
socket.onclose = () => this.scheduleReconnect();
|
|
67
|
+
});
|
|
68
|
+
socket.onclose = contain('Relay.close', () => this.scheduleReconnect());
|
|
68
69
|
socket.onerror = () => undefined;
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -74,9 +75,12 @@ export class RelayTransport implements AgentTransport {
|
|
|
74
75
|
}
|
|
75
76
|
const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.attempts, RECONNECT_MAX_MS);
|
|
76
77
|
this.attempts += 1;
|
|
77
|
-
this.reconnectTimer = setTimeout(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
this.reconnectTimer = setTimeout(
|
|
79
|
+
contain('Relay.reconnect', () => {
|
|
80
|
+
this.reconnectTimer = null;
|
|
81
|
+
this.connect();
|
|
82
|
+
}),
|
|
83
|
+
delay,
|
|
84
|
+
);
|
|
81
85
|
}
|
|
82
86
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '0.1.
|
|
1
|
+
export const SDK_VERSION = '0.1.7';
|