@fixback/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.d.ts CHANGED
@@ -6,5 +6,15 @@
6
6
  * token values are **vendored** Signal design tokens — copied from
7
7
  * `packages/ui/src/tokens.css` rather than imported, because the SDK must not
8
8
  * depend on `@fixback/ui` at runtime (ticket #47). Keep them in sync by value.
9
+ *
10
+ * The launcher is the prototype's polished pill (spec 0003 §A): a bottom-right
11
+ * **Feedback** pill that **hover-peeks**, can be **tucked away** (sliding off
12
+ * behind an edge nub, with a corner hover-zone to bring it back), a first-visit
13
+ * **welcome toast**, and a **reduce-motion** mode that stills the pulse. Every
14
+ * piece — pill, nub, corner zone, toasts — lives in this one Shadow DOM.
15
+ *
16
+ * Motion is driven by two host-element attributes the mount toggles:
17
+ * `data-fb-hidden` (tucked away) and `data-fb-peeking` (peeked back on hover);
18
+ * `data-fb-reduce-motion` (or the OS `prefers-reduced-motion`) stills it all.
9
19
  */
10
- 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 --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.fb-launcher {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n height: 40px;\n margin: 0;\n padding: 0 16px;\n border: 0;\n border-radius: 999px;\n background: var(--fb-color-accent);\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 box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 1px 2px rgba(15, 23, 32, 0.12);\n transition:\n background-color 120ms ease,\n transform 120ms ease;\n}\n\n.fb-launcher:hover {\n background: var(--fb-color-accent-hover);\n}\n\n.fb-launcher:active {\n transform: translateY(1px);\n}\n\n.fb-launcher:focus-visible {\n outline: 2px solid var(--fb-color-accent);\n outline-offset: 2px;\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@media (prefers-reduced-motion: reduce) {\n .fb-launcher {\n transition: none;\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}\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/* Toasts: the first-visit welcome (above the pill) and the tuck-away hint. */\n.fb-launcher__welcome,\n.fb-launcher__hint {\n position: fixed;\n right: 20px;\n z-index: 5;\n box-sizing: border-box;\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 {\n bottom: 70px;\n max-width: 210px;\n padding: 9px 13px;\n}\n\n.fb-launcher__welcome strong {\n color: #8fc0ff;\n font-weight: 600;\n}\n\n.fb-launcher__hint {\n bottom: 20px;\n max-width: 232px;\n padding: 10px 13px;\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 transition: none;\n}\n\n:host([data-fb-reduce-motion]) .fb-launcher--pulse,\n:host([data-fb-reduce-motion]) .fb-launcher__welcome,\n:host([data-fb-reduce-motion]) .fb-launcher__hint {\n animation: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-launcher,\n .fb-launcher__nub {\n transition: none;\n }\n .fb-launcher--pulse,\n .fb-launcher__welcome,\n .fb-launcher__hint {\n animation: none;\n }\n}\n";
package/dist/submit.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AutoReportBackoff } from "./auto-report-backoff";
1
2
  import type { IdentityInputs, ReporterTier } from "./boot";
2
3
  import type { ReportContent } from "./report";
3
4
  /** What ingest returns for an accepted submission (vendored server shape). */
@@ -27,13 +28,30 @@ export type SubmitResult = {
27
28
  readonly ok: false;
28
29
  readonly reason: "unreachable" | "refused";
29
30
  readonly status?: number;
31
+ } | {
32
+ /**
33
+ * A `source: auto` report shed under ingest backpressure (spec §E/§H) — either
34
+ * held locally because the window is still open, or answered `429` by ingest,
35
+ * which opened/extended the window. Carries the seconds left to wait. Manual
36
+ * reports never produce this.
37
+ */
38
+ readonly ok: false;
39
+ readonly reason: "backpressure";
40
+ readonly retryAfterSeconds: number;
30
41
  };
31
42
  /** Join an API base URL with the feedback path, tolerating a trailing slash. */
32
43
  export declare function feedbackEndpoint(apiUrl: string): string;
33
44
  /**
34
45
  * Submit a report to ingest. Assembles the `payload` JSON (key + identity +
35
- * content) and the optional `screenshot` file into a `FormData`, posts it, and
36
- * resolves to the recorded Feedback on success or a named failure otherwise. The
37
- * `fetchImpl` seam exists purely so the call is testable.
46
+ * content, with an explicit `source` stamped) and the optional full masked
47
+ * `screenshot` file into a `FormData`, posts it, and resolves to the recorded
48
+ * Feedback on success or a named failure otherwise.
49
+ *
50
+ * A `source: auto` report first consults the shared backpressure window: while it
51
+ * is open the report is dropped without a request (spec §E/§H). Ingest's `429` +
52
+ * `Retry-After` opens/extends that window (default 60 s if the header is absent).
53
+ * Manual reports (`source: reporter`, the default) never consult the window and a
54
+ * non-2xx for them stays a plain refusal. The `fetchImpl` and `backoff` seams exist
55
+ * purely so the call is testable.
38
56
  */
39
- export declare function submitReport(apiUrl: string, input: SubmitInput, fetchImpl?: typeof fetch): Promise<SubmitResult>;
57
+ export declare function submitReport(apiUrl: string, input: SubmitInput, fetchImpl?: typeof fetch, backoff?: AutoReportBackoff): Promise<SubmitResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixback/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",