@lightworkai.official/debug-capture-react 0.6.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.
Files changed (41) hide show
  1. package/README.md +85 -0
  2. package/dist/DebugCaptureInit.d.ts +25 -0
  3. package/dist/DebugErrorBoundary.d.ts +26 -0
  4. package/dist/ReportProblemButton.d.ts +21 -0
  5. package/dist/components/ImageAnnotatorDialog.d.ts +11 -0
  6. package/dist/components/MyTicketsPanel.d.ts +11 -0
  7. package/dist/components/ReopenPanel.d.ts +7 -0
  8. package/dist/components/RichReplyEditor.d.ts +29 -0
  9. package/dist/components/StatusHero.d.ts +8 -0
  10. package/dist/components/StatusPill.d.ts +14 -0
  11. package/dist/components/TicketBody.d.ts +5 -0
  12. package/dist/components/TicketConversation.d.ts +13 -0
  13. package/dist/components/TicketDetail.d.ts +23 -0
  14. package/dist/components/TicketScreenshots.d.ts +6 -0
  15. package/dist/components/TicketTable.d.ts +18 -0
  16. package/dist/components/TicketTimeline.d.ts +14 -0
  17. package/dist/components/ToolbarButton.d.ts +18 -0
  18. package/dist/components/icons.d.ts +35 -0
  19. package/dist/crash.d.ts +5 -0
  20. package/dist/index.d.ts +23 -0
  21. package/dist/index.mjs +1705 -0
  22. package/package.json +52 -0
  23. package/src/DebugCaptureInit.tsx +56 -0
  24. package/src/DebugErrorBoundary.tsx +50 -0
  25. package/src/ReportProblemButton.tsx +79 -0
  26. package/src/components/ImageAnnotatorDialog.tsx +160 -0
  27. package/src/components/MyTicketsPanel.tsx +321 -0
  28. package/src/components/ReopenPanel.tsx +52 -0
  29. package/src/components/RichReplyEditor.tsx +264 -0
  30. package/src/components/StatusHero.tsx +92 -0
  31. package/src/components/StatusPill.tsx +14 -0
  32. package/src/components/TicketBody.tsx +50 -0
  33. package/src/components/TicketConversation.tsx +177 -0
  34. package/src/components/TicketDetail.tsx +110 -0
  35. package/src/components/TicketScreenshots.tsx +41 -0
  36. package/src/components/TicketTable.tsx +161 -0
  37. package/src/components/TicketTimeline.tsx +81 -0
  38. package/src/components/ToolbarButton.tsx +37 -0
  39. package/src/components/icons.tsx +79 -0
  40. package/src/crash.ts +15 -0
  41. package/src/index.ts +27 -0
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @lightworkai.official/debug-capture-react
2
+
3
+ React bindings for [`@lightworkai.official/debug-capture`](../debug-capture/README.md).
4
+
5
+ ```tsx
6
+ // app entry
7
+ import { configureDebugCapture } from "@lightworkai.official/debug-capture-react";
8
+
9
+ configureDebugCapture({
10
+ host: "https://support.example.com",
11
+ realmKey: "pk_live_…",
12
+ app: { version: "1.4.2", environment: "production" },
13
+ user: () => ({ id: me.id, email: me.email, fullName: me.name, unit: me.unit }),
14
+ });
15
+ ```
16
+
17
+ ```tsx
18
+ // root layout — once, above the routes
19
+ <DebugCaptureInit />
20
+
21
+ // wherever the button belongs, styled by you
22
+ <ReportProblemButton className="rounded-lg p-2 hover:bg-gray-100" />
23
+ ```
24
+
25
+ ## Components
26
+
27
+ | | |
28
+ | --- | --- |
29
+ | `<DebugCaptureInit />` | Arms the buffers, mounts the reporter. Renders nothing. |
30
+ | `<ReportProblemButton />` | Opens the reporter. Unstyled; `className` and children are yours. |
31
+ | `<DebugErrorBoundary />` | Catches a render crash and offers to report it. |
32
+ | `<MyTicketsPanel />` | The reporter's own tickets — list, detail, conversation, reopen. |
33
+
34
+ `DebugErrorBoundary` is the one thing React needs that Vue and Angular do not: a
35
+ render error never reaches `window.onerror`, so the core's crash watcher cannot
36
+ see it. Those are the crashes users describe as "the screen went white".
37
+
38
+ ## Notes
39
+
40
+ **Mount `DebugCaptureInit` above the routes.** The buffers have to be recording
41
+ *before* a user hits a problem. A report opened from a page that only just
42
+ started listening has no trail of how the user got there, which is the half that
43
+ makes it reproducible.
44
+
45
+ **Do not wrap the button in anything that awaits.** `launch` runs straight out
46
+ of the click because screen capture is gated on a user gesture — one `await`
47
+ between the click and `getDisplayMedia` loses it, and the browser refuses with
48
+ an error that explains nothing.
49
+
50
+ **Failed queries are opt-in**, so this package never depends on a data library:
51
+
52
+ ```tsx
53
+ <DebugCaptureInit
54
+ errorQueries={() =>
55
+ queryClient.getQueryCache().findAll()
56
+ .filter((q) => q.state.status === "error")
57
+ .map((q) => ({ key: JSON.stringify(q.queryKey), error: String(q.state.error) }))}
58
+ />
59
+ ```
60
+
61
+ **`MyTicketsPanel` needs `identity` in the config** — a token your *server*
62
+ signs. `realmKey` is public, so it can authorise filing a report but never
63
+ reading one back; see [the core README](../debug-capture/README.md#it-needs-to-know-who-is-asking).
64
+ Put the panel on a route of your own:
65
+
66
+ ```tsx
67
+ export default function SupportPage() {
68
+ return <MyTicketsPanel />; // or openMyTickets() for a modal
69
+ }
70
+ ```
71
+
72
+ It is a real React component now — props, callbacks, and children where they
73
+ make sense — not a web component mounted through a ref. The logic it arranges
74
+ lives in the core and is shared with the Vue and Angular packages.
75
+
76
+ Import the stylesheet once, from anywhere:
77
+
78
+ ```ts
79
+ import "@lightworkai.official/debug-capture/style.css";
80
+ ```
81
+
82
+ **TipTap is a peer dependency** (`@tiptap/react` + starter-kit, image,
83
+ placeholder) — the reply box is the same editor the team uses. Pass `refreshKey`
84
+ to make the panel re-read its list, and `onError` to route failures into your own
85
+ notification system.
@@ -0,0 +1,25 @@
1
+ import { type DebugCaptureConfig, type ErroredQuery, type InstallOptions } from "@lightworkai.official/debug-capture";
2
+ export interface DebugCaptureInitProps extends InstallOptions {
3
+ /**
4
+ * The config. Pass it here rather than calling `configureDebugCapture`
5
+ * yourself from a parent's effect — React runs CHILD effects before parent
6
+ * ones, so a parent that configures in `useEffect` runs *after* this
7
+ * component has already tried to install, and capture never arms.
8
+ *
9
+ * Calling `configureDebugCapture` at module scope also works, and is what the
10
+ * README shows. This exists so the wrong order is not reachable.
11
+ */
12
+ config?: DebugCaptureConfig;
13
+ /**
14
+ * Failed queries from the app's data layer, if it has one. A callback so this
15
+ * package never depends on react-query or any other client — with TanStack
16
+ * Query that is:
17
+ *
18
+ * errorQueries={() =>
19
+ * queryClient.getQueryCache().findAll()
20
+ * .filter((q) => q.state.status === 'error')
21
+ * .map((q) => ({ key: JSON.stringify(q.queryKey), error: String(q.state.error) }))}
22
+ */
23
+ errorQueries?: () => ErroredQuery[];
24
+ }
25
+ export declare function DebugCaptureInit({ config, errorQueries, ...options }: DebugCaptureInitProps): null;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Catches a render crash and offers to report it.
3
+ *
4
+ * A React render error does NOT reach window.onerror, so the crash watcher in
5
+ * the core never sees it — this is the only way those get captured at all, and
6
+ * they are the ones users describe as "the screen went white".
7
+ *
8
+ * Class component because that is the only thing React gives us:
9
+ * componentDidCatch has no hook equivalent.
10
+ */
11
+ import { Component, type ErrorInfo, type ReactNode } from "react";
12
+ interface Props {
13
+ children: ReactNode;
14
+ /** What to show instead of the crashed subtree. */
15
+ fallback?: (error: Error, report: () => void) => ReactNode;
16
+ }
17
+ interface State {
18
+ error: Error | null;
19
+ }
20
+ export declare class DebugErrorBoundary extends Component<Props, State> {
21
+ state: State;
22
+ static getDerivedStateFromError(error: Error): State;
23
+ componentDidCatch(error: Error, info: ErrorInfo): void;
24
+ render(): ReactNode;
25
+ }
26
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The bug icon that opens the reporter.
3
+ *
4
+ * `launch` runs straight out of the click with nothing awaited in front of it.
5
+ * That is not style: screen capture is gated on a user gesture, and a single
6
+ * await between the click and `getDisplayMedia` loses it — the browser then
7
+ * refuses with an error that says nothing about why.
8
+ *
9
+ * It arrives presentable and `className` replaces that entirely. Shipping it
10
+ * unstyled meant every host wrote the same forty characters of CSS to make a
11
+ * bug icon look like a header button — work the library should have done. But
12
+ * the default must be *replaceable*, not merged: a widget with opinions about
13
+ * its border radius that you cannot override is a widget somebody has to fight.
14
+ */
15
+ import type { ButtonHTMLAttributes, ReactNode } from "react";
16
+ export interface ReportProblemButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
17
+ /** Replace the default bug glyph — pass the host's own icon component. */
18
+ children?: ReactNode;
19
+ label?: string;
20
+ }
21
+ export declare function ReportProblemButton({ children, label, className, style, onClick, ...rest }: ReportProblemButtonProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ export interface ImageAnnotatorDialogProps {
2
+ /** The picked image, as a same-origin data URI. */
3
+ src: string;
4
+ /** Shown in the description, as the app's dialog does. */
5
+ filename?: string;
6
+ locale: "th" | "en" | undefined;
7
+ /** The annotated image, at full resolution. */
8
+ onConfirm: (dataUri: string) => void;
9
+ onCancel: () => void;
10
+ }
11
+ export declare function ImageAnnotatorDialog({ src, filename, locale, onConfirm, onCancel }: ImageAnnotatorDialogProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { type MyTicketDetail } from "@lightworkai.official/debug-capture";
2
+ export interface MyTicketsPanelProps {
3
+ /** Change it to re-read the list — after filing a report, say. */
4
+ refreshKey?: string | number;
5
+ /** Hide the panel's own heading when the host page already has one. */
6
+ hideTitle?: boolean;
7
+ onReplied?: (ticket: MyTicketDetail) => void;
8
+ onReopened?: (ticket: MyTicketDetail) => void;
9
+ onError?: (message: string) => void;
10
+ }
11
+ export declare function MyTicketsPanel({ refreshKey, hideTitle, onReplied, onReopened, onError }: MyTicketsPanelProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TicketStrings } from "@lightworkai.official/debug-capture";
2
+ export declare function ReopenPanel({ t, locale, busy, onReopen, }: {
3
+ t: TicketStrings;
4
+ locale: "th" | "en";
5
+ busy?: boolean;
6
+ onReopen: (note: string) => void;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The toolbar's words.
3
+ *
4
+ * An open map because the toolbar is configurable — a host asking for `strike`
5
+ * or `blockquote` needs a label for it, and enumerating every possible control
6
+ * here would mean editing this type every time one is added.
7
+ */
8
+ export type EditorLabels = Record<string, string>;
9
+ export interface RichReplyEditorHandle {
10
+ /** Sanitised HTML, or "" when there is nothing worth sending. */
11
+ value: () => string;
12
+ clear: () => void;
13
+ focus: () => void;
14
+ }
15
+ export interface RichReplyEditorProps {
16
+ placeholder: string;
17
+ /** Support host origin — the sanitiser resolves inline image URLs against it. */
18
+ host: string;
19
+ disabled?: boolean;
20
+ labels: EditorLabels;
21
+ /** Store an image and return the URL to reference it by. */
22
+ uploadImage: (file: File) => Promise<string>;
23
+ onError: (message: string) => void;
24
+ /** UI language, for the annotator's own labels. */
25
+ locale?: "th" | "en";
26
+ /** Content to start from — editing an existing message rather than writing one. */
27
+ initial?: string;
28
+ }
29
+ export declare const RichReplyEditor: import("react").ForwardRefExoticComponent<RichReplyEditorProps & import("react").RefAttributes<RichReplyEditorHandle>>;
@@ -0,0 +1,8 @@
1
+ import type { MyTicketDetail, StatusMaps, TicketStrings } from "@lightworkai.official/debug-capture";
2
+ export declare function StatusHero({ ticket, maps, locale, host, }: {
3
+ ticket: MyTicketDetail;
4
+ maps: StatusMaps;
5
+ t: TicketStrings;
6
+ locale: "th" | "en";
7
+ host: string;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A status as the realm coloured it.
3
+ *
4
+ * The tone arrives as DATA — a realm adds lanes whenever it likes — so it is an
5
+ * inline style rather than a class. No stylesheet could know the names ahead of
6
+ * time.
7
+ */
8
+ export declare function StatusPill({ label, tone }: {
9
+ label: string;
10
+ tone: {
11
+ fg: string;
12
+ bg: string;
13
+ };
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ export declare function TicketBody({ html, host, className }: {
2
+ html: string;
3
+ host: string;
4
+ className?: string;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { type MyTicketDetail, type TicketStrings } from "@lightworkai.official/debug-capture";
2
+ export declare function TicketConversation({ ticket, t, host, timezone, locale, sending, uploadImage, onSend, onEdited, onError, }: {
3
+ ticket: MyTicketDetail;
4
+ t: TicketStrings;
5
+ host: string;
6
+ timezone: string;
7
+ locale: "th" | "en" | undefined;
8
+ sending?: boolean;
9
+ uploadImage: (file: File) => Promise<string>;
10
+ onSend: (body: string) => void;
11
+ onEdited: () => void;
12
+ onError: (message: string) => void;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * One report: what was filed, where it stands, what happened, and the thread.
3
+ * The order is the original's.
4
+ */
5
+ import { type MyTicketDetail, type RealmConfig, type StatusEvent, type StatusMaps, type TicketStrings } from "@lightworkai.official/debug-capture";
6
+ export declare function TicketDetail(props: {
7
+ ticket: MyTicketDetail;
8
+ history: StatusEvent[];
9
+ config: RealmConfig;
10
+ maps: StatusMaps;
11
+ t: TicketStrings;
12
+ host: string;
13
+ locale: "th" | "en" | undefined;
14
+ sending?: boolean;
15
+ reopening?: boolean;
16
+ uploadImage: (file: File) => Promise<string>;
17
+ attachmentUrl: (id: string) => Promise<string>;
18
+ onBack: () => void;
19
+ onSend: (body: string) => void;
20
+ onEdited: () => void;
21
+ onReopen: (note: string) => void;
22
+ onError: (message: string) => void;
23
+ }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { TicketAttachment } from "@lightworkai.official/debug-capture";
2
+ export declare function TicketScreenshots({ images, title, attachmentUrl, }: {
3
+ images: TicketAttachment[];
4
+ title: string;
5
+ attachmentUrl: (id: string) => Promise<string>;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { type MyTicketListItem, type Sort, type SortKey, type StatusMaps, type TicketStrings } from "@lightworkai.official/debug-capture";
2
+ export interface TicketTableProps {
3
+ items: MyTicketListItem[];
4
+ maps: StatusMaps;
5
+ t: TicketStrings;
6
+ slug: string | undefined;
7
+ timezone: string;
8
+ locale: "th" | "en" | undefined;
9
+ keyword: string;
10
+ statusFilter: string;
11
+ sort: Sort;
12
+ page: number;
13
+ onOpen: (id: string) => void;
14
+ onSort: (key: SortKey) => void;
15
+ onPage: (page: number) => void;
16
+ onCount?: (count: number) => void;
17
+ }
18
+ export declare function TicketTable(props: TicketTableProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The lifecycle, chronologically — including a reopen, which is the event a
3
+ * fixed happy-path progress bar hides and the one a reporter most wants to see
4
+ * recorded.
5
+ */
6
+ import { type StatusEvent, type StatusMaps } from "@lightworkai.official/debug-capture";
7
+ export declare function TicketTimeline({ history, status, createdAt, maps, timezone, locale, }: {
8
+ history: StatusEvent[];
9
+ status: string;
10
+ createdAt: string;
11
+ maps: StatusMaps;
12
+ timezone: string;
13
+ locale: "th" | "en";
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * One editor toolbar button.
3
+ *
4
+ * It fires on `mousedown` with the default prevented, not on `click`, and that
5
+ * is the whole reason it is a component rather than a `<button>` repeated seven
6
+ * times: a toolbar button taking focus collapses the editor's selection, so by
7
+ * the time `click` fires the command has nothing to apply to. Getting that
8
+ * wrong is invisible until someone selects a word and presses B.
9
+ */
10
+ import type { ReactNode } from "react";
11
+ export interface ToolbarButtonProps {
12
+ label: string;
13
+ active?: boolean;
14
+ disabled?: boolean;
15
+ onActivate: () => void;
16
+ children: ReactNode;
17
+ }
18
+ export declare function ToolbarButton({ label, active, disabled, onActivate, children }: ToolbarButtonProps): import("react").JSX.Element;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The panel's icons, drawn rather than imported.
3
+ *
4
+ * The original uses lucide, and a package that pulled an icon set in so a reply
5
+ * box could show a B would cost every consumer that dependency. These are the
6
+ * same lucide paths at the same 24-unit scale.
7
+ */
8
+ import type { SVGProps } from "react";
9
+ export declare const BoldIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
10
+ export declare const ItalicIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
11
+ export declare const UnderlineIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
12
+ export declare const BulletListIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
13
+ export declare const OrderedListIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
14
+ export declare const LinkIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
15
+ export declare const ImageIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
16
+ export declare const SendIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
17
+ export declare const SearchIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
18
+ export declare const ArrowLeftIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
19
+ export declare const CheckIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
20
+ export declare const MessageIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
21
+ export declare const RotateIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
22
+ export declare const AlertIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
23
+ export declare const InboxIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
24
+ export declare const ClockIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
25
+ export declare const CheckCircleIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
26
+ export declare const BanIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
27
+ export declare const WrenchIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
28
+ export declare const SquareIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
29
+ export declare const ArrowUpRightIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
30
+ export declare const HighlighterIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
31
+ export declare const TypeIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
32
+ export declare const TrashIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
33
+ export declare const PencilIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
34
+ export declare const CheckSmallIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
35
+ export declare const XSmallIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Announce a render crash on the same channel as an uncaught error, so a host
3
+ * that already listens for crashes gets these too without a second wiring.
4
+ */
5
+ export declare function emitCrashFromBoundary(error: Error, componentStack?: string): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * React bindings for @lightworkai.official/debug-capture.
3
+ *
4
+ * Thin on purpose: the dialog, the annotator and the capture all live in the
5
+ * core as one custom element, so these three components are a mount, a click
6
+ * and an error boundary. Everything a Vue or Angular host gets, a React host
7
+ * gets from the same code.
8
+ */
9
+ export { DebugCaptureInit } from "./DebugCaptureInit";
10
+ export type { DebugCaptureInitProps } from "./DebugCaptureInit";
11
+ export { ReportProblemButton } from "./ReportProblemButton";
12
+ export type { ReportProblemButtonProps } from "./ReportProblemButton";
13
+ export { DebugErrorBoundary } from "./DebugErrorBoundary";
14
+ export { MyTicketsPanel } from "./components/MyTicketsPanel";
15
+ export type { MyTicketsPanelProps } from "./components/MyTicketsPanel";
16
+ export { TicketTable } from "./components/TicketTable";
17
+ export { TicketDetail } from "./components/TicketDetail";
18
+ export { TicketConversation } from "./components/TicketConversation";
19
+ export { RichReplyEditor } from "./components/RichReplyEditor";
20
+ export { TicketBody } from "./components/TicketBody";
21
+ export { ImageAnnotatorDialog } from "./components/ImageAnnotatorDialog";
22
+ export { configureDebugCapture, launch, onCrash } from "@lightworkai.official/debug-capture";
23
+ export type { DebugCaptureConfig, DebugReason, ErroredQuery, MyTicketListItem, MyTicketDetail } from "@lightworkai.official/debug-capture";