@schlessera/brain-ui-react 0.36.0 → 0.37.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/dist/components/activity/activity-page.d.ts.map +1 -1
- package/dist/components/activity/activity-page.js +13 -9
- package/dist/components/activity/activity-page.js.map +1 -1
- package/dist/components/activity/approval-card.d.ts +19 -1
- package/dist/components/activity/approval-card.d.ts.map +1 -1
- package/dist/components/activity/approval-card.js +30 -3
- package/dist/components/activity/approval-card.js.map +1 -1
- package/dist/components/activity/span-bits.d.ts +11 -0
- package/dist/components/activity/span-bits.d.ts.map +1 -1
- package/dist/components/activity/span-bits.js +41 -0
- package/dist/components/activity/span-bits.js.map +1 -1
- package/dist/components/chat/chat-page.d.ts.map +1 -1
- package/dist/components/chat/chat-page.js +3 -2
- package/dist/components/chat/chat-page.js.map +1 -1
- package/dist/components/chat/tool-call-timeline.d.ts.map +1 -1
- package/dist/components/chat/tool-call-timeline.js +9 -3
- package/dist/components/chat/tool-call-timeline.js.map +1 -1
- package/dist/components/chat/tool-views.d.ts +6 -0
- package/dist/components/chat/tool-views.d.ts.map +1 -1
- package/dist/components/chat/tool-views.js +8 -0
- package/dist/components/chat/tool-views.js.map +1 -1
- package/dist/components/files/file-panel.js +1 -1
- package/dist/components/layout/side-rail.d.ts +5 -1
- package/dist/components/layout/side-rail.d.ts.map +1 -1
- package/dist/components/layout/side-rail.js +13 -6
- package/dist/components/layout/side-rail.js.map +1 -1
- package/dist/components/layout/slide-panel.d.ts +14 -1
- package/dist/components/layout/slide-panel.d.ts.map +1 -1
- package/dist/components/layout/slide-panel.js +5 -5
- package/dist/components/layout/slide-panel.js.map +1 -1
- package/dist/components/quick-actions/add-form.d.ts.map +1 -1
- package/dist/components/quick-actions/add-form.js +4 -2
- package/dist/components/quick-actions/add-form.js.map +1 -1
- package/dist/components/quick-actions/search-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/search-modal.js +4 -2
- package/dist/components/quick-actions/search-modal.js.map +1 -1
- package/dist/components/quick-actions/streaming-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/streaming-modal.js +15 -2
- package/dist/components/quick-actions/streaming-modal.js.map +1 -1
- package/dist/components/quick-actions/whatsup-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/whatsup-modal.js +16 -1
- package/dist/components/quick-actions/whatsup-modal.js.map +1 -1
- package/dist/components/settings/settings-panel.d.ts.map +1 -1
- package/dist/components/settings/settings-panel.js +8 -8
- package/dist/components/settings/settings-panel.js.map +1 -1
- package/dist/hooks/use-fine-pointer.d.ts +12 -0
- package/dist/hooks/use-fine-pointer.d.ts.map +1 -0
- package/dist/hooks/use-fine-pointer.js +15 -0
- package/dist/hooks/use-fine-pointer.js.map +1 -0
- package/dist/hooks/use-media-query.d.ts +5 -3
- package/dist/hooks/use-media-query.d.ts.map +1 -1
- package/dist/hooks/use-media-query.js +11 -7
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/websocket-handlers/chat.d.ts.map +1 -1
- package/dist/hooks/websocket-handlers/chat.js +1 -1
- package/dist/hooks/websocket-handlers/chat.js.map +1 -1
- package/dist/stores/chat-state.d.ts +22 -1
- package/dist/stores/chat-state.d.ts.map +1 -1
- package/dist/stores/chat-state.js +12 -1
- package/dist/stores/chat-state.js.map +1 -1
- package/package.json +3 -3
- package/src/components/activity/activity-page.tsx +16 -9
- package/src/components/activity/approval-card.tsx +42 -5
- package/src/components/activity/span-bits.tsx +52 -0
- package/src/components/chat/chat-page.tsx +3 -2
- package/src/components/chat/tool-call-timeline.tsx +18 -6
- package/src/components/chat/tool-views.tsx +9 -0
- package/src/components/files/file-panel.tsx +1 -1
- package/src/components/layout/side-rail.tsx +13 -6
- package/src/components/layout/slide-panel.tsx +20 -4
- package/src/components/quick-actions/add-form.tsx +9 -1
- package/src/components/quick-actions/search-modal.tsx +10 -2
- package/src/components/quick-actions/streaming-modal.tsx +16 -2
- package/src/components/quick-actions/whatsup-modal.tsx +17 -1
- package/src/components/settings/settings-panel.tsx +11 -8
- package/src/hooks/use-fine-pointer.ts +15 -0
- package/src/hooks/use-media-query.ts +10 -7
- package/src/hooks/websocket-handlers/chat.ts +2 -1
- package/src/stores/chat-state.ts +29 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Suspense, lazy } from "react";
|
|
2
2
|
import { KeyRound, Laptop, Puzzle, SlidersHorizontal } from "lucide-react";
|
|
3
3
|
import { useUIStore, type SettingsTab } from "../../stores/ui-store.js";
|
|
4
|
-
import { SlidePanel } from "../layout/slide-panel.js";
|
|
4
|
+
import { SlidePanel, type SlidePanelClosedBy } from "../layout/slide-panel.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { usePrincipalStore } from "../../stores/principal-store.js";
|
|
7
7
|
import { useBrainUiRoot } from "../../root-context.js";
|
|
@@ -73,10 +73,10 @@ export function SettingsPanel({
|
|
|
73
73
|
const oneTimeCredential = usePrincipalStore((s) => s.oneTimeCredential);
|
|
74
74
|
const credentialProtected = mintPending || oneTimeCredential !== null;
|
|
75
75
|
const pane = useMediaQuery(PANE_QUERY);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
// While a mint is in flight or its one-time value is unacknowledged, the
|
|
77
|
+
// panel does not light-dismiss: a stray click or Escape must not hide the
|
|
78
|
+
// surface the credential is about to land on. Its close control stays live.
|
|
79
|
+
const closedBy = credentialProtected ? "none" : "any";
|
|
80
80
|
|
|
81
81
|
function select(id: SettingsTab) {
|
|
82
82
|
if (!credentialProtected) setTab(id);
|
|
@@ -88,8 +88,9 @@ export function SettingsPanel({
|
|
|
88
88
|
open={open}
|
|
89
89
|
tab={tab}
|
|
90
90
|
credentialProtected={credentialProtected}
|
|
91
|
+
closedBy={closedBy}
|
|
91
92
|
onSelect={select}
|
|
92
|
-
onClose={
|
|
93
|
+
onClose={onClose}
|
|
93
94
|
/>
|
|
94
95
|
);
|
|
95
96
|
}
|
|
@@ -99,7 +100,7 @@ export function SettingsPanel({
|
|
|
99
100
|
const stripTab: StripTab = tab === "appearance" ? "models" : tab;
|
|
100
101
|
|
|
101
102
|
return (
|
|
102
|
-
<SlidePanel open={open} onClose={
|
|
103
|
+
<SlidePanel open={open} onClose={onClose} title="Settings" wide closedBy={closedBy}>
|
|
103
104
|
<div className="flex h-full flex-col">
|
|
104
105
|
<div className="flex shrink-0 gap-1 border-b border-border px-2 pt-2">
|
|
105
106
|
{TABS.map(({ id, label, icon: TabIcon }) => (
|
|
@@ -158,18 +159,20 @@ function SettingsPane({
|
|
|
158
159
|
open,
|
|
159
160
|
tab,
|
|
160
161
|
credentialProtected,
|
|
162
|
+
closedBy,
|
|
161
163
|
onSelect,
|
|
162
164
|
onClose,
|
|
163
165
|
}: {
|
|
164
166
|
open: boolean;
|
|
165
167
|
tab: SettingsTab;
|
|
166
168
|
credentialProtected: boolean;
|
|
169
|
+
closedBy: SlidePanelClosedBy;
|
|
167
170
|
onSelect: (tab: SettingsTab) => void;
|
|
168
171
|
onClose: () => void;
|
|
169
172
|
}) {
|
|
170
173
|
const appName = useBrainUiRoot().config.appName;
|
|
171
174
|
return (
|
|
172
|
-
<SlidePanel open={open} onClose={onClose} title="Settings" mode="pane">
|
|
175
|
+
<SlidePanel open={open} onClose={onClose} title="Settings" mode="pane" closedBy={closedBy}>
|
|
173
176
|
{/* The kit header is `width: 100%`; it needs a shrinking flex child
|
|
174
177
|
around it, or it fills the row and pushes Close past the viewport
|
|
175
178
|
(the Files pane wraps its header the same way). */}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useMediaQuery } from "./use-media-query.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether any pointer is fine: the display heuristic chosen in #86 for
|
|
5
|
+
* shortcut hints on touch devices. This does not detect a keyboard. A paired
|
|
6
|
+
* mouse or trackpad updates the query live; a keyboard alone does not, so a
|
|
7
|
+
* tablet with a keyboard but no trackpad prints none of these hints — ruled acceptable,
|
|
8
|
+
* with the platform fact behind it (no media query reports a keyboard), in
|
|
9
|
+
* the D36 addendum of docs/decisions/design-kit.md.
|
|
10
|
+
* Only printed hints follow this signal. Bindings remain active either way.
|
|
11
|
+
* Without matchMedia (SSR or a test DOM), preserve the existing hints.
|
|
12
|
+
*/
|
|
13
|
+
export function useFinePointer(): boolean {
|
|
14
|
+
return useMediaQuery("(any-pointer: fine)", true);
|
|
15
|
+
}
|
|
@@ -2,11 +2,13 @@ import { useEffect, useState } from "react";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Whether a media query matches, kept current as the window changes. Reads
|
|
5
|
-
* `
|
|
6
|
-
* so a component that branches on
|
|
5
|
+
* `fallback` where there is no `matchMedia` — the server, and the test DOM.
|
|
6
|
+
* It defaults to `false`, so a component that branches on a width query
|
|
7
|
+
* renders its mobile shape there; a capability query that must fail open
|
|
8
|
+
* passes `true` (see `useFinePointer`).
|
|
7
9
|
*/
|
|
8
|
-
export function useMediaQuery(query: string): boolean {
|
|
9
|
-
const [matches, setMatches] = useState(() => read(query));
|
|
10
|
+
export function useMediaQuery(query: string, fallback = false): boolean {
|
|
11
|
+
const [matches, setMatches] = useState(() => read(query, fallback));
|
|
10
12
|
useEffect(() => {
|
|
11
13
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
12
14
|
let list: MediaQueryList;
|
|
@@ -23,10 +25,11 @@ export function useMediaQuery(query: string): boolean {
|
|
|
23
25
|
return matches;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
function read(query: string): boolean {
|
|
28
|
+
function read(query: string, fallback: boolean): boolean {
|
|
27
29
|
try {
|
|
28
|
-
|
|
30
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return fallback;
|
|
31
|
+
return window.matchMedia(query).matches;
|
|
29
32
|
} catch {
|
|
30
|
-
return
|
|
33
|
+
return fallback;
|
|
31
34
|
}
|
|
32
35
|
}
|
package/src/stores/chat-state.ts
CHANGED
|
@@ -65,6 +65,20 @@ export interface ToolCall {
|
|
|
65
65
|
* allow"; "command" (a destructive-bash confirmation) is per-use only.
|
|
66
66
|
*/
|
|
67
67
|
approvalKind?: "tool" | "command";
|
|
68
|
+
/**
|
|
69
|
+
* `false` when the host said it will not keep an "always allow" given on
|
|
70
|
+
* this card (#147) — the request is outside the turn's enforced allowlist.
|
|
71
|
+
* Absent means `approvalKind` alone decides. Read it through
|
|
72
|
+
* `offersAlwaysAllow`, not directly.
|
|
73
|
+
*/
|
|
74
|
+
approvalRememberable?: false;
|
|
75
|
+
/**
|
|
76
|
+
* The approval request's description. For a "command" confirmation it is
|
|
77
|
+
* what the command will do, in words — the matched confirm pattern's
|
|
78
|
+
* effect (#112) — and the card draws it; for a "tool" grant it is the
|
|
79
|
+
* runtime's own text and is not drawn.
|
|
80
|
+
*/
|
|
81
|
+
approvalDescription?: string;
|
|
68
82
|
/**
|
|
69
83
|
* Execution timing for the duration badge. `startedAt` is (re)stamped when
|
|
70
84
|
* the input finishes streaming or an approval is granted — so approval
|
|
@@ -189,7 +203,8 @@ export interface ChatState {
|
|
|
189
203
|
toolName: string,
|
|
190
204
|
input: Record<string, unknown>,
|
|
191
205
|
description?: string,
|
|
192
|
-
kind?: "tool" | "command"
|
|
206
|
+
kind?: "tool" | "command",
|
|
207
|
+
rememberable?: boolean
|
|
193
208
|
) => void;
|
|
194
209
|
resolveToolApproval: (key: ChatKey, toolUseId: string, approved: boolean) => void;
|
|
195
210
|
setToolResult: (key: ChatKey, toolUseId: string, output: string, isError: boolean) => void;
|
|
@@ -290,6 +305,16 @@ function revokeAttachmentUrls(messages: ChatMessage[]) {
|
|
|
290
305
|
}
|
|
291
306
|
}
|
|
292
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Whether a pending approval may offer "always allow": never for a
|
|
310
|
+
* destructive-command confirmation, and never when the host said it would
|
|
311
|
+
* not keep the grant. Both approval surfaces and the Actions receipt read
|
|
312
|
+
* this one rule, so the button and the receipt cannot disagree.
|
|
313
|
+
*/
|
|
314
|
+
export function offersAlwaysAllow(tool: Pick<ToolCall, "approvalKind" | "approvalRememberable">): boolean {
|
|
315
|
+
return tool.approvalKind !== "command" && tool.approvalRememberable !== false;
|
|
316
|
+
}
|
|
317
|
+
|
|
293
318
|
/**
|
|
294
319
|
* Every tool call waiting on the user, across every buffer (D37: an approval
|
|
295
320
|
* that arrived while you were reading something else cannot only exist as a
|
|
@@ -533,7 +558,7 @@ export function createChatStore(env: StoreEnvironment, provider: StoreApi<Provid
|
|
|
533
558
|
),
|
|
534
559
|
})),
|
|
535
560
|
|
|
536
|
-
requestToolApproval: (key, toolUseId, toolName, input,
|
|
561
|
+
requestToolApproval: (key, toolUseId, toolName, input, description, kind, rememberable) =>
|
|
537
562
|
mutateLastAssistant(key, (last) => {
|
|
538
563
|
// Check if tool call already exists (from streaming)
|
|
539
564
|
const existingIdx = last.toolCalls.findIndex(
|
|
@@ -547,6 +572,8 @@ export function createChatStore(env: StoreEnvironment, provider: StoreApi<Provid
|
|
|
547
572
|
inputJson: JSON.stringify(input, null, 2),
|
|
548
573
|
status: "pending_approval",
|
|
549
574
|
...(kind ? { approvalKind: kind } : {}),
|
|
575
|
+
...(rememberable === false ? { approvalRememberable: false } : {}),
|
|
576
|
+
...(description ? { approvalDescription: description } : {}),
|
|
550
577
|
};
|
|
551
578
|
let parts = last.parts;
|
|
552
579
|
if (existingIdx >= 0) {
|