@fixback/sdk 0.3.0 → 0.4.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/README.md +11 -10
- package/dist/annotation.d.ts +1 -1
- package/dist/boot.d.ts +6 -0
- package/dist/error-capture.d.ts +31 -1
- package/dist/fixback.umd.js +109 -62
- package/dist/fixback.umd.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +4868 -1824
- package/dist/index.mjs.map +1 -1
- package/dist/init.d.ts +21 -1
- package/dist/launcher.d.ts +2 -2
- package/dist/overlay-styles.d.ts +1 -1
- package/dist/overlay.d.ts +12 -0
- package/dist/replay.d.ts +101 -0
- package/dist/report.d.ts +47 -5
- package/dist/styles.d.ts +2 -2
- package/dist/submit.d.ts +8 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -1
package/dist/init.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IdentityInputs } from "./boot";
|
|
2
2
|
import { type BeforeBreadcrumb, type BreadcrumbLevel, type TraceStream } from "./breadcrumbs";
|
|
3
|
+
import { type ReplayOptions } from "./replay";
|
|
3
4
|
import type { BeforeSend } from "./scrub";
|
|
4
5
|
/**
|
|
5
6
|
* The hosted Fixback API origin the SDK talks to by default. A self-hosted or
|
|
@@ -69,17 +70,25 @@ export interface InitOptions extends IdentityInputs {
|
|
|
69
70
|
readonly scrub?: boolean;
|
|
70
71
|
/** Trace buffer tuning, or `false` to turn the buffer off entirely. */
|
|
71
72
|
readonly trace?: TraceOptions | false;
|
|
73
|
+
/**
|
|
74
|
+
* Buffered session-replay tuning (issue #189, ADR-0024) — masking defaults on:
|
|
75
|
+
* all inputs, all text, SDK chrome blocked — or `false` to turn the recorder
|
|
76
|
+
* off entirely regardless of the served config.
|
|
77
|
+
*/
|
|
78
|
+
readonly replay?: ReplayOptions | false;
|
|
72
79
|
/**
|
|
73
80
|
* The dev-side override of the Project's server-served capture config (spec #122
|
|
74
81
|
* §L; ticket #138). Capture is **default-on** and normally governed per-project
|
|
75
82
|
* from the dashboard, surfaced on the boot answer; set a stream here to override
|
|
76
83
|
* what the server serves for it — `{ network: false }` turns network capture off
|
|
77
84
|
* even where the Project leaves it on, and a stream left unset follows the served
|
|
78
|
-
* config. Independent of {@link trace} `false`, which turns the whole buffer off
|
|
85
|
+
* config. Independent of {@link trace} `false`, which turns the whole buffer off,
|
|
86
|
+
* and of {@link replay} `false`, which turns the recorder off.
|
|
79
87
|
*/
|
|
80
88
|
readonly capture?: {
|
|
81
89
|
readonly console?: boolean;
|
|
82
90
|
readonly network?: boolean;
|
|
91
|
+
readonly replay?: boolean;
|
|
83
92
|
};
|
|
84
93
|
/**
|
|
85
94
|
* Automatic error capture — the SDK files uncaught exceptions / unhandled
|
|
@@ -89,6 +98,17 @@ export interface InitOptions extends IdentityInputs {
|
|
|
89
98
|
* never filed where a manual report would be refused.
|
|
90
99
|
*/
|
|
91
100
|
readonly autoCapture?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The host app's **Release** — a build identifier (a git SHA, a semver, a
|
|
103
|
+
* `name@version`) stamped into every submission's environment (#117,
|
|
104
|
+
* ADR-0024). Set it to the same value the build uploaded sourcemaps under
|
|
105
|
+
* (`npx fixback sourcemaps upload --release <v>`), and auto-captured errors
|
|
106
|
+
* gain a symbolicated **code-area pointer** on their Issues. Optional: without
|
|
107
|
+
* it (or without uploaded sourcemaps) everything behaves exactly as before.
|
|
108
|
+
* An invalid value (whitespace, slashes, over 100 chars) is ignored with a
|
|
109
|
+
* console warning.
|
|
110
|
+
*/
|
|
111
|
+
readonly release?: string;
|
|
92
112
|
}
|
|
93
113
|
/**
|
|
94
114
|
* Options for {@link redeem} — the explicit form of the `?fixback_invite=` URL
|
package/dist/launcher.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ export interface Launcher {
|
|
|
29
29
|
* Mount the launcher into `target` (typically `document.body`). Everything the
|
|
30
30
|
* launcher draws — the **Feedback** pill, its **tuck** control, the edge **nub**
|
|
31
31
|
* and corner **hover-zone** that peek it back, and the first-visit **welcome**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* toast — lives inside one open Shadow DOM, so its styles are fully isolated
|
|
33
|
+
* from the host page and vice-versa. The host element is
|
|
34
34
|
* fixed-positioned and out of flow, so mounting never shifts the host page's
|
|
35
35
|
* layout. Only one launcher can exist at a time — an earlier one is removed first.
|
|
36
36
|
*
|
package/dist/overlay-styles.d.ts
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* vendored Signal tokens (copied from `packages/ui/src/tokens.css`, not imported —
|
|
7
7
|
* the SDK must not depend on `@fixback/ui` at runtime). Keep them in sync by value.
|
|
8
8
|
*/
|
|
9
|
-
export declare const OVERLAY_STYLES = "\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-border: #e0e6ec;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-bug: #e5484d;\n --fb-color-bug-bg: #fdecec;\n --fb-color-impr: #2f6fed;\n --fb-color-impr-bg: #eaf1fe;\n --fb-color-idea: #8b5cf6;\n --fb-color-idea-bg: #f2ecfe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n}\n\n* { box-sizing: border-box; }\n\n.fb-ov-panel {\n width: 300px;\n max-width: calc(100vw - 40px);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 13px;\n box-shadow: 0 18px 44px rgba(20, 40, 70, 0.2);\n overflow: hidden;\n}\n\n.fb-ov-head {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--fb-color-border-soft);\n}\n.fb-ov-mark-sq {\n width: 13px;\n height: 13px;\n border-radius: 4px;\n background: var(--fb-color-accent);\n flex: none;\n}\n.fb-ov-brand { font-size: 14px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-tier {\n flex: none;\n font-family: var(--fb-font-mono);\n font-size: 9.5px;\n color: var(--fb-color-faint);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 5px;\n padding: 1px 6px;\n}\n.fb-ov-close {\n margin-left: auto;\n border: 0;\n background: none;\n color: var(--fb-color-faint);\n font-size: 16px;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: 6px;\n}\n.fb-ov-close:hover { color: var(--fb-color-muted); background: #f4f7fa; }\n\n.fb-ov-tabs { display: flex; gap: 6px; padding: 12px 14px 6px; }\n.fb-ov-tab {\n flex: 1;\n text-align: center;\n font-size: 11px;\n font-weight: 600;\n padding: 6px;\n border-radius: 7px;\n border: 1px solid var(--fb-color-border);\n background: var(--fb-color-surface);\n color: var(--fb-color-muted);\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-tab:hover { border-color: #cfd8e2; }\n.fb-ov-tab.is-active[data-kind=\"bug\"] {\n background: var(--fb-color-bug-bg); color: var(--fb-color-bug); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"improvement\"] {\n background: var(--fb-color-impr-bg); color: var(--fb-color-impr); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"idea\"] {\n background: var(--fb-color-idea-bg); color: var(--fb-color-idea); border-color: transparent;\n}\n\n.fb-ov-mark {\n margin: 10px 14px;\n min-height: 52px;\n border-radius: 9px;\n border: 1px solid var(--fb-color-border-soft);\n background: repeating-linear-gradient(135deg, #f4f7fa, #f4f7fa 7px, #eaeff4 7px, #eaeff4 14px);\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n gap: 6px;\n padding: 10px 12px;\n}\n.fb-ov-selector {\n display: none;\n max-width: 100%;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n padding: 3px 8px;\n border-radius: 5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-ov-mark.has-element .fb-ov-selector { display: inline-block; }\n.fb-ov-mark-caption { font-size: 10px; color: var(--fb-color-faint); font-family: var(--fb-font-mono); }\n\n.fb-ov-comment {\n display: block;\n width: calc(100% - 28px);\n margin: 0 14px 10px;\n min-height: 62px;\n resize: vertical;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 9px;\n padding: 10px 11px;\n}\n.fb-ov-comment::placeholder { color: var(--fb-color-faint); }\n.fb-ov-comment:focus-visible { outline: 2px solid var(--fb-color-accent); outline-offset: 1px; }\n\n.fb-ov-status { padding: 0 14px; font-size: 11px; min-height: 0; }\n.fb-ov-status.is-error { color: var(--fb-color-bug); }\n\n.fb-ov-tools { display: flex; align-items: center; gap: 7px; padding: 8px 14px 14px; }\n.fb-ov-pickbtn,\n.fb-ov-capturebtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--fb-color-muted);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 8px;\n padding: 8px 11px;\n cursor: pointer;\n}\n.fb-ov-capturebtn { padding: 8px 9px; }\n.fb-ov-pickbtn:hover,\n.fb-ov-capturebtn:hover { border-color: #cfd8e2; }\n.fb-ov-pickbtn.is-active,\n.fb-ov-capturebtn.is-active,\n.fb-ov-capturebtn.is-attached {\n color: var(--fb-color-accent);\n border-color: var(--fb-color-accent);\n background: var(--fb-color-impr-bg);\n}\n.fb-ov-pickbtn__glyph { font-size: 14px; line-height: 1; }\n.fb-ov-icon { display: block; }\n\n.fb-ov-send {\n margin-left: auto;\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n border: 0;\n border-radius: 8px;\n padding: 9px 18px;\n cursor: pointer;\n}\n.fb-ov-send:hover { background: var(--fb-color-accent-hover); }\n.fb-ov-send:disabled { opacity: 0.6; cursor: default; }\n\n.fb-ov-done { display: none; padding: 24px 18px; text-align: center; }\n.fb-ov-panel.is-sent .fb-ov-form { display: none; }\n.fb-ov-panel.is-sent .fb-ov-done { display: block; }\n.fb-ov-done__check {\n width: 40px; height: 40px; margin: 0 auto 12px;\n border-radius: 50%;\n background: #e7f6ee; color: var(--fb-color-success);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; font-weight: 700;\n}\n.fb-ov-done__title { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-done__sub { font-size: 12px; color: var(--fb-color-muted); margin-top: 4px; }\n\n/* The panel is hidden while any full-screen marking layer is active. */\n.fb-ov-panel.is-marking { visibility: hidden; }\n\n.fb-ov-pick,\n.fb-ov-capture,\n.fb-ov-draw { position: fixed; inset: 0; z-index: 2147483002; display: none; }\n.fb-ov-pick.is-visible,\n.fb-ov-capture.is-visible,\n.fb-ov-draw.is-visible { display: block; }\n\n/* Element-pick highlight layer \u2014 visual only, never intercepts host events. */\n.fb-ov-pick { pointer-events: none; }\n.fb-ov-highlight {\n position: absolute;\n border: 2px dashed var(--fb-color-accent);\n border-radius: 6px;\n box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);\n transition: all 60ms ease;\n}\n.fb-ov-hint {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n font-family: var(--fb-font-mono);\n font-size: 11px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-ink);\n padding: 6px 12px;\n border-radius: 7px;\n box-shadow: 0 8px 20px rgba(20, 40, 70, 0.25);\n}\n\n/* Region-capture layer \u2014 a dim wash with a live selection box. */\n.fb-ov-capture { cursor: crosshair; }\n.fb-ov-capture-dim { position: absolute; inset: 0; background: rgba(15, 23, 32, 0.28); }\n.fb-ov-capture-sel {\n position: absolute;\n border: 2px solid var(--fb-color-accent);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.32);\n}\n\n/* Draw layer \u2014 the mark SVG, the region frame, the label input, and the toolbar. */\n.fb-ov-draw-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }\n.fb-ov-draw-frame {\n position: absolute;\n border: 1px solid rgba(47, 111, 237, 0.5);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.18);\n pointer-events: none;\n}\n.fb-ov-draw-text {\n position: absolute;\n display: none;\n font-family: var(--fb-font-sans);\n font-size: 15px;\n font-weight: 600;\n color: var(--fb-color-bug);\n background: rgba(255, 255, 255, 0.92);\n border: 1px dashed var(--fb-color-bug);\n border-radius: 4px;\n padding: 2px 6px;\n outline: none;\n z-index: 3;\n}\n.fb-ov-draw-toolbar {\n position: fixed;\n left: 50%;\n bottom: 26px;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 5px;\n background: var(--fb-color-ink);\n border-radius: 12px;\n padding: 7px;\n box-shadow: 0 16px 40px rgba(15, 40, 70, 0.4);\n}\n.fb-ov-drawtool {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-drawtool:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-drawtool.is-active { background: var(--fb-color-accent); color: var(--fb-color-on-emphasis); }\n.fb-ov-drawtool__t { font-weight: 700; font-size: 14px; line-height: 1; }\n.fb-ov-draw-divider { width: 1px; height: 22px; background: #2a343e; margin: 0 3px; }\n.fb-ov-draw-undo {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n}\n.fb-ov-draw-undo:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-draw-undo:disabled { opacity: 0.4; cursor: default; }\n.fb-ov-draw-cancel {\n height: 34px;\n padding: 0 12px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--fb-color-faint);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n}\n.fb-ov-draw-cancel:hover { color: #fff; }\n.fb-ov-draw-attach {\n height: 34px;\n padding: 0 15px;\n border: 0;\n border-radius: 8px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n}\n.fb-ov-draw-attach:hover { background: var(--fb-color-accent-hover); }\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ov-highlight { transition: none; }\n}\n";
|
|
9
|
+
export declare const OVERLAY_STYLES = "\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-border: #e0e6ec;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-bug: #e5484d;\n --fb-color-bug-bg: #fdecec;\n --fb-color-impr: #2f6fed;\n --fb-color-impr-bg: #eaf1fe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n}\n\n* { box-sizing: border-box; }\n\n.fb-ov-panel {\n width: 300px;\n max-width: calc(100vw - 40px);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 13px;\n box-shadow: 0 18px 44px rgba(20, 40, 70, 0.2);\n overflow: hidden;\n}\n\n.fb-ov-head {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--fb-color-border-soft);\n}\n.fb-ov-mark-sq {\n width: 13px;\n height: 13px;\n border-radius: 4px;\n background: var(--fb-color-accent);\n flex: none;\n}\n.fb-ov-brand { font-size: 14px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-tier {\n flex: none;\n font-family: var(--fb-font-mono);\n font-size: 9.5px;\n color: var(--fb-color-faint);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 5px;\n padding: 1px 6px;\n}\n.fb-ov-close {\n margin-left: auto;\n border: 0;\n background: none;\n color: var(--fb-color-faint);\n font-size: 16px;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: 6px;\n}\n.fb-ov-close:hover { color: var(--fb-color-muted); background: #f4f7fa; }\n\n.fb-ov-mark {\n margin: 12px 14px 10px;\n min-height: 52px;\n border-radius: 9px;\n border: 1px solid var(--fb-color-border-soft);\n background: repeating-linear-gradient(135deg, #f4f7fa, #f4f7fa 7px, #eaeff4 7px, #eaeff4 14px);\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n gap: 6px;\n padding: 10px 12px;\n}\n.fb-ov-selector {\n display: none;\n max-width: 100%;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n padding: 3px 8px;\n border-radius: 5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-ov-mark.has-element .fb-ov-selector { display: inline-block; }\n.fb-ov-mark-caption { font-size: 10px; color: var(--fb-color-faint); font-family: var(--fb-font-mono); }\n\n.fb-ov-comment {\n display: block;\n width: calc(100% - 28px);\n margin: 0 14px 10px;\n min-height: 62px;\n resize: vertical;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 9px;\n padding: 10px 11px;\n}\n.fb-ov-comment::placeholder { color: var(--fb-color-faint); }\n.fb-ov-comment:focus-visible { outline: 2px solid var(--fb-color-accent); outline-offset: 1px; }\n\n.fb-ov-status { padding: 0 14px; font-size: 11px; min-height: 0; }\n.fb-ov-status.is-error { color: var(--fb-color-bug); }\n\n.fb-ov-tools { display: flex; align-items: center; gap: 7px; padding: 8px 14px 14px; }\n.fb-ov-pickbtn,\n.fb-ov-capturebtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--fb-color-muted);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 8px;\n padding: 8px 11px;\n cursor: pointer;\n}\n.fb-ov-capturebtn { padding: 8px 9px; }\n.fb-ov-pickbtn:hover,\n.fb-ov-capturebtn:hover { border-color: #cfd8e2; }\n.fb-ov-pickbtn.is-active,\n.fb-ov-capturebtn.is-active,\n.fb-ov-capturebtn.is-attached {\n color: var(--fb-color-accent);\n border-color: var(--fb-color-accent);\n background: var(--fb-color-impr-bg);\n}\n.fb-ov-pickbtn__glyph { font-size: 14px; line-height: 1; }\n.fb-ov-icon { display: block; }\n\n.fb-ov-send {\n margin-left: auto;\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n border: 0;\n border-radius: 8px;\n padding: 9px 18px;\n cursor: pointer;\n}\n.fb-ov-send:hover { background: var(--fb-color-accent-hover); }\n.fb-ov-send:disabled { opacity: 0.6; cursor: default; }\n\n.fb-ov-done { display: none; padding: 24px 18px; text-align: center; }\n.fb-ov-panel.is-sent .fb-ov-form { display: none; }\n.fb-ov-panel.is-sent .fb-ov-done { display: block; }\n.fb-ov-done__check {\n width: 40px; height: 40px; margin: 0 auto 12px;\n border-radius: 50%;\n background: #e7f6ee; color: var(--fb-color-success);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; font-weight: 700;\n}\n.fb-ov-done__title { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-done__sub { font-size: 12px; color: var(--fb-color-muted); margin-top: 4px; }\n\n/* The panel is hidden while any full-screen marking layer is active. */\n.fb-ov-panel.is-marking { visibility: hidden; }\n\n.fb-ov-pick,\n.fb-ov-capture,\n.fb-ov-draw { position: fixed; inset: 0; z-index: 2147483002; display: none; }\n.fb-ov-pick.is-visible,\n.fb-ov-capture.is-visible,\n.fb-ov-draw.is-visible { display: block; }\n\n/* Element-pick highlight layer \u2014 visual only, never intercepts host events. */\n.fb-ov-pick { pointer-events: none; }\n.fb-ov-highlight {\n position: absolute;\n border: 2px dashed var(--fb-color-accent);\n border-radius: 6px;\n box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);\n transition: all 60ms ease;\n}\n.fb-ov-hint {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n font-family: var(--fb-font-mono);\n font-size: 11px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-ink);\n padding: 6px 12px;\n border-radius: 7px;\n box-shadow: 0 8px 20px rgba(20, 40, 70, 0.25);\n}\n\n/* Region-capture layer \u2014 a dim wash with a live selection box. */\n.fb-ov-capture { cursor: crosshair; }\n.fb-ov-capture-dim { position: absolute; inset: 0; background: rgba(15, 23, 32, 0.28); }\n.fb-ov-capture-sel {\n position: absolute;\n border: 2px solid var(--fb-color-accent);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.32);\n}\n\n/* Draw layer \u2014 the mark SVG, the region frame, the label input, and the toolbar. */\n.fb-ov-draw-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }\n.fb-ov-draw-frame {\n position: absolute;\n border: 1px solid rgba(47, 111, 237, 0.5);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.18);\n pointer-events: none;\n}\n.fb-ov-draw-text {\n position: absolute;\n display: none;\n font-family: var(--fb-font-sans);\n font-size: 15px;\n font-weight: 600;\n color: var(--fb-color-bug);\n background: rgba(255, 255, 255, 0.92);\n border: 1px dashed var(--fb-color-bug);\n border-radius: 4px;\n padding: 2px 6px;\n outline: none;\n z-index: 3;\n}\n.fb-ov-draw-toolbar {\n position: fixed;\n left: 50%;\n bottom: 26px;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 5px;\n background: var(--fb-color-ink);\n border-radius: 12px;\n padding: 7px;\n box-shadow: 0 16px 40px rgba(15, 40, 70, 0.4);\n}\n.fb-ov-drawtool {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-drawtool:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-drawtool.is-active { background: var(--fb-color-accent); color: var(--fb-color-on-emphasis); }\n.fb-ov-drawtool__t { font-weight: 700; font-size: 14px; line-height: 1; }\n.fb-ov-draw-divider { width: 1px; height: 22px; background: #2a343e; margin: 0 3px; }\n.fb-ov-draw-undo {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n}\n.fb-ov-draw-undo:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-draw-undo:disabled { opacity: 0.4; cursor: default; }\n.fb-ov-draw-cancel {\n height: 34px;\n padding: 0 12px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--fb-color-faint);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n}\n.fb-ov-draw-cancel:hover { color: #fff; }\n.fb-ov-draw-attach {\n height: 34px;\n padding: 0 15px;\n border: 0;\n border-radius: 8px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n}\n.fb-ov-draw-attach:hover { background: var(--fb-color-accent-hover); }\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ov-highlight { transition: none; }\n}\n";
|
package/dist/overlay.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { IdentityInputs, ProjectGate, ReporterTier } from "./boot";
|
|
2
2
|
import type { Breadcrumb } from "./breadcrumbs";
|
|
3
|
+
import type { ReporterDisplay } from "./invite";
|
|
3
4
|
import { type ElementPicker, type ElementPickerOptions } from "./element-picker";
|
|
5
|
+
import type { ReplaySource } from "./replay";
|
|
4
6
|
import { type BeforeSend } from "./scrub";
|
|
5
7
|
import { type Capture, type CaptureOptions } from "./screenshot";
|
|
6
8
|
import { type SubmitInput, type SubmitResult } from "./submit";
|
|
@@ -38,6 +40,14 @@ export interface OverlayConfig {
|
|
|
38
40
|
*/
|
|
39
41
|
readonly tier?: ReporterTier | null;
|
|
40
42
|
readonly sdkVersion?: string;
|
|
43
|
+
/** The host app's Release (#117, ADR-0024), validated by `init`; rides in the environment. */
|
|
44
|
+
readonly release?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The Reporter's self-provided display name / email (spec §F), when the SDK holds
|
|
47
|
+
* them (from a redemption in this session or a prior one). They ride along with the
|
|
48
|
+
* report so a Member sees who filed it — display only, never a trust signal.
|
|
49
|
+
*/
|
|
50
|
+
readonly display?: ReporterDisplay;
|
|
41
51
|
/** Where to mount the overlay host. Defaults to `document.body`. */
|
|
42
52
|
readonly target?: HTMLElement;
|
|
43
53
|
readonly doc?: Document;
|
|
@@ -45,6 +55,8 @@ export interface OverlayConfig {
|
|
|
45
55
|
readonly deps?: Partial<OverlayDeps>;
|
|
46
56
|
/** The trace buffer whose snapshot rides on each report (spec §C). */
|
|
47
57
|
readonly buffer?: BreadcrumbSource | null;
|
|
58
|
+
/** The replay recorder whose buffered window rides on each report (ADR-0024). */
|
|
59
|
+
readonly replay?: ReplaySource | null;
|
|
48
60
|
/** Per-project client scrub hook, run at the `beforeSend` choke point. */
|
|
49
61
|
readonly beforeSend?: BeforeSend;
|
|
50
62
|
/** Run the built-in default scrubbers. Defaults to `true` (private-by-default). */
|
package/dist/replay.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type eventWithTime } from "@rrweb/types";
|
|
2
|
+
/**
|
|
3
|
+
* Buffered session replay — the rrweb capture half of issue #189 (ADR-0024).
|
|
4
|
+
*
|
|
5
|
+
* The recorder runs continuously from mount ("the moment before matters",
|
|
6
|
+
* VISION principle 4) but never streams: rrweb events accumulate in **two
|
|
7
|
+
* checkout-delimited buckets**, and every {@link REPLAY_CHECKOUT_MS} rrweb takes
|
|
8
|
+
* a fresh full DOM snapshot (a "checkout") that rotates the buckets. A report's
|
|
9
|
+
* {@link ReplayRecorder.snapshot} is the concatenation of both buckets, so it
|
|
10
|
+
* always starts at a full snapshot and carries between one and two checkout
|
|
11
|
+
* intervals of lead-up — the bounded-window posture the Trace already follows,
|
|
12
|
+
* never an unbounded full-session stream.
|
|
13
|
+
*
|
|
14
|
+
* Masking is applied by rrweb **at serialization time, inside the page** — the
|
|
15
|
+
* same client-side, private-by-default boundary as the screenshot and the Trace
|
|
16
|
+
* (spec 0003 §C, VISION §5): every input is masked (`maskAllInputs`) and, by
|
|
17
|
+
* default, **all text** is masked (`maskTextSelector: "*"`), so the recording
|
|
18
|
+
* that leaves the browser carries layout and interaction, not content. A
|
|
19
|
+
* Project that accepts the trade-off can narrow the text mask via
|
|
20
|
+
* {@link ReplayOptions.maskTextSelector} (rrweb 2.1 has no per-element unmask
|
|
21
|
+
* selector, so narrowing the mask selector is the escape hatch). The SDK's own
|
|
22
|
+
* chrome — launcher and overlay hosts — is blocked from the recording entirely,
|
|
23
|
+
* mirroring how the screenshot strips it.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* How often rrweb takes a checkout full snapshot, which is also the bucket
|
|
27
|
+
* rotation period: a snapshot carries 1–2 of these intervals of lead-up
|
|
28
|
+
* (30–60 s), Sentry's buffered-replay ballpark.
|
|
29
|
+
*/
|
|
30
|
+
export declare const REPLAY_CHECKOUT_MS = 30000;
|
|
31
|
+
/**
|
|
32
|
+
* The SDK's own host elements (dom.ts markers), excluded from the recording the
|
|
33
|
+
* way the screenshot's clone walk strips them.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FIXBACK_BLOCK_SELECTOR = "[data-fixback-root],[data-fixback-overlay]";
|
|
36
|
+
/**
|
|
37
|
+
* Media elements blocked from the recording by default (Sentry's `blockAllMedia`
|
|
38
|
+
* posture, adopted): text masking cannot redact what an image shows, so media
|
|
39
|
+
* renders as a same-size placeholder in replay unless a Project opts out via
|
|
40
|
+
* {@link ReplayOptions.blockMedia}.
|
|
41
|
+
*/
|
|
42
|
+
export declare const FIXBACK_MEDIA_SELECTOR = "img,image,svg,video,object,picture,embed,map,audio";
|
|
43
|
+
/** Tuning for the replay recorder. All optional; the defaults are the contract. */
|
|
44
|
+
export interface ReplayOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Mask **all** text content (default `true`, private-by-default). `false`
|
|
47
|
+
* records text in the clear — inputs stay masked regardless.
|
|
48
|
+
*/
|
|
49
|
+
readonly maskText?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Which elements' text rrweb masks when {@link maskText} is on. Defaults to
|
|
52
|
+
* `"*"` (everything); narrow it to unmask known-safe regions.
|
|
53
|
+
*/
|
|
54
|
+
readonly maskTextSelector?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Block media content — images, video, audio, SVG — from the recording
|
|
57
|
+
* (default `true`, {@link FIXBACK_MEDIA_SELECTOR}): what a picture shows
|
|
58
|
+
* cannot be text-masked, so media is a placeholder in replay. `false` records
|
|
59
|
+
* it (an internal/beta Project trading privacy for fidelity, VISION §5).
|
|
60
|
+
*/
|
|
61
|
+
readonly blockMedia?: boolean;
|
|
62
|
+
/** Extra elements to exclude from the recording, alongside the SDK's own chrome. */
|
|
63
|
+
readonly blockSelector?: string;
|
|
64
|
+
}
|
|
65
|
+
/** One rrweb event as recorded; the wire payload is an array of these. */
|
|
66
|
+
export type ReplayEvent = eventWithTime;
|
|
67
|
+
/** The buffered window a report ships: rrweb events starting at a full snapshot. */
|
|
68
|
+
export interface ReplaySnapshot {
|
|
69
|
+
readonly events: readonly ReplayEvent[];
|
|
70
|
+
}
|
|
71
|
+
/** A read-only view of the recorder the report surfaces attach from. */
|
|
72
|
+
export interface ReplaySource {
|
|
73
|
+
/** The current buffered window, or `null` when nothing replayable exists. */
|
|
74
|
+
snapshot(): ReplaySnapshot | null;
|
|
75
|
+
}
|
|
76
|
+
/** A running replay recorder. */
|
|
77
|
+
export interface ReplayRecorder extends ReplaySource {
|
|
78
|
+
/** Stop recording and release the buffers. Safe to call more than once. */
|
|
79
|
+
stop(): void;
|
|
80
|
+
}
|
|
81
|
+
/** The slice of rrweb's record options the recorder sets. */
|
|
82
|
+
export interface ReplayRecordOptions {
|
|
83
|
+
readonly emit: (event: ReplayEvent, isCheckout?: boolean) => void;
|
|
84
|
+
readonly checkoutEveryNms?: number;
|
|
85
|
+
readonly maskAllInputs?: boolean;
|
|
86
|
+
readonly maskTextSelector?: string;
|
|
87
|
+
readonly blockSelector?: string;
|
|
88
|
+
readonly slimDOMOptions?: "all";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The slice of rrweb's `record` the recorder consumes; injectable for tests.
|
|
92
|
+
* (A concrete signature — rrweb's own is generic, which mocks can't satisfy.)
|
|
93
|
+
*/
|
|
94
|
+
export type RecordFn = (options: ReplayRecordOptions) => (() => void) | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Start the buffered replay recorder. Never throws: an rrweb failure (an exotic
|
|
97
|
+
* DOM, an unsupported environment) degrades to a recorder whose `snapshot()` is
|
|
98
|
+
* `null`, so a Fixback problem never surfaces on the host page and a report
|
|
99
|
+
* simply ships without replay.
|
|
100
|
+
*/
|
|
101
|
+
export declare function startReplayRecorder(options?: ReplayOptions, recordFn?: RecordFn): ReplayRecorder;
|
package/dist/report.d.ts
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { type Annotation, type Mark, type Rect } from "./annotation";
|
|
12
12
|
import type { Breadcrumb } from "./breadcrumbs";
|
|
13
|
-
/** The Kind a Reporter tags a report with. Mirrors the server's `ISSUE_KINDS`. */
|
|
14
|
-
export type IssueKind = "bug" | "improvement" | "idea";
|
|
15
13
|
/**
|
|
16
14
|
* Where a Feedback came from — a human in the overlay (`reporter`, the default) or
|
|
17
15
|
* the SDK's automatic error capture (`auto`). Mirrors the server's `FEEDBACK_SOURCES`;
|
|
@@ -42,7 +40,34 @@ export interface CaptureEnvironment {
|
|
|
42
40
|
readonly viewportHeight?: number;
|
|
43
41
|
readonly browser?: string;
|
|
44
42
|
readonly sdkVersion?: string;
|
|
43
|
+
/**
|
|
44
|
+
* The host app's **Release** — the build identifier the builder configured at
|
|
45
|
+
* `init` (#117, ADR-0024). Uploaded sourcemaps are keyed by it, so the server
|
|
46
|
+
* can symbolicate this session's stack traces against the exact build that
|
|
47
|
+
* produced them. Omitted when the Project doesn't set one.
|
|
48
|
+
*/
|
|
49
|
+
readonly release?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One structured frame of a captured stack trace (#117, ADR-0024), top of stack
|
|
53
|
+
* first — the server's `errorFrames` wire shape (mirrors `@fixback/shared`'s
|
|
54
|
+
* `CapturedStackFrame` by value, per this file's vendoring rule). `file` is the
|
|
55
|
+
* scrubbed script URL; `line`/`column` are 1-based as browsers report them.
|
|
56
|
+
*/
|
|
57
|
+
export interface CapturedFrame {
|
|
58
|
+
readonly file: string;
|
|
59
|
+
readonly line: number;
|
|
60
|
+
readonly column: number | null;
|
|
61
|
+
readonly function: string | null;
|
|
45
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate + trim a builder-supplied release (#117, ADR-0024), mirroring the
|
|
65
|
+
* server's rules by value: 1–100 visible characters, no whitespace, control
|
|
66
|
+
* characters, or path separators, and not a reserved name. Returns `undefined`
|
|
67
|
+
* for an invalid value — the SDK drops it (with a warning at `init`) rather than
|
|
68
|
+
* shipping a value the server would discard.
|
|
69
|
+
*/
|
|
70
|
+
export declare function normaliseRelease(value: string): string | undefined;
|
|
46
71
|
/**
|
|
47
72
|
* The JSON content of a feedback submission — the object serialised into the
|
|
48
73
|
* multipart `payload` part next to the `key` and identity evidence. Every field
|
|
@@ -52,7 +77,15 @@ export interface CaptureEnvironment {
|
|
|
52
77
|
*/
|
|
53
78
|
export interface ReportContent {
|
|
54
79
|
readonly comment?: string;
|
|
55
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The Reporter's self-provided display **name / email** (spec §F), captured in the
|
|
82
|
+
* invite onboarding step and riding along with every submission so a Member sees
|
|
83
|
+
* who reported an Issue. **Display only, never a trust signal** — the server derives
|
|
84
|
+
* the tier from identity evidence alone and ignores these. Omitted when the Reporter
|
|
85
|
+
* gave none (a Public/anonymous visitor).
|
|
86
|
+
*/
|
|
87
|
+
readonly reporterName?: string;
|
|
88
|
+
readonly reporterEmail?: string;
|
|
56
89
|
readonly url?: string;
|
|
57
90
|
readonly environment?: CaptureEnvironment;
|
|
58
91
|
readonly annotation?: Annotation;
|
|
@@ -67,6 +100,13 @@ export interface ReportContent {
|
|
|
67
100
|
readonly errorSignature?: string;
|
|
68
101
|
/** For `source: auto` only — the running occurrence count within the session (spec §E). */
|
|
69
102
|
readonly occurrences?: number;
|
|
103
|
+
/**
|
|
104
|
+
* For `source: auto` only — the captured error's parsed stack frames (#117,
|
|
105
|
+
* ADR-0024), top of stack first, capped client-side. The analysis worker
|
|
106
|
+
* matches them against the release's uploaded sourcemaps to write the Issue's
|
|
107
|
+
* Code-area pointer.
|
|
108
|
+
*/
|
|
109
|
+
readonly errorFrames?: readonly CapturedFrame[];
|
|
70
110
|
}
|
|
71
111
|
/**
|
|
72
112
|
* What the overlay hands to {@link assembleContent} when the Reporter sends. The
|
|
@@ -74,7 +114,6 @@ export interface ReportContent {
|
|
|
74
114
|
* folds whatever is present into the structured {@link Annotation}.
|
|
75
115
|
*/
|
|
76
116
|
export interface ReportDraft {
|
|
77
|
-
readonly kind?: IssueKind;
|
|
78
117
|
readonly comment?: string;
|
|
79
118
|
readonly element?: SelectedElement;
|
|
80
119
|
readonly region?: Rect;
|
|
@@ -82,6 +121,9 @@ export interface ReportDraft {
|
|
|
82
121
|
readonly url?: string;
|
|
83
122
|
readonly environment?: CaptureEnvironment;
|
|
84
123
|
readonly trace?: readonly Breadcrumb[];
|
|
124
|
+
/** The Reporter's self-provided display name / email (spec §F), if the SDK holds them. */
|
|
125
|
+
readonly reporterName?: string;
|
|
126
|
+
readonly reporterEmail?: string;
|
|
85
127
|
}
|
|
86
128
|
/**
|
|
87
129
|
* Read the capture environment off a window: the viewport size, the browser's
|
|
@@ -89,7 +131,7 @@ export interface ReportDraft {
|
|
|
89
131
|
* positive number (a headless/zero viewport records nothing rather than an
|
|
90
132
|
* invalid `0`, which the server would reject).
|
|
91
133
|
*/
|
|
92
|
-
export declare function collectEnvironment(win: Window, sdkVersion: string): CaptureEnvironment;
|
|
134
|
+
export declare function collectEnvironment(win: Window, sdkVersion: string, release?: string): CaptureEnvironment;
|
|
93
135
|
/**
|
|
94
136
|
* Assemble the ingest content from what the Reporter composed. Empty pieces are
|
|
95
137
|
* dropped rather than sent as blanks: a whitespace-only comment, an absent
|
package/dist/styles.d.ts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* **Feedback** pill that **hover-peeks**, can be **tucked away** (sliding off
|
|
12
12
|
* behind an edge nub, with a corner hover-zone to bring it back), a first-visit
|
|
13
13
|
* **welcome toast**, and a **reduce-motion** mode that stills the pulse. Every
|
|
14
|
-
* piece — pill, nub, corner zone,
|
|
14
|
+
* piece — pill, nub, corner zone, welcome toast — lives in this one Shadow DOM.
|
|
15
15
|
*
|
|
16
16
|
* Motion is driven by two host-element attributes the mount toggles:
|
|
17
17
|
* `data-fb-hidden` (tucked away) and `data-fb-peeking` (peeked back on hover);
|
|
18
18
|
* `data-fb-reduce-motion` (or the OS `prefers-reduced-motion`) stills it all.
|
|
19
19
|
*/
|
|
20
|
-
export declare const LAUNCHER_STYLES = "\n:host {\n /* Vendored Signal tokens (packages/ui/src/tokens.css). */\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-text: #0f1720;\n /* Inverse (dark) surface for the toasts \u2014 Signal --fb-ink-900. */\n --fb-color-surface-inverse: #0f1720;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n}\n\n/* The pill: Feedback button + divider + tuck control, anchored bottom-right. */\n.fb-launcher {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 2;\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 40px;\n margin: 0;\n border-radius: 999px;\n background: var(--fb-color-accent);\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 1px 2px rgba(15, 23, 32, 0.12);\n transform: translateX(0);\n transition:\n transform 340ms cubic-bezier(0.2, 0.8, 0.3, 1),\n background-color 120ms ease;\n}\n\n.fb-launcher:hover {\n background: var(--fb-color-accent-hover);\n}\n\n.fb-launcher__button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n height: 100%;\n margin: 0;\n padding: 0 6px 0 16px;\n border: 0;\n background: transparent;\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.01em;\n cursor: pointer;\n}\n\n.fb-launcher__button:focus-visible,\n.fb-launcher__tuck:focus-visible {\n outline: 2px solid var(--fb-color-on-emphasis);\n outline-offset: -3px;\n border-radius: 999px;\n}\n\n.fb-launcher__icon {\n display: block;\n flex: none;\n width: 16px;\n height: 16px;\n}\n\n.fb-launcher__label {\n white-space: nowrap;\n}\n\n.fb-launcher__divider {\n width: 1px;\n margin: 9px 0;\n flex: none;\n background: rgba(255, 255, 255, 0.28);\n}\n\n.fb-launcher__tuck {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 32px;\n height: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n color: rgba(255, 255, 255, 0.82);\n cursor: pointer;\n}\n\n.fb-launcher__tuck:hover {\n color: var(--fb-color-on-emphasis);\n}\n\n.fb-launcher__tuck-icon {\n display: block;\n width: 14px;\n height: 14px;\n}\n\n/* The corner hover-zone that peeks a tucked launcher back \u2014 inert until tucked,\n * so it never swallows the host page's own bottom-right clicks. */\n.fb-launcher__peekzone {\n position: fixed;\n right: 0;\n bottom: 0;\n z-index: 1;\n width: 160px;\n height: 160px;\n pointer-events: none;\n}\n\n/* The edge nub: the visible re-reveal affordance, hidden until tucked. */\n.fb-launcher__nub {\n position: fixed;\n right: 0;\n bottom: 22px;\n z-index: 2;\n width: 13px;\n height: 42px;\n border-radius: 9px 0 0 9px;\n background: var(--fb-color-accent);\n box-shadow: -6px 5px 18px rgba(47, 111, 237, 0.4);\n cursor: pointer;\n opacity: 0;\n transform: translateX(10px);\n pointer-events: none;\n transition:\n opacity 220ms ease,\n transform 220ms ease;\n}\n\n/* Tucked and not peeking: slide the pill off, reveal the nub, arm the zone. */\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher {\n transform: translateX(calc(100% + 30px));\n}\n\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher__nub {\n opacity: 1;\n transform: none;\n pointer-events: auto;\n}\n\n:host([data-fb-hidden]) .fb-launcher__peekzone {\n pointer-events: auto;\n}\n\n/*
|
|
20
|
+
export declare const LAUNCHER_STYLES = "\n:host {\n /* Vendored Signal tokens (packages/ui/src/tokens.css). */\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-text: #0f1720;\n /* Inverse (dark) surface for the toasts \u2014 Signal --fb-ink-900. */\n --fb-color-surface-inverse: #0f1720;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n}\n\n/* The pill: Feedback button + divider + tuck control, anchored bottom-right. */\n.fb-launcher {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 2;\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 40px;\n margin: 0;\n border-radius: 999px;\n background: var(--fb-color-accent);\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 1px 2px rgba(15, 23, 32, 0.12);\n transform: translateX(0);\n transition:\n transform 340ms cubic-bezier(0.2, 0.8, 0.3, 1),\n background-color 120ms ease;\n}\n\n.fb-launcher:hover {\n background: var(--fb-color-accent-hover);\n}\n\n.fb-launcher__button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n height: 100%;\n margin: 0;\n padding: 0 6px 0 16px;\n border: 0;\n background: transparent;\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.01em;\n cursor: pointer;\n}\n\n.fb-launcher__button:focus-visible,\n.fb-launcher__tuck:focus-visible {\n outline: 2px solid var(--fb-color-on-emphasis);\n outline-offset: -3px;\n border-radius: 999px;\n}\n\n.fb-launcher__icon {\n display: block;\n flex: none;\n width: 16px;\n height: 16px;\n}\n\n.fb-launcher__label {\n white-space: nowrap;\n}\n\n.fb-launcher__divider {\n width: 1px;\n margin: 9px 0;\n flex: none;\n background: rgba(255, 255, 255, 0.28);\n}\n\n.fb-launcher__tuck {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 32px;\n height: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n color: rgba(255, 255, 255, 0.82);\n cursor: pointer;\n}\n\n.fb-launcher__tuck:hover {\n color: var(--fb-color-on-emphasis);\n}\n\n.fb-launcher__tuck-icon {\n display: block;\n width: 14px;\n height: 14px;\n transition: transform 340ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n\n/* Tucked away: mirror the chevron so it points left \u2014 back into view. */\n:host([data-fb-hidden]) .fb-launcher__tuck-icon {\n transform: scaleX(-1);\n}\n\n/* The corner hover-zone that peeks a tucked launcher back \u2014 inert until tucked,\n * so it never swallows the host page's own bottom-right clicks. */\n.fb-launcher__peekzone {\n position: fixed;\n right: 0;\n bottom: 0;\n z-index: 1;\n width: 160px;\n height: 160px;\n pointer-events: none;\n}\n\n/* The edge nub: the visible re-reveal affordance, hidden until tucked. */\n.fb-launcher__nub {\n position: fixed;\n right: 0;\n bottom: 22px;\n z-index: 2;\n width: 13px;\n height: 42px;\n border-radius: 9px 0 0 9px;\n background: var(--fb-color-accent);\n box-shadow: -6px 5px 18px rgba(47, 111, 237, 0.4);\n cursor: pointer;\n opacity: 0;\n transform: translateX(10px);\n pointer-events: none;\n transition:\n opacity 220ms ease,\n transform 220ms ease;\n}\n\n/* Tucked and not peeking: slide the pill off, reveal the nub, arm the zone. */\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher {\n transform: translateX(calc(100% + 30px));\n}\n\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher__nub {\n opacity: 1;\n transform: none;\n pointer-events: auto;\n}\n\n:host([data-fb-hidden]) .fb-launcher__peekzone {\n pointer-events: auto;\n}\n\n/* The first-visit welcome toast, above the pill. */\n.fb-launcher__welcome {\n position: fixed;\n right: 20px;\n bottom: 70px;\n z-index: 5;\n box-sizing: border-box;\n max-width: 210px;\n padding: 9px 13px;\n color: #fff;\n font-size: 12px;\n line-height: 1.45;\n background: var(--fb-color-surface-inverse);\n border-radius: 10px;\n box-shadow: 0 12px 30px rgba(15, 40, 70, 0.32);\n animation: fbToast 300ms ease both;\n}\n\n.fb-launcher__welcome strong {\n color: #8fc0ff;\n font-weight: 600;\n}\n\n/* The pulse that draws the eye while the welcome shows. */\n.fb-launcher--pulse {\n animation: fbPulse 1.8s ease-in-out 2;\n}\n\n@keyframes fbPulse {\n 0%,\n 100% {\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 0 0 0 rgba(47, 111, 237, 0.45);\n }\n 50% {\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 0 0 12px rgba(47, 111, 237, 0);\n }\n}\n\n@keyframes fbToast {\n from {\n opacity: 0;\n transform: translateY(14px);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n\n/* Reduce motion \u2014 the explicit opt-in and the OS setting both still it all. */\n:host([data-fb-reduce-motion]) .fb-launcher,\n:host([data-fb-reduce-motion]) .fb-launcher__nub,\n:host([data-fb-reduce-motion]) .fb-launcher__tuck-icon {\n transition: none;\n}\n\n:host([data-fb-reduce-motion]) .fb-launcher--pulse,\n:host([data-fb-reduce-motion]) .fb-launcher__welcome {\n animation: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-launcher,\n .fb-launcher__nub,\n .fb-launcher__tuck-icon {\n transition: none;\n }\n .fb-launcher--pulse,\n .fb-launcher__welcome {\n animation: none;\n }\n}\n";
|
package/dist/submit.d.ts
CHANGED
|
@@ -13,12 +13,20 @@ export interface SubmitScreenshot {
|
|
|
13
13
|
readonly blob: Blob;
|
|
14
14
|
readonly filename?: string;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The buffered replay window to attach, if any — the rrweb events a
|
|
18
|
+
* `ReplaySnapshot` holds (replay.ts). Serialized into its own binary part.
|
|
19
|
+
*/
|
|
20
|
+
export interface SubmitReplay {
|
|
21
|
+
readonly events: readonly unknown[];
|
|
22
|
+
}
|
|
16
23
|
/** Everything a single feedback submission carries. */
|
|
17
24
|
export interface SubmitInput {
|
|
18
25
|
readonly key: string;
|
|
19
26
|
readonly identity?: IdentityInputs;
|
|
20
27
|
readonly content: ReportContent;
|
|
21
28
|
readonly screenshot?: SubmitScreenshot | null;
|
|
29
|
+
readonly replay?: SubmitReplay | null;
|
|
22
30
|
}
|
|
23
31
|
/** The outcome of a submission — never an exception. */
|
|
24
32
|
export type SubmitResult = {
|
package/dist/version.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fixback/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Fixback capture SDK — a boot-gated, self-isolating on-page feedback launcher.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
+
"@rrweb/record": "^2.1.1",
|
|
45
|
+
"@rrweb/types": "^2.1.1",
|
|
44
46
|
"modern-screenshot": "^4.7.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|