@ganeshdole/brainbox-widget 0.0.0-stage → 0.2.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,28 @@
1
+ O'Saasy License
2
+
3
+ Copyright © 2026, Ganesh Dole.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ 1. The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ 2. No licensee or downstream recipient may use the Software (including
17
+ any modified or derivative versions) to directly compete with the
18
+ original Licensor by offering it to third parties as a hosted, managed,
19
+ or Software-as-a-Service (SaaS) product or cloud service where the
20
+ primary value of the service is the functionality of the Software itself.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @ganeshdole/brainbox-widget
2
+
3
+ Brainbox's own capture UI: the floating button, the markup step, the
4
+ recorder, the composer. Built on `@ganeshdole/brainbox-core` and `@ganeshdole/brainbox-react`.
5
+ Three ways in, one UI.
6
+
7
+ ## Script tag
8
+
9
+ For a page with no build step. Paste before `</body>`:
10
+
11
+ ```html
12
+ <script src="https://app.brainbox.sh/widget.js"
13
+ data-project="pk_..."
14
+ data-endpoint="https://app.brainbox.sh/ingest"></script>
15
+ ```
16
+
17
+ | Attribute | Values | Default |
18
+ |---|---|---|
19
+ | `data-project` | your project key | required |
20
+ | `data-endpoint` | where reports are POSTed | required |
21
+ | `data-mode` | `float` (our button) or `mount` (your own trigger) | `float` |
22
+ | `data-mount` | CSS selector of your trigger, with `data-mode="mount"` | |
23
+ | `data-position` | `bottom-right`, `bottom-left`, `top-right`, `top-left` | `bottom-right` |
24
+
25
+ The tag publishes `window.Brainbox` with `open()`, `start(mode)`, `close()` and
26
+ `identify({ id, email })`, and fires a `brainbox:submitted` event on `window`
27
+ (`event.detail.id`) after each report.
28
+
29
+ ## In a React app
30
+
31
+ ```sh
32
+ npm install @ganeshdole/brainbox-widget @ganeshdole/brainbox-react @ganeshdole/brainbox-core
33
+ ```
34
+
35
+ ```tsx
36
+ import { BrainboxProvider } from "@ganeshdole/brainbox-react";
37
+ import { BrainboxWidget, useBrainboxWidget } from "@ganeshdole/brainbox-widget";
38
+
39
+ <BrainboxProvider transport={transport} identity={user}>
40
+ <App />
41
+ <BrainboxWidget /> {/* trigger="floating" | "manual", position */}
42
+ </BrainboxProvider>
43
+
44
+ function ReportProblemButton() {
45
+ const { open } = useBrainboxWidget(); // anywhere under the provider
46
+ return <button onClick={open}>Report a problem</button>;
47
+ }
48
+ ```
49
+
50
+ `open()` shows our chooser. If your own UI already offers the two choices,
51
+ `start("screenshot")` or `start("record")` goes straight into one. Recording
52
+ falls back to the chooser in a browser that cannot record (`canSessionRecord()`
53
+ from `@ganeshdole/brainbox-core` tells you in advance).
54
+
55
+ The widget renders into a shadow root appended to `<body>`, wherever it sits
56
+ in your tree, so your styles cannot reach it and no ancestor's `overflow` or
57
+ `transform` can trap its panels. Your React is used; the package does not
58
+ bundle one. Clicks on the widget stop at its shadow host, so a menu your page
59
+ closes on an outside click stays open while the user reports it (a listener in
60
+ the capture phase still sees them).
61
+
62
+ ## Without React, from a bundler
63
+
64
+ ```ts
65
+ import { mount } from "@ganeshdole/brainbox-widget";
66
+ import { brainboxTransport } from "@ganeshdole/brainbox-core";
67
+
68
+ const widget = mount({ transport: brainboxTransport({ endpoint, projectKey }), trigger: "manual" });
69
+ widget.open();
70
+ widget.identify({ id, email });
71
+ widget.destroy();
72
+ ```
73
+
74
+ ## Where things live
75
+
76
+ | Concern | Package |
77
+ |---|---|
78
+ | What gets captured and sent | `@ganeshdole/brainbox-core` |
79
+ | Holding an instance in React | `@ganeshdole/brainbox-react` |
80
+ | What the user sees | this package |
81
+
82
+ Reports go wherever the `Transport` sends them. `brainboxTransport()` is
83
+ Brainbox Cloud; see `@ganeshdole/brainbox-core` for pointing one at your own backend.
84
+
85
+ ## Build
86
+
87
+ `pnpm build` emits `dist/widget.js` (the script-tag IIFE, everything bundled
88
+ except the capture libraries), `dist/widget-capture.js` (rrweb and
89
+ modern-screenshot, which `widget.js` loads from beside itself the first time the
90
+ widget opens - serve the two files from the same folder), `dist/index.js` (an ES module of the library entry with React, `lucide-react`
91
+ and the other `@ganeshdole/brainbox-*` packages external) and the `.d.ts` files. Inside
92
+ the monorepo the package is consumed from source; the published package's
93
+ `exports` point at `dist/` (`publishConfig`). `react` and `react-dom` are peer
94
+ dependencies of the library; the IIFE still bundles them.
package/dist/App.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { type Brainbox } from "@ganeshdole/brainbox-core";
2
+ import type { TriggerMode } from "@ganeshdole/brainbox-shared";
3
+ import type { Position } from "./lib/position.ts";
4
+ export declare function App({ brainbox, hostEl, trigger, position, }: {
5
+ brainbox: Brainbox;
6
+ /** The shadow host: left out of screenshots. */
7
+ hostEl: HTMLElement;
8
+ trigger: TriggerMode;
9
+ position: Position;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ /** A row of level bars, themed with brainbox tokens rather than the shadcn ones
2
+ * ElevenLabs UI's `bar-visualizer` ships with. `dimFrom` greys everything at or
3
+ * past that index, which is how a recorded clip doubles as a playback scrubber. */
4
+ export declare const BarRow: import("react").MemoExoticComponent<({ levels, dimFrom, }: {
5
+ levels: readonly number[];
6
+ dimFrom?: number;
7
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,8 @@
1
+ import { type Position } from "../lib/position.ts";
2
+ export declare function Chooser({ position, canRecord, onScreenshot, onRecord, onCancel, }: {
3
+ position: Position;
4
+ canRecord: boolean;
5
+ onScreenshot: () => void;
6
+ onRecord: () => void;
7
+ onCancel: () => void;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { type Position } from "../lib/position.ts";
2
+ export declare function Composer({ screenshotUrl, videoUrl, sessionReady, voiceCaptured, capturePending, captureFailed, position, onCancel, onSubmit, }: {
3
+ screenshotUrl?: string;
4
+ videoUrl?: string;
5
+ sessionReady?: boolean;
6
+ voiceCaptured?: boolean;
7
+ /** Screenshot still rasterising - the composer opens without waiting for it. */
8
+ capturePending?: boolean;
9
+ captureFailed?: boolean;
10
+ position: Position;
11
+ onCancel: () => void;
12
+ onSubmit: (text: string, audio: Blob | null) => void;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { type Position } from "../lib/position.ts";
2
+ export declare function Launcher({ position, onClick }: {
3
+ position: Position;
4
+ onClick: () => void;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { type Mark } from "@ganeshdole/brainbox-core";
2
+ /** One mark as SVG. Shared by the markup step and the in-recording overlay so
3
+ * a box drawn in one looks identical in the other - and matches the bake,
4
+ * which draws the same geometry onto a canvas. */
5
+ export declare const MarkShape: import("react").MemoExoticComponent<({ mark }: {
6
+ mark: Mark;
7
+ }) => import("react").JSX.Element>;
8
+ /** The floating input a text mark is typed into, positioned so the text sits
9
+ * where the baked glyphs will land. */
10
+ export declare function MarkTextInput({ at, color, onChange, onCommit, onCancel, }: {
11
+ at: {
12
+ x: number;
13
+ y: number;
14
+ value: string;
15
+ };
16
+ color: string;
17
+ onChange: (value: string) => void;
18
+ onCommit: () => void;
19
+ onCancel: () => void;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { type Mark } from "@ganeshdole/brainbox-core";
2
+ /**
3
+ * The markup step: the page is frozen and the end-user draws on it - boxes,
4
+ * arrows, freehand, text - before saying what's wrong.
5
+ *
6
+ * The screenshot is already rasterising when this mounts, so the marks are held
7
+ * as vectors here and baked onto the shot on the way out. Drawing never waits
8
+ * on the capture, and everything stays undoable right up to Done.
9
+ */
10
+ export declare function MarkupOverlay({ frozenUrl, pending, failed, onDone, onCancel, }: {
11
+ /** The frozen page, once rasterised. Until then the live page shows through. */
12
+ frozenUrl?: string;
13
+ pending: boolean;
14
+ failed: boolean;
15
+ onDone: (marks: Mark[]) => void;
16
+ onCancel: () => void;
17
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { type Tool } from "@ganeshdole/brainbox-core";
2
+ export declare function MarkupToolbar({ tool, color, canUndo, canDelete, onTool, onColor, onUndo, onDelete, onCancel, onDone, }: {
3
+ tool: Tool;
4
+ color: string;
5
+ canUndo: boolean;
6
+ canDelete: boolean;
7
+ onTool: (t: Tool) => void;
8
+ onColor: (c: string) => void;
9
+ onUndo: () => void;
10
+ onDelete: () => void;
11
+ onCancel: () => void;
12
+ onDone: () => void;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { type Annotations } from "@ganeshdole/brainbox-core";
2
+ export declare function RecordOverlay({ annotations, onStop, micActive, onMuteChange, }: {
3
+ /** The recording's marks on the host page - drawn here, cleared by core. */
4
+ annotations: Annotations;
5
+ onStop: () => void;
6
+ micActive: () => boolean;
7
+ onMuteChange: (muted: boolean) => void;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { type Position } from "../lib/position.ts";
2
+ export declare function Result({ kind, id, message, position, onClose, }: {
3
+ kind: "loading" | "success" | "error";
4
+ id?: string;
5
+ message?: string;
6
+ position: Position;
7
+ onClose?: () => void;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ /** Voice capture as the primary way to file a report: one big mic target, bars
2
+ * that track the speaker's voice per frequency band, and playback before
3
+ * sending - people won't submit audio they can't hear back. */
4
+ export declare function VoiceNote({ onChange }: {
5
+ onChange: (b: Blob | null) => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ export { BrainboxWidget, type BrainboxWidgetProps } from "./widget.tsx";
2
+ export { useBrainboxWidget } from "./use-brainbox-widget.ts";
3
+ export { mount, type MountOptions, type WidgetHandle } from "./mount.tsx";
4
+ export type { Position } from "./lib/position.ts";
5
+ export type { CaptureMode } from "./lib/widget-store.ts";
6
+ export type { TriggerMode } from "@ganeshdole/brainbox-shared";