@signalsupport/sdk 0.0.0-stage → 0.7.1

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +373 -2
  3. package/dist/chunks/app-7692BILt.js +2 -0
  4. package/dist/chunks/app-7692BILt.js.map +1 -0
  5. package/dist/chunks/constants-ClGbEpjT.js +2 -0
  6. package/dist/chunks/constants-ClGbEpjT.js.map +1 -0
  7. package/dist/chunks/support-x7N-k02b.js +4 -0
  8. package/dist/chunks/support-x7N-k02b.js.map +1 -0
  9. package/dist/chunks/widget-Bxsz5StK.js +2 -0
  10. package/dist/chunks/widget-Bxsz5StK.js.map +1 -0
  11. package/dist/client.d.ts +99 -0
  12. package/dist/constants.d.ts +2 -0
  13. package/dist/errors.d.ts +25 -0
  14. package/dist/evidence.d.ts +87 -0
  15. package/dist/iife.d.ts +12 -0
  16. package/dist/index.d.ts +21 -0
  17. package/dist/index.js +1 -0
  18. package/dist/model.d.ts +201 -0
  19. package/dist/react.d.ts +26 -0
  20. package/dist/react.js +2 -0
  21. package/dist/react.js.map +1 -0
  22. package/dist/reporter.d.ts +14 -0
  23. package/dist/server.d.ts +54 -0
  24. package/dist/server.js +2 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/session.d.ts +29 -0
  27. package/dist/signal.iife.js +4 -0
  28. package/dist/signal.iife.js.map +1 -0
  29. package/dist/support.d.ts +108 -0
  30. package/dist/trail.d.ts +27 -0
  31. package/dist/widget/app.d.ts +31 -0
  32. package/dist/widget/copy.d.ts +104 -0
  33. package/dist/widget/ctx.d.ts +75 -0
  34. package/dist/widget/icons.d.ts +38 -0
  35. package/dist/widget/index.d.ts +111 -0
  36. package/dist/widget/index.js +1 -0
  37. package/dist/widget/parts.d.ts +28 -0
  38. package/dist/widget/pick.d.ts +30 -0
  39. package/dist/widget/screens/faq.d.ts +8 -0
  40. package/dist/widget/screens/feedback.d.ts +11 -0
  41. package/dist/widget/screens/home.d.ts +12 -0
  42. package/dist/widget/screens/report.d.ts +9 -0
  43. package/dist/widget/screens/reports.d.ts +26 -0
  44. package/dist/widget/store.d.ts +31 -0
  45. package/dist/widget/styles.d.ts +10 -0
  46. package/dist/widget/theme.d.ts +57 -0
  47. package/dist/widget/ui.d.ts +58 -0
  48. package/package.json +73 -4
@@ -0,0 +1,108 @@
1
+ import { SignalClient, type InitOptions } from './client.js';
2
+ import { type Consent, type Diagnostics } from './evidence.js';
3
+ import { type FeedbackKind, type ReporterReport, type ReporterReportDetail, type Severity } from './model.js';
4
+ import { type ReporterApi } from './reporter.js';
5
+ import { type Trail, type TrailEntry } from './trail.js';
6
+ /**
7
+ * Support, without a UI.
8
+ *
9
+ * Everything the panel does, as plain functions and one subscribable state, so
10
+ * a product can draw its own panel and still get the whole loop: file with
11
+ * evidence, follow the report, read and answer the team, and confirm the fix.
12
+ * The widget (`@signalsupport/sdk/widget`) is built on exactly this.
13
+ */
14
+ export interface SupportConfig extends InitOptions {
15
+ /**
16
+ * The build the person is running. Sent with every report, and what decides
17
+ * whether "does the fix work?" can be asked yet.
18
+ */
19
+ appVersion?: string;
20
+ /** Your own diagnostics, read only when the reporter reaches the Attach step. */
21
+ diagnostics?: Diagnostics;
22
+ /** Pass your own trail to share it, or `false` to keep none. */
23
+ trail?: Trail | false;
24
+ /** Put uncaught errors in the trail. Observes only; never reports by itself. Default true. */
25
+ captureErrors?: boolean;
26
+ /** How often to refresh the reporter's reports while the page is visible. Default 2 minutes. */
27
+ refreshMs?: number;
28
+ }
29
+ export interface SupportState {
30
+ /** Null until known. False means reports are anonymous and cannot be followed. */
31
+ signedIn: boolean | null;
32
+ loading: boolean;
33
+ reports: ReporterReport[];
34
+ /** Reports wanting something from the reporter, or carrying an unread team reply. */
35
+ attention: number;
36
+ error: string | null;
37
+ /** When the reports were last fetched successfully (ms since epoch), for "Updated 12s ago". */
38
+ updatedAt: number | null;
39
+ }
40
+ export interface IssueDraft {
41
+ what: string;
42
+ expected: string;
43
+ severity: Severity;
44
+ /** Where in the product. Prefilled from the context's `area`. */
45
+ area?: string;
46
+ screenshot?: string;
47
+ /** What the reporter chose to attach, after seeing it. */
48
+ include?: Partial<Pick<Consent, 'system' | 'browser' | 'trail' | 'extra' | 'screenshot'>>;
49
+ /** For an anonymous report: how to reach them, if they want to be reached. */
50
+ contact?: {
51
+ name?: string;
52
+ email?: string;
53
+ };
54
+ }
55
+ export interface FeedbackDraft {
56
+ kind: FeedbackKind;
57
+ body: string;
58
+ area?: string;
59
+ screenshot?: string;
60
+ contact?: {
61
+ name?: string;
62
+ email?: string;
63
+ };
64
+ }
65
+ /** What the Attach step shows, read when the reporter gets there and not before. */
66
+ export interface Collected {
67
+ system?: Record<string, unknown>;
68
+ browser?: Record<string, unknown>;
69
+ trail: TrailEntry[];
70
+ extra?: Record<string, unknown>;
71
+ }
72
+ export interface Support {
73
+ readonly client: SignalClient;
74
+ readonly reporter: ReporterApi;
75
+ readonly trail: Trail;
76
+ readonly appVersion?: string;
77
+ getState(): SupportState;
78
+ subscribe(listener: (state: SupportState) => void): () => void;
79
+ refresh(): Promise<void>;
80
+ context(): Record<string, unknown>;
81
+ setContext(context: Record<string, unknown>): void;
82
+ /** Record something the person did, for the trail a report may carry. */
83
+ record(kind: string, label: string): void;
84
+ collect(): Promise<Collected>;
85
+ submitIssue(draft: IssueDraft): Promise<{
86
+ id: string;
87
+ ref: string;
88
+ }>;
89
+ submitFeedback(draft: FeedbackDraft): Promise<{
90
+ id: string;
91
+ ref: string;
92
+ }>;
93
+ detail(id: string): Promise<ReporterReportDetail>;
94
+ reply(id: string, body: string): Promise<ReporterReportDetail>;
95
+ verify(id: string, works: boolean, note?: string): Promise<ReporterReportDetail>;
96
+ /**
97
+ * Take a report back: it sorted itself out, or they no longer need it. A
98
+ * close, never a delete; the team keeps the report and sees why.
99
+ */
100
+ withdraw(id: string, note?: string): Promise<ReporterReportDetail>;
101
+ /** Whether a report carries a team reply this browser has not opened. */
102
+ unread(report: ReporterReport): boolean;
103
+ markSeen(id: string): void;
104
+ destroy(): void;
105
+ }
106
+ /** The short reference a person can quote: the tail of the id, as the service derives it. */
107
+ export declare function refOf(id: string): string;
108
+ export declare function createSupport(config: SupportConfig): Support;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The last few things that happened, kept in memory and attached to a report
3
+ * only if the reporter keeps it.
4
+ *
5
+ * ⚠ IT ONLY OBSERVES. The error hooks append a line and do nothing else: they
6
+ * never swallow, never `preventDefault`, and never send anything anywhere. A
7
+ * listener that quietly transmitted would break the promise the report form
8
+ * makes on its own Attach step, and would be the first thing someone found in
9
+ * their DevTools.
10
+ */
11
+ export type TrailKind = 'click' | 'navigate' | 'edit' | 'error' | 'note' | (string & {});
12
+ export interface TrailEntry {
13
+ /** Epoch milliseconds. */
14
+ at: number;
15
+ kind: TrailKind;
16
+ label: string;
17
+ }
18
+ export interface Trail {
19
+ record(kind: TrailKind, label: string, at?: number): void;
20
+ /** The most recent `count`, oldest first. */
21
+ recent(count?: number): TrailEntry[];
22
+ size(): number;
23
+ clear(): void;
24
+ /** Put uncaught errors and unhandled rejections into the trail. Returns the undo. */
25
+ captureErrors(target?: Window): () => void;
26
+ }
27
+ export declare function createTrail(capacity?: number): Trail;
@@ -0,0 +1,31 @@
1
+ import type { Support } from '../support.js';
2
+ import { type Copy } from './copy.js';
3
+ import type { FaqItem, Features, HomeAction } from './ctx.js';
4
+ import type { Layout, UiStore } from './store.js';
5
+ export type { Layout } from './store.js';
6
+ export interface AppProps {
7
+ ui: UiStore;
8
+ support: Support;
9
+ copy: Copy;
10
+ features: Features;
11
+ faq: FaqItem[];
12
+ actions: HomeAction[];
13
+ layout: Layout;
14
+ side: 'left' | 'right';
15
+ launcher: false | {
16
+ position: 'bottom-right' | 'bottom-left';
17
+ label?: string;
18
+ };
19
+ brand?: {
20
+ name?: string;
21
+ logo?: string;
22
+ };
23
+ zIndex: number;
24
+ /** The host's own frame source (a desktop shell); `WidgetConfig.captureFrame`. */
25
+ captureFrame?: () => Promise<string>;
26
+ emit: (event: string, detail?: unknown) => void;
27
+ root: ShadowRoot;
28
+ }
29
+ export declare function App(p: AppProps): import("preact").JSX.Element;
30
+ export declare function renderPanel(mountPoint: HTMLElement, root: ShadowRoot, props: AppProps): void;
31
+ export declare function unmountPanel(mountPoint: HTMLElement): void;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Every word the panel says, overridable one at a time.
3
+ *
4
+ * Written from the reporter's side of the table. A product can rename
5
+ * anything to fit its voice ("Help" instead of "Support", "Ticket" instead of
6
+ * "Report") without touching layout.
7
+ */
8
+ export declare const DEFAULT_COPY: {
9
+ launcher: string;
10
+ title: string;
11
+ homeTitle: string;
12
+ reportAction: string;
13
+ reportHint: string;
14
+ feedbackAction: string;
15
+ feedbackHint: string;
16
+ faqAction: string;
17
+ faqHint: string;
18
+ yourReports: string;
19
+ refresh: string;
20
+ updatedAgo: string;
21
+ upToDate: string;
22
+ seeAll: string;
23
+ signedOutReports: string;
24
+ fixesWaitingOne: string;
25
+ fixesWaitingMany: string;
26
+ questionWaiting: string;
27
+ stepDescribe: string;
28
+ stepAttach: string;
29
+ stepReview: string;
30
+ whatLabel: string;
31
+ whatHint: string;
32
+ whatPlaceholder: string;
33
+ expectedLabel: string;
34
+ expectedHint: string;
35
+ expectedPlaceholder: string;
36
+ severityLabel: string;
37
+ whereLabel: string;
38
+ whereHint: string;
39
+ attachIntro: string;
40
+ screenshot: string;
41
+ screenshotNone: string;
42
+ capture: string;
43
+ retake: string;
44
+ capturing: string;
45
+ pickHint: string;
46
+ pickCapture: string;
47
+ system: string;
48
+ trail: string;
49
+ trailNone: string;
50
+ diagnostics: string;
51
+ contactLabel: string;
52
+ contactHint: string;
53
+ namePlaceholder: string;
54
+ emailPlaceholder: string;
55
+ attached: string;
56
+ notAttached: string;
57
+ next: string;
58
+ back: string;
59
+ cancel: string;
60
+ submitReport: string;
61
+ reportSent: string;
62
+ reportSentBody: string;
63
+ reportSentAnon: string;
64
+ track: string;
65
+ done: string;
66
+ feedbackKindLabel: string;
67
+ feedbackLabel: string;
68
+ feedbackPlaceholder: string;
69
+ sendFeedback: string;
70
+ thanks: string;
71
+ thanksBody: string;
72
+ faqSearch: string;
73
+ faqEmpty: string;
74
+ faqNotFound: string;
75
+ allReports: string;
76
+ noReports: string;
77
+ noReportsBody: string;
78
+ conversation: string;
79
+ replyPlaceholder: string;
80
+ send: string;
81
+ closedNote: string;
82
+ withdrawAction: string;
83
+ withdrawTitle: string;
84
+ withdrawBody: string;
85
+ withdrawPlaceholder: string;
86
+ withdrawConfirm: string;
87
+ withdrawKeep: string;
88
+ closedReports: string;
89
+ nothingOpen: string;
90
+ verifyTitle: string;
91
+ verifyBody: string;
92
+ verifyYes: string;
93
+ verifyNo: string;
94
+ verifyNoPlaceholder: string;
95
+ verifySend: string;
96
+ updateTitle: string;
97
+ updateBody: string;
98
+ whatYouReported: string;
99
+ team: string;
100
+ you: string;
101
+ error: string;
102
+ };
103
+ export type Copy = typeof DEFAULT_COPY;
104
+ export declare function fill(text: string, vars: Record<string, string | number>): string;
@@ -0,0 +1,75 @@
1
+ import type { CaptureResult } from '../evidence.js';
2
+ import type { Support, SupportState } from '../support.js';
3
+ import type { Copy } from './copy.js';
4
+ export interface FaqItem {
5
+ q: string;
6
+ a: string;
7
+ /** Extra words the search should match. */
8
+ tags?: string[];
9
+ }
10
+ export interface Features {
11
+ report: boolean;
12
+ feedback: boolean;
13
+ faq: boolean;
14
+ /** "Your reports". Needs a session; hidden when nobody is signed in. */
15
+ reports: boolean;
16
+ /** Offer a screen capture in the report and feedback forms. */
17
+ screenshot: boolean;
18
+ }
19
+ export type Screen = {
20
+ name: 'home';
21
+ } | {
22
+ name: 'report';
23
+ } | {
24
+ name: 'feedback';
25
+ } | {
26
+ name: 'faq';
27
+ } | {
28
+ name: 'reports';
29
+ } | {
30
+ name: 'detail';
31
+ id: string;
32
+ } | {
33
+ name: 'sent';
34
+ id: string;
35
+ ref: string;
36
+ } | {
37
+ name: 'thanks';
38
+ };
39
+ export type View = 'home' | 'report' | 'feedback' | 'faq' | 'reports';
40
+ /**
41
+ * Where a host's own content can go. Each is a native `<slot>`: what the host
42
+ * puts there stays in ITS document, so its own CSS and its own framework
43
+ * render it.
44
+ */
45
+ export type SlotName = 'header' | 'home-top' | 'home-bottom' | 'report-top' | 'feedback-top' | 'sent' | 'footer';
46
+ export declare const SLOT_NAMES: readonly SlotName[];
47
+ /** An extra entry under "How can we help?": a status page, a community, a sales line. */
48
+ export interface HomeAction {
49
+ label: string;
50
+ hint?: string;
51
+ /** A built-in icon name (`external`, `book`, `message`, …) or an image URL. */
52
+ icon?: string;
53
+ /** Opened in a new tab. */
54
+ href?: string;
55
+ /** Called on press. The panel stays open; call the widget's `close()` to leave it. */
56
+ onClick?: () => void;
57
+ }
58
+ /** What every screen is handed. */
59
+ export interface Ctx {
60
+ support: Support;
61
+ state: SupportState;
62
+ /** Changes when the person asks for fresh data; a screen with its own copy reloads on it. */
63
+ tick: number;
64
+ copy: Copy;
65
+ features: Features;
66
+ faq: FaqItem[];
67
+ actions: HomeAction[];
68
+ /** The slots the host has filled; an empty one renders nothing, not a gap. */
69
+ slots: ReadonlySet<string>;
70
+ appVersion?: string;
71
+ go: (screen: Screen) => void;
72
+ /** Take one frame, stepping the panel out of its own picture. */
73
+ capture: () => Promise<CaptureResult>;
74
+ emit: (event: 'submitted' | 'verified' | 'withdrawn', detail: unknown) => void;
75
+ }
@@ -0,0 +1,38 @@
1
+ /** @jsxImportSource preact */
2
+ /**
3
+ * The panel's icons: stroked, 24px grid, inheriting `currentColor`, so a theme
4
+ * colours them without a second set.
5
+ */
6
+ declare const PATHS: {
7
+ readonly lifebuoy: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0-5a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm2.8-6.8L18.4 5.6M9.2 9.2 5.6 5.6m3.6 9.2-3.6 3.6m9.2-3.6 3.6 3.6";
8
+ readonly bug: "M9 9V8a3 3 0 0 1 6 0v1M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1-10 0v-3a6 6 0 0 1 1-3ZM3 13h4m10 0h4M12 20v-6M4 19l3.35-2M20 19l-3.35-2M4 7l3.75 2.4M20 7l-3.75 2.4";
9
+ readonly bulb: "M3 12h1m8-9v1m8 8h1M5.6 5.6l.7.7m12.1-.7-.7.7M9 16a5 5 0 1 1 6 0 3.5 3.5 0 0 0-1 3 2 2 0 0 1-4 0 3.5 3.5 0 0 0-1-3M9.7 17h4.6";
10
+ readonly question: "M8 9h8M8 13h6M9 18H6a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-3l-3 3-3-3Z";
11
+ readonly chevronRight: "m9 6 6 6-6 6";
12
+ readonly chevronLeft: "m15 6-6 6 6 6";
13
+ readonly arrowLeft: "M5 12h14M5 12l6 6M5 12l6-6";
14
+ readonly x: "M18 6 6 18M6 6l12 12";
15
+ readonly check: "m5 12 5 5L20 7";
16
+ readonly checkCircle: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm-3-9 2 2 4-4";
17
+ readonly camera: "M5 7h1a2 2 0 0 0 2-2 1 1 0 0 1 1-1h6a1 1 0 0 1 1 1 2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Zm7 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z";
18
+ readonly monitor: "M3 5a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Zm4 15h10M9 16v4m6-4v4";
19
+ readonly history: "M12 8v4l2 2M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5";
20
+ readonly cpu: "M5 6a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6Zm4 3h6v6H9V9ZM3 10h2m-2 4h2m14-4h2m-2 4h2M10 3v2m4-2v2m-4 14v2m4-2v2";
21
+ readonly send: "M10 14 21 3M21 3l-6.5 18a.55.55 0 0 1-1 0L10 14l-7-3.5a.55.55 0 0 1 0-1L21 3";
22
+ readonly search: "M10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14Zm11 4-6-6";
23
+ readonly thumbUp: "M7 11v8a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1h3Zm0 0a4 4 0 0 0 4-4V6a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1-2 2h-7a3 3 0 0 1-3-3";
24
+ readonly refresh: "M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4";
25
+ readonly download: "M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 11l5 5 5-5M12 4v12";
26
+ readonly external: "M12 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6m-7 1 9-9m-5 0h5v5";
27
+ readonly book: "M3 19a9 9 0 0 1 9 0 9 9 0 0 1 9 0M3 6a9 9 0 0 1 9 0 9 9 0 0 1 9 0M3 6v13M12 6v13M21 6v13";
28
+ readonly message: "M8 9h8M8 13h6M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-5l-5 3v-3H6a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h12Z";
29
+ };
30
+ export type IconName = keyof typeof PATHS;
31
+ /** Whether a host-supplied name is one of ours, rather than an image URL. */
32
+ export declare function isIconName(name: string): name is IconName;
33
+ export declare function Icon({ name, size, stroke }: {
34
+ name: IconName;
35
+ size?: number;
36
+ stroke?: number;
37
+ }): import("preact").JSX.Element;
38
+ export {};
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `@signalsupport/sdk/widget` — the support panel, in any page.
3
+ *
4
+ * ```ts
5
+ * import { mountSupport } from '@signalsupport/sdk/widget';
6
+ *
7
+ * const support = mountSupport({
8
+ * key: process.env.NEXT_PUBLIC_SIGNAL_KEY_ID, // anonymous reports
9
+ * session: '/api/signal/session', // signed-in reporters follow theirs
10
+ * appVersion: '2.4.0',
11
+ * context: { area: 'Editor' },
12
+ * theme: { preset: 'midnight', accent: '#7a50dd' },
13
+ * layout: 'drawer',
14
+ * });
15
+ *
16
+ * support.open('report');
17
+ * support.on('attention', (n) => badge(n));
18
+ * ```
19
+ *
20
+ * It renders into a Shadow DOM: your CSS cannot restyle it by accident and
21
+ * none of its CSS reaches your page. It works in React, Vue, Svelte or plain
22
+ * HTML alike, because it owns its own element.
23
+ *
24
+ * # IT COSTS ALMOST NOTHING UNTIL SOMEONE NEEDS HELP
25
+ *
26
+ * This module is the shell: the element, the state and the badge count. The
27
+ * panel itself (Preact, every screen, the stylesheet) is a separate chunk,
28
+ * fetched when the browser is idle after the page has loaded, or the moment
29
+ * `open()` is called, whichever comes first. A product's first paint never
30
+ * waits for its support panel.
31
+ */
32
+ import { type Support, type SupportConfig } from '../support.js';
33
+ import { type Copy } from './copy.js';
34
+ import { type FaqItem, type Features, type HomeAction, type SlotName, type View } from './ctx.js';
35
+ import { type Layout } from './store.js';
36
+ import type { ThemeInput } from './theme.js';
37
+ export { PRESETS, resolveTheme, type PresetName, type Theme, type ThemeInput } from './theme.js';
38
+ export { DEFAULT_COPY, type Copy } from './copy.js';
39
+ export type { FaqItem, Features, HomeAction, SlotName, View } from './ctx.js';
40
+ export { PARTS } from './parts.js';
41
+ export type { Layout } from './store.js';
42
+ export interface WidgetConfig extends SupportConfig {
43
+ /** A preset name, or tokens on top of one: `{ preset: 'daylight', accent: '#e11d48' }`. */
44
+ theme?: ThemeInput;
45
+ /** `drawer` from the side (default), `modal`, `popover` above the launcher, or `sheet` from the bottom. Phones always get a sheet. */
46
+ layout?: Layout;
47
+ /** Which side a drawer slides from. */
48
+ side?: 'left' | 'right';
49
+ /** A floating button, or `false` when your own control calls `open()`. */
50
+ launcher?: false | {
51
+ position?: 'bottom-right' | 'bottom-left';
52
+ label?: string;
53
+ };
54
+ brand?: {
55
+ name?: string;
56
+ logo?: string;
57
+ };
58
+ features?: Partial<Features>;
59
+ faq?: FaqItem[];
60
+ copy?: Partial<Copy>;
61
+ /** Where the panel's element goes. Default `document.body`. */
62
+ container?: HTMLElement;
63
+ zIndex?: number;
64
+ /** Share a support instance you already created headlessly. */
65
+ support?: Support;
66
+ /** Load the panel immediately rather than when the browser is idle. */
67
+ eager?: boolean;
68
+ /**
69
+ * Your own CSS, applied INSIDE the panel after its own, so it wins at equal
70
+ * specificity. Target the `::part()` names from outside, or `[part~="button"]`
71
+ * and friends from here. The `sg-` class names are internal and may change.
72
+ */
73
+ css?: string;
74
+ /** Your own content in the panel: an element per slot name. See `slot()`. */
75
+ slots?: Partial<Record<SlotName, HTMLElement>>;
76
+ /** Extra entries under "How can we help?", after the built-in ones. */
77
+ actions?: HomeAction[];
78
+ /**
79
+ * Your own picture of your own view, for Capture, in place of the browser's
80
+ * screen capture. For a product in a desktop shell, where `getDisplayMedia`
81
+ * opens the shell's own permission dialog: resolve to an image data URL of
82
+ * the window (WebView2 `CapturePreview`, Electron `capturePage`). Capture
83
+ * mode is unchanged; there is simply no prompt. Omit it in a browser.
84
+ */
85
+ captureFrame?: () => Promise<string>;
86
+ }
87
+ export type WidgetEvent = 'open' | 'close' | 'submitted' | 'verified' | 'withdrawn' | 'attention';
88
+ export interface SupportWidget {
89
+ open(view?: View): void;
90
+ close(): void;
91
+ toggle(view?: View): void;
92
+ isOpen(): boolean;
93
+ /** Merge into the context sent with reports (where in the product, which project). */
94
+ setContext(context: Record<string, unknown>): void;
95
+ setTheme(theme: ThemeInput): void;
96
+ /** Replace the CSS given as `css`, live. An empty string removes it. */
97
+ setCss(css: string): void;
98
+ /**
99
+ * The element for one slot, created on first ask. Render into it with
100
+ * anything: plain DOM, a React portal, Vue's Teleport. It lives in YOUR
101
+ * document, so your CSS styles it.
102
+ */
103
+ slot(name: SlotName): HTMLElement;
104
+ on(event: WidgetEvent, fn: (detail: unknown) => void): () => void;
105
+ /** Resolves once the panel's code has loaded and rendered. */
106
+ ready(): Promise<void>;
107
+ /** The headless core, for your own badges and buttons. */
108
+ readonly support: Support;
109
+ destroy(): void;
110
+ }
111
+ export declare function mountSupport(config: WidgetConfig): SupportWidget;
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,s as r,t as i}from"../chunks/widget-Bxsz5StK.js";export{r as DEFAULT_COPY,n as PARTS,t as PRESETS,i as mountSupport,e as resolveTheme};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The panel's PUBLIC styling surface: which internal elements are exposed to
3
+ * the host page as `::part()` names.
4
+ *
5
+ * ```css
6
+ * [data-signal-support]::part(launcher) { border-radius: 8px; }
7
+ * [data-signal-support]::part(button-primary):hover { filter: none; }
8
+ * ```
9
+ *
10
+ * ⚠ THE PART NAMES ARE THE CONTRACT; THE `sg-` CLASSES ARE NOT. Classes get
11
+ * renamed when a screen is reworked. A part name only ever gains members, and
12
+ * removing one is a breaking change to the package. That is why this is one
13
+ * table, applied from one place, rather than a `part=` typed by hand on each
14
+ * element: the next screen inherits it without anyone remembering to.
15
+ *
16
+ * `::part()` cannot be followed by an attribute selector, so states a host may
17
+ * want to style differently get a part of their own (`chip-selected`,
18
+ * `message-you`).
19
+ */
20
+ export declare const PARTS: Record<string, string>;
21
+ /** The part names one element carries, from its classes and its state. */
22
+ export declare function partsFor(el: Element): string;
23
+ /**
24
+ * Keep every element's `part` in step with its classes for the life of the
25
+ * panel. Preact re-renders change classes and aria state in place, so this
26
+ * watches those attributes as well as new elements.
27
+ */
28
+ export declare function exposeParts(root: ShadowRoot): () => void;
@@ -0,0 +1,30 @@
1
+ import type { CaptureResult } from '../evidence.js';
2
+ import type { UiStore } from './store.js';
3
+ /**
4
+ * Capture mode: the panel steps aside so the person can go to the screen they
5
+ * mean, and a small bar stays up with Capture and Cancel.
6
+ *
7
+ * A capture taken from inside the panel shows whatever was behind it, which is
8
+ * rarely the screen the report is about. Here the panel is only HIDDEN (still
9
+ * mounted), so the half-written report survives the person changing tabs,
10
+ * scrolling or opening a menu in the product, and the panel comes back on the
11
+ * same step with the frame in it.
12
+ *
13
+ * The browser's permission prompt is asked on Capture, not on entering the
14
+ * mode: Capture is the user gesture `getDisplayMedia` needs, and the stream is
15
+ * live for one frame rather than for the whole walk round the product.
16
+ *
17
+ * `start()` resolves once: with the frame, with `cancelled` (Cancel, Escape,
18
+ * the panel closed), or with the error that ended it. Declining the browser's
19
+ * own prompt is NOT an end: the bar stays up, to try again or cancel.
20
+ */
21
+ export interface Picker {
22
+ start(): Promise<CaptureResult>;
23
+ capture(): Promise<void>;
24
+ cancel(): void;
25
+ readonly active: boolean;
26
+ }
27
+ export declare function createPicker(ui: UiStore, grab: (hooks: {
28
+ beforeCapture: () => void;
29
+ afterCapture: () => void;
30
+ }) => Promise<CaptureResult>, supported: () => boolean): Picker;
@@ -0,0 +1,8 @@
1
+ import type { Ctx } from '../ctx.js';
2
+ /**
3
+ * Answers first, a report second. Built on <details>, so it is keyboard
4
+ * operable, announced correctly, and findable by the browser's own search.
5
+ */
6
+ export declare function Faq({ w }: {
7
+ w: Ctx;
8
+ }): import("preact").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import type { Ctx } from '../ctx.js';
2
+ /**
3
+ * Feedback: an idea, a frustration, or something worth protecting.
4
+ *
5
+ * Deliberately lighter than a report. It is not a fault to be tracked, so it
6
+ * asks for one kind and one box, and offers a picture only if the reporter
7
+ * wants to point at something.
8
+ */
9
+ export declare function FeedbackForm({ w }: {
10
+ w: Ctx;
11
+ }): import("preact").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import type { Ctx } from '../ctx.js';
2
+ /**
3
+ * The first screen: what can be done, and what is waiting on you.
4
+ *
5
+ * ⚠ THE EMPTY STATE DRAWS THE JOURNEY. "You have not reported anything" over
6
+ * an empty panel argues for doing nothing; the same space spent showing what a
7
+ * report actually does, ending with the reporter's own check, is the strongest
8
+ * case for filing one, and the only moment it gets read unhurried.
9
+ */
10
+ export declare function Home({ w }: {
11
+ w: Ctx;
12
+ }): import("preact").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { Ctx } from '../ctx.js';
2
+ export declare function ReportFlow({ w }: {
3
+ w: Ctx;
4
+ }): import("preact").JSX.Element;
5
+ /** The step rail: a line that fills, because "how much is left" is the question mid-form. */
6
+ export declare function Steps({ step, labels }: {
7
+ step: number;
8
+ labels: string[];
9
+ }): import("preact").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import type { Ctx } from '../ctx.js';
2
+ /**
3
+ * The reporter's own reports: the list, one report, the conversation, and the
4
+ * check that closes the loop.
5
+ *
6
+ * ⚠ THE CHECK IS THE REASON THIS SCREEN EXISTS. "We think we fixed it, you are
7
+ * running the build that has the fix, does it work?" can only be asked in the
8
+ * product, to the person already looking at the thing, and it turns a tracker
9
+ * into a loop that closes.
10
+ */
11
+ /**
12
+ * Open reports first; closed ones folded away under "Closed".
13
+ *
14
+ * A closed report is history, and a list where "Closed, you confirmed it"
15
+ * sits between two live ones makes the live ones harder to see. Folded, not
16
+ * hidden: one tap shows them, nothing is lost. On the home screen only the
17
+ * open ones are listed, with a way through to the rest.
18
+ */
19
+ export declare function ReportList({ w, compact }: {
20
+ w: Ctx;
21
+ compact?: boolean;
22
+ }): import("preact").JSX.Element;
23
+ export declare function ReportDetail({ w, id }: {
24
+ w: Ctx;
25
+ id: string;
26
+ }): import("preact").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import type { Screen } from './ctx.js';
2
+ import type { ThemeInput } from './theme.js';
3
+ /**
4
+ * The panel's own state, outside Preact.
5
+ *
6
+ * ⚠ IT LIVES IN THE SMALL SHELL, NOT IN THE PANEL CHUNK. The panel is loaded
7
+ * lazily (see `index.ts`), and a product can call `open()` before that chunk
8
+ * has arrived. Keeping the state here means the call is remembered and the
9
+ * panel opens where it was asked to the moment it renders.
10
+ */
11
+ export type Layout = 'drawer' | 'modal' | 'popover' | 'sheet';
12
+ export interface UiState {
13
+ open: boolean;
14
+ history: Screen[];
15
+ /** Bumped per open: every open starts fresh, never mid-way through an abandoned form. */
16
+ session: number;
17
+ hidden: boolean;
18
+ /** Capture mode: the panel is stepped aside (still mounted) and the capture bar is up. See `pick.ts`. */
19
+ picking: boolean;
20
+ theme: ThemeInput | undefined;
21
+ /** Slot names the host has filled, so the panel only makes room for those. */
22
+ slots: string[];
23
+ /** Bumped by the refresh button: a screen holding its own copy (a report) reloads on it. */
24
+ tick: number;
25
+ }
26
+ export interface UiStore {
27
+ get(): UiState;
28
+ set(patch: Partial<UiState>): void;
29
+ subscribe(fn: () => void): () => void;
30
+ }
31
+ export declare function createUiStore(initial: UiState): UiStore;