@omg-dev/sdk 0.4.30 → 0.4.31

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.
@@ -1,114 +1,85 @@
1
- import { t as VibesFeedback } from "./VibesFeedback-BF2Vf6FK.mjs";
2
1
  import { useId, useState } from "react";
3
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
3
  //#region src/brand/OmgBadge.tsx
5
4
  const BRAND = "#FF5530";
6
5
  const Z_BADGE = 2147482e3;
7
6
  const Z_DIALOG = 2147483100;
8
- const FEEDBACK_OPEN_EVENT = "vibes:feedback:open";
9
- function feedbackBundled() {
10
- try {
11
- return typeof __VIBES_BRAND_FEEDBACK__ === "undefined" ? true : __VIBES_BRAND_FEEDBACK__;
12
- } catch {
13
- return true;
14
- }
15
- }
16
7
  function OmgBadge({ href = "https://omg.dev", label = "What is omg?" }) {
17
8
  const [open, setOpen] = useState(false);
18
9
  const titleId = useId();
19
10
  const descriptionId = useId();
20
11
  const markId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
21
- function summonFeedback() {
22
- setOpen(false);
23
- if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent(FEEDBACK_OPEN_EVENT));
24
- }
25
- return /* @__PURE__ */ jsxs(Fragment, { children: [
26
- /* @__PURE__ */ jsxs("button", {
27
- type: "button",
28
- "aria-label": label,
29
- "data-vibes-feedback": "",
30
- onClick: () => setOpen(true),
31
- style: badgeStyle,
32
- children: [/* @__PURE__ */ jsx(OmgMark, {
33
- size: 18,
34
- id: `badge-${markId}`
35
- }), /* @__PURE__ */ jsx("span", {
36
- style: badgeTextStyle,
37
- children: "omg"
38
- })]
39
- }),
40
- open && /* @__PURE__ */ jsx("div", {
41
- "data-vibes-feedback": "",
42
- role: "presentation",
43
- style: backdropStyle,
44
- onMouseDown: (event) => {
45
- if (event.target === event.currentTarget) setOpen(false);
46
- },
47
- children: /* @__PURE__ */ jsxs("div", {
48
- role: "dialog",
49
- "aria-modal": "true",
50
- "aria-labelledby": titleId,
51
- "aria-describedby": descriptionId,
52
- style: dialogStyle,
53
- children: [
54
- /* @__PURE__ */ jsx("button", {
55
- type: "button",
56
- "aria-label": "Close",
57
- onClick: () => setOpen(false),
58
- style: closeButtonStyle,
59
- children: /* @__PURE__ */ jsx("svg", {
60
- "aria-hidden": "true",
61
- width: "18",
62
- height: "18",
63
- viewBox: "0 0 24 24",
64
- fill: "none",
65
- children: /* @__PURE__ */ jsx("path", {
66
- d: "M6 6l12 12M18 6 6 18",
67
- stroke: "currentColor",
68
- strokeWidth: "2",
69
- strokeLinecap: "round"
70
- })
71
- })
72
- }),
73
- /* @__PURE__ */ jsx("div", {
74
- style: markWrapStyle,
75
- children: /* @__PURE__ */ jsx(OmgMark, {
76
- size: 34,
77
- id: `dialog-${markId}`
12
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
13
+ type: "button",
14
+ "aria-label": label,
15
+ onClick: () => setOpen(true),
16
+ style: badgeStyle,
17
+ children: [/* @__PURE__ */ jsx(OmgMark, {
18
+ size: 18,
19
+ id: `badge-${markId}`
20
+ }), /* @__PURE__ */ jsx("span", {
21
+ style: badgeTextStyle,
22
+ children: "omg"
23
+ })]
24
+ }), open && /* @__PURE__ */ jsx("div", {
25
+ role: "presentation",
26
+ style: backdropStyle,
27
+ onMouseDown: (event) => {
28
+ if (event.target === event.currentTarget) setOpen(false);
29
+ },
30
+ children: /* @__PURE__ */ jsxs("div", {
31
+ role: "dialog",
32
+ "aria-modal": "true",
33
+ "aria-labelledby": titleId,
34
+ "aria-describedby": descriptionId,
35
+ style: dialogStyle,
36
+ children: [
37
+ /* @__PURE__ */ jsx("button", {
38
+ type: "button",
39
+ "aria-label": "Close",
40
+ onClick: () => setOpen(false),
41
+ style: closeButtonStyle,
42
+ children: /* @__PURE__ */ jsx("svg", {
43
+ "aria-hidden": "true",
44
+ width: "18",
45
+ height: "18",
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ children: /* @__PURE__ */ jsx("path", {
49
+ d: "M6 6l12 12M18 6 6 18",
50
+ stroke: "currentColor",
51
+ strokeWidth: "2",
52
+ strokeLinecap: "round"
78
53
  })
79
- }),
80
- /* @__PURE__ */ jsx("h2", {
81
- id: titleId,
82
- style: titleStyle,
83
- children: "Built with omg"
84
- }),
85
- /* @__PURE__ */ jsx("p", {
86
- id: descriptionId,
87
- style: bodyStyle,
88
- children: "omg turns a plain-language idea into a real app, then hosts it with sign-in, data storage, and publishing already handled."
89
- }),
90
- /* @__PURE__ */ jsx("a", {
91
- href,
92
- target: "_blank",
93
- rel: "noopener noreferrer",
94
- style: ctaStyle,
95
- children: "Learn about omg"
96
- }),
97
- feedbackBundled() && /* @__PURE__ */ jsx("button", {
98
- type: "button",
99
- onClick: summonFeedback,
100
- style: secondaryStyle,
101
- children: "Send feedback"
102
54
  })
103
- ]
104
- })
105
- }),
106
- feedbackBundled() && /* @__PURE__ */ jsx(VibesFeedback, {
107
- showButton: false,
108
- accent: BRAND,
109
- title: "Send feedback"
55
+ }),
56
+ /* @__PURE__ */ jsx("div", {
57
+ style: markWrapStyle,
58
+ children: /* @__PURE__ */ jsx(OmgMark, {
59
+ size: 34,
60
+ id: `dialog-${markId}`
61
+ })
62
+ }),
63
+ /* @__PURE__ */ jsx("h2", {
64
+ id: titleId,
65
+ style: titleStyle,
66
+ children: "Built with omg"
67
+ }),
68
+ /* @__PURE__ */ jsx("p", {
69
+ id: descriptionId,
70
+ style: bodyStyle,
71
+ children: "omg turns a plain-language idea into a real app, then hosts it with sign-in, data storage, and publishing already handled."
72
+ }),
73
+ /* @__PURE__ */ jsx("a", {
74
+ href,
75
+ target: "_blank",
76
+ rel: "noopener noreferrer",
77
+ style: ctaStyle,
78
+ children: "Learn about omg"
79
+ })
80
+ ]
110
81
  })
111
- ] });
82
+ })] });
112
83
  }
113
84
  function OmgMark({ size, id }) {
114
85
  const maskId = `omg-mark-cut-${id}`;
@@ -247,22 +218,5 @@ const ctaStyle = {
247
218
  textDecoration: "none",
248
219
  letterSpacing: 0
249
220
  };
250
- const secondaryStyle = {
251
- display: "inline-flex",
252
- alignItems: "center",
253
- justifyContent: "center",
254
- width: "100%",
255
- height: 44,
256
- marginTop: 8,
257
- borderRadius: 12,
258
- background: "transparent",
259
- border: "1px solid rgba(21, 17, 13, 0.14)",
260
- color: "#15110d",
261
- fontSize: 15,
262
- fontWeight: 650,
263
- cursor: "pointer",
264
- fontFamily: font,
265
- letterSpacing: 0
266
- };
267
221
  //#endregion
268
222
  export { OmgBadge as t };
@@ -1,4 +1,4 @@
1
- import { t as OmgBadge } from "../OmgBadge-DPcZZfJl.mjs";
1
+ import { t as OmgBadge } from "../OmgBadge-C559Dp1f.mjs";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { createRoot } from "react-dom/client";
4
4
  //#region src/brand/auto.tsx
@@ -8,7 +8,6 @@ function mount() {
8
8
  if (document.getElementById(HOST_ID)) return;
9
9
  const host = document.createElement("div");
10
10
  host.id = HOST_ID;
11
- host.setAttribute("data-vibes-feedback", "");
12
11
  document.body.appendChild(host);
13
12
  createRoot(host).render(/* @__PURE__ */ jsx(OmgBadge, {}));
14
13
  }
package/dist/index.mjs CHANGED
@@ -1,11 +1,46 @@
1
- import { a as installTrace, c as requestMotionPermission, d as getAuthContext, f as notifyAuthRequired, h as subscribeAuthRequired, i as getTrace, l as useFeedbackGesture, m as subscribeAuthChange, n as captureScreenshot, o as attachGestureListeners, p as setAuthContext, r as clearTrace, s as motionPermissionState, t as VibesFeedback, u as useUpload } from "./VibesFeedback-BF2Vf6FK.mjs";
2
- import { t as OmgBadge } from "./OmgBadge-DPcZZfJl.mjs";
1
+ import { t as OmgBadge } from "./OmgBadge-C559Dp1f.mjs";
3
2
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
3
  import { fetchEventSource } from "@microsoft/fetch-event-source";
5
4
  import { createAuthClient } from "better-auth/react";
6
5
  import { passkeyClient } from "@better-auth/passkey/client";
7
6
  import { emailOTPClient, magicLinkClient } from "better-auth/client/plugins";
8
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/auth/bridge.ts
9
+ let current = {
10
+ user: null,
11
+ token: null,
12
+ authReady: false
13
+ };
14
+ const authChangeListeners = /* @__PURE__ */ new Set();
15
+ function setAuthContext(snap) {
16
+ current = snap;
17
+ for (const cb of authChangeListeners) cb(snap);
18
+ }
19
+ function getAuthContext() {
20
+ return current;
21
+ }
22
+ /**
23
+ * Subscribe to auth snapshot changes (user / token / authReady). Returns an
24
+ * unsubscribe fn. Data hooks use this to re-establish a subscription when the
25
+ * JWT finishes loading (null→token) or on sign-out (token→null) — without it
26
+ * the WS connects once, often before getToken() resolves, and never retries
27
+ * with a bearer.
28
+ */
29
+ function subscribeAuthChange(cb) {
30
+ authChangeListeners.add(cb);
31
+ return () => authChangeListeners.delete(cb);
32
+ }
33
+ const authRequiredListeners = /* @__PURE__ */ new Set();
34
+ /** Subscribe to auth-required signals. Returns an unsubscribe fn. */
35
+ function subscribeAuthRequired(cb) {
36
+ authRequiredListeners.add(cb);
37
+ return () => authRequiredListeners.delete(cb);
38
+ }
39
+ /** Notify all subscribers that a request hit the server's auth-required signal. */
40
+ function notifyAuthRequired() {
41
+ for (const cb of authRequiredListeners) cb();
42
+ }
43
+ //#endregion
9
44
  //#region src/auth/client.ts
10
45
  /**
11
46
  * Recover the user carried by a slug-scoped JWT.
@@ -650,6 +685,143 @@ function VibesAuthGuard({ children, fallback, loadingFallback }) {
650
685
  return /* @__PURE__ */ jsx(Fragment, { children });
651
686
  }
652
687
  //#endregion
688
+ //#region src/storage/useUpload.ts
689
+ function bearerHeaders() {
690
+ const { token } = getAuthContext();
691
+ return token ? { Authorization: `Bearer ${token}` } : {};
692
+ }
693
+ /**
694
+ * Headless upload hook. Returns the upload function plus state.
695
+ *
696
+ * Usage:
697
+ * const { upload, uploading, progress, error } = useUpload()
698
+ * await upload(file, { key: `avatars/${user.id}.png` })
699
+ */
700
+ function useUpload() {
701
+ const [state, setState] = useState({
702
+ uploading: false,
703
+ progress: 0,
704
+ error: null,
705
+ result: null
706
+ });
707
+ const reset = useCallback(() => {
708
+ setState({
709
+ uploading: false,
710
+ progress: 0,
711
+ error: null,
712
+ result: null
713
+ });
714
+ }, []);
715
+ const upload = useCallback(async (file, opts) => {
716
+ setState({
717
+ uploading: true,
718
+ progress: 0,
719
+ error: null,
720
+ result: null
721
+ });
722
+ const presignUrl = opts.presignUrl ?? "/api/_storage/presign";
723
+ const contentType = opts.contentType ?? file.type ?? "application/octet-stream";
724
+ try {
725
+ const presignRes = await fetch(presignUrl, {
726
+ method: "POST",
727
+ headers: {
728
+ "Content-Type": "application/json",
729
+ ...bearerHeaders()
730
+ },
731
+ body: JSON.stringify({
732
+ action: "put",
733
+ key: opts.key,
734
+ contentType,
735
+ scope: opts.scope ?? "user"
736
+ })
737
+ });
738
+ if (!presignRes.ok) {
739
+ if (presignRes.status === 401 && getAuthContext().authReady) notifyAuthRequired();
740
+ const text = await presignRes.text().catch(() => "");
741
+ throw new Error(`presign ${presignRes.status}: ${text.slice(0, 200)}`);
742
+ }
743
+ const { url, key: storedKey, maxBytes } = await presignRes.json();
744
+ if (typeof maxBytes === "number" && file.size > maxBytes) throw new Error(`file too large (${file.size} > ${maxBytes} bytes)`);
745
+ await new Promise((resolve, reject) => {
746
+ const xhr = new XMLHttpRequest();
747
+ xhr.open("PUT", url);
748
+ xhr.setRequestHeader("Content-Type", contentType);
749
+ xhr.upload.onprogress = (e) => {
750
+ if (e.lengthComputable) {
751
+ const frac = e.loaded / e.total;
752
+ setState((s) => ({
753
+ ...s,
754
+ progress: frac
755
+ }));
756
+ opts.onProgress?.(frac);
757
+ }
758
+ };
759
+ xhr.onload = () => {
760
+ if (xhr.status >= 200 && xhr.status < 300) resolve();
761
+ else reject(/* @__PURE__ */ new Error(`PUT ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));
762
+ };
763
+ xhr.onerror = () => reject(/* @__PURE__ */ new Error("network error during upload"));
764
+ xhr.send(file);
765
+ });
766
+ const dlRes = await fetch(presignUrl, {
767
+ method: "POST",
768
+ headers: {
769
+ "Content-Type": "application/json",
770
+ ...bearerHeaders()
771
+ },
772
+ body: JSON.stringify({
773
+ action: "get",
774
+ key: opts.key,
775
+ scope: opts.scope ?? "user"
776
+ })
777
+ });
778
+ let downloadUrl = "";
779
+ if (dlRes.ok) downloadUrl = (await dlRes.json()).url ?? "";
780
+ fetch(presignUrl.replace(/\/presign$/, "/notify"), {
781
+ method: "POST",
782
+ headers: {
783
+ "Content-Type": "application/json",
784
+ ...bearerHeaders()
785
+ },
786
+ body: JSON.stringify({
787
+ action: "upload",
788
+ key: opts.key,
789
+ size: file.size,
790
+ contentType,
791
+ scope: opts.scope ?? "user"
792
+ })
793
+ }).catch(() => {});
794
+ const result = {
795
+ key: storedKey,
796
+ downloadUrl,
797
+ size: file.size,
798
+ contentType
799
+ };
800
+ setState({
801
+ uploading: false,
802
+ progress: 1,
803
+ error: null,
804
+ result
805
+ });
806
+ return result;
807
+ } catch (err) {
808
+ const msg = err.message ?? String(err);
809
+ setState({
810
+ uploading: false,
811
+ progress: 0,
812
+ error: msg,
813
+ result: null
814
+ });
815
+ throw err;
816
+ }
817
+ }, []);
818
+ return {
819
+ ...state,
820
+ upload,
821
+ reset
822
+ };
823
+ }
824
+ //#endregion
653
825
  //#region src/storage/VibesUpload.tsx
654
826
  function defaultKeyFor(file) {
655
827
  const safe = file.name.replace(/[^a-zA-Z0-9._-]/g, "-");
@@ -889,6 +1061,7 @@ async function createNotification(input) {
889
1061
  const runtimeClaims = {
890
1062
  llmInvoke: "llm.invoke",
891
1063
  mediaInvoke: "media.invoke",
1064
+ speechTranscribe: "speech.transcribe",
892
1065
  browserUse: "browser.use",
893
1066
  sandboxCreate: "sandbox.create",
894
1067
  sandboxDelegate: "sandbox.delegate"
@@ -1185,17 +1358,19 @@ function orderedRows(rows) {
1185
1358
  return Array.from(rows).sort((a, b) => compareRows(a, b));
1186
1359
  }
1187
1360
  var SharedCollectionSocket = class {
1361
+ url;
1362
+ token;
1363
+ ws = null;
1364
+ connecting = false;
1365
+ closedByManager = false;
1366
+ consecutiveErrors = 0;
1367
+ reconnectTimer = null;
1368
+ flushQueued = false;
1369
+ subs = /* @__PURE__ */ new Map();
1370
+ pendingSubIds = /* @__PURE__ */ new Set();
1188
1371
  constructor(url, token) {
1189
1372
  this.url = url;
1190
1373
  this.token = token;
1191
- this.ws = null;
1192
- this.connecting = false;
1193
- this.closedByManager = false;
1194
- this.consecutiveErrors = 0;
1195
- this.reconnectTimer = null;
1196
- this.flushQueued = false;
1197
- this.subs = /* @__PURE__ */ new Map();
1198
- this.pendingSubIds = /* @__PURE__ */ new Set();
1199
1374
  }
1200
1375
  add(sub) {
1201
1376
  this.subs.set(sub.subId, sub);
@@ -1421,24 +1596,29 @@ function makeInitialQuerySnapshot() {
1421
1596
  };
1422
1597
  }
1423
1598
  var CollectionQueryStore = class {
1599
+ key;
1600
+ url;
1601
+ collection;
1602
+ where;
1603
+ onIdle;
1604
+ listeners = /* @__PURE__ */ new Set();
1605
+ rowSubscribers = /* @__PURE__ */ new Set();
1606
+ socketHandle = null;
1607
+ fallbackHandle = null;
1608
+ authUnsub = null;
1609
+ authToken = getAuthContext().token;
1610
+ authReady = getAuthContext().authReady;
1611
+ lastSeq = null;
1612
+ pendingAuthRequired = null;
1613
+ notifyQueued = false;
1614
+ rowsDirty = false;
1615
+ snapshot = makeInitialQuerySnapshot();
1424
1616
  constructor(key, url, collection, where, onIdle) {
1425
1617
  this.key = key;
1426
1618
  this.url = url;
1427
1619
  this.collection = collection;
1428
1620
  this.where = where;
1429
1621
  this.onIdle = onIdle;
1430
- this.listeners = /* @__PURE__ */ new Set();
1431
- this.rowSubscribers = /* @__PURE__ */ new Set();
1432
- this.socketHandle = null;
1433
- this.fallbackHandle = null;
1434
- this.authUnsub = null;
1435
- this.authToken = getAuthContext().token;
1436
- this.authReady = getAuthContext().authReady;
1437
- this.lastSeq = null;
1438
- this.pendingAuthRequired = null;
1439
- this.notifyQueued = false;
1440
- this.rowsDirty = false;
1441
- this.snapshot = makeInitialQuerySnapshot();
1442
1622
  }
1443
1623
  subscribe(listener) {
1444
1624
  this.listeners.add(listener);
@@ -1637,4 +1817,4 @@ function _canonicalPredicateForTests(predicate) {
1637
1817
  return canonicalPredicateString(predicate);
1638
1818
  }
1639
1819
  //#endregion
1640
- export { OmgBadge, VibesAuthAutoPrompt, VibesAuthGuard, VibesAuthProvider, VibesAuthRequiredError, VibesFeedback, VibesLogin, VibesUpload, _canonicalPredicateForTests, _resetSharedCollectionSocketsForTests, attachGestureListeners, captureScreenshot, clearTrace, createNotification, createSandbox, createVibesAuth, deriveAppId, deriveTokenUrl, forkSandbox, getAuthContext, getTrace, installTrace, mailAppForEmail, motionPermissionState, notificationSupport, notifyAuthRequired, requestMotionPermission, runtimeClaims, subscribeAuthRequired, subscribeCollection, useAuth, useCollection, useFeedbackGesture, useNotificationPermission, useNotifications, useQuery, useUpload, useUser, useVibesAuth, useVibesToken, vibesFetch };
1820
+ export { OmgBadge, VibesAuthAutoPrompt, VibesAuthGuard, VibesAuthProvider, VibesAuthRequiredError, VibesLogin, VibesUpload, _canonicalPredicateForTests, _resetSharedCollectionSocketsForTests, createNotification, createSandbox, createVibesAuth, deriveAppId, deriveTokenUrl, forkSandbox, getAuthContext, mailAppForEmail, notificationSupport, notifyAuthRequired, runtimeClaims, subscribeAuthRequired, subscribeCollection, useAuth, useCollection, useNotificationPermission, useNotifications, useQuery, useUpload, useUser, useVibesAuth, useVibesToken, vibesFetch };
package/package.json CHANGED
@@ -1,16 +1,12 @@
1
1
  {
2
2
  "name": "@omg-dev/sdk",
3
- "version": "0.4.30",
3
+ "version": "0.4.31",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./src/index.ts",
8
8
  "default": "./dist/index.mjs"
9
9
  },
10
- "./feedback/auto": {
11
- "types": "./src/feedback/auto.tsx",
12
- "default": "./dist/feedback/auto.mjs"
13
- },
14
10
  "./brand/auto": {
15
11
  "types": "./src/brand/auto.tsx",
16
12
  "default": "./dist/brand/auto.mjs"
@@ -19,8 +15,7 @@
19
15
  "dependencies": {
20
16
  "better-auth": "^1.2.0",
21
17
  "@better-auth/passkey": "^1.2.0",
22
- "@microsoft/fetch-event-source": "^2.0.1",
23
- "modern-screenshot": "^4.0.0"
18
+ "@microsoft/fetch-event-source": "^2.0.1"
24
19
  },
25
20
  "peerDependencies": {
26
21
  "react": ">=18",
@@ -1,5 +1,4 @@
1
1
  import { useId, useState, type CSSProperties } from "react"
2
- import { VibesFeedback } from "../feedback/VibesFeedback"
3
2
 
4
3
  export interface OmgBadgeProps {
5
4
  /** Link used by the dialog CTA. Default: https://omg.dev */
@@ -13,24 +12,6 @@ const BRAND = "#FF5530"
13
12
  const Z_BADGE = 2147482000
14
13
  const Z_DIALOG = 2147483100
15
14
 
16
- // Cross-process signal the brand badge dispatches to summon the (button-less)
17
- // feedback sheet it mounts. Bundling feedback INTO this badge is deliberate:
18
- // one floating control in the corner, not three.
19
- export const FEEDBACK_OPEN_EVENT = "vibes:feedback:open"
20
-
21
- // Build-time gate (set by @omg-dev/vite-plugin via `define`). True when the app
22
- // has feedback enabled AND the brand badge owns it. Falls back to true when the
23
- // badge is used outside the plugin (it's only auto-injected by the plugin, so
24
- // in practice the define is always present in published bundles).
25
- declare const __VIBES_BRAND_FEEDBACK__: boolean | undefined
26
- function feedbackBundled(): boolean {
27
- try {
28
- return typeof __VIBES_BRAND_FEEDBACK__ === "undefined" ? true : __VIBES_BRAND_FEEDBACK__
29
- } catch {
30
- return true
31
- }
32
- }
33
-
34
15
  export function OmgBadge({
35
16
  href = "https://omg.dev",
36
17
  label = "What is omg?",
@@ -40,19 +21,11 @@ export function OmgBadge({
40
21
  const descriptionId = useId()
41
22
  const markId = useId().replace(/[^a-zA-Z0-9_-]/g, "")
42
23
 
43
- function summonFeedback() {
44
- setOpen(false)
45
- if (typeof window !== "undefined") {
46
- window.dispatchEvent(new CustomEvent(FEEDBACK_OPEN_EVENT))
47
- }
48
- }
49
-
50
24
  return (
51
25
  <>
52
26
  <button
53
27
  type="button"
54
28
  aria-label={label}
55
- data-vibes-feedback=""
56
29
  onClick={() => setOpen(true)}
57
30
  style={badgeStyle}
58
31
  >
@@ -62,7 +35,6 @@ export function OmgBadge({
62
35
 
63
36
  {open && (
64
37
  <div
65
- data-vibes-feedback=""
66
38
  role="presentation"
67
39
  style={backdropStyle}
68
40
  onMouseDown={(event) => {
@@ -100,20 +72,9 @@ export function OmgBadge({
100
72
  <a href={href} target="_blank" rel="noopener noreferrer" style={ctaStyle}>
101
73
  Learn about omg
102
74
  </a>
103
- {feedbackBundled() && (
104
- <button type="button" onClick={summonFeedback} style={secondaryStyle}>
105
- Send feedback
106
- </button>
107
- )}
108
75
  </div>
109
76
  </div>
110
77
  )}
111
-
112
- {/* Feedback is bundled into the badge — a button-less sheet summoned from
113
- the dialog (or by gesture), so there's no second floating control. */}
114
- {feedbackBundled() && (
115
- <VibesFeedback showButton={false} accent={BRAND} title="Send feedback" />
116
- )}
117
78
  </>
118
79
  )
119
80
  }
@@ -256,21 +217,3 @@ const ctaStyle: CSSProperties = {
256
217
  textDecoration: "none",
257
218
  letterSpacing: 0,
258
219
  }
259
-
260
- const secondaryStyle: CSSProperties = {
261
- display: "inline-flex",
262
- alignItems: "center",
263
- justifyContent: "center",
264
- width: "100%",
265
- height: 44,
266
- marginTop: 8,
267
- borderRadius: 12,
268
- background: "transparent",
269
- border: "1px solid rgba(21, 17, 13, 0.14)",
270
- color: "#15110d",
271
- fontSize: 15,
272
- fontWeight: 650,
273
- cursor: "pointer",
274
- fontFamily: font,
275
- letterSpacing: 0,
276
- }
@@ -4,10 +4,6 @@
4
4
  // app's React tree so it survives user app rewrites and does not require each
5
5
  // generated app to carry attribution code in its source.
6
6
  //
7
- // The badge also bundles the button-less feedback sheet summoned from its
8
- // dialog, so the vite plugin skips the standalone feedback control whenever
9
- // this badge is enabled (the default).
10
-
11
7
  import { createRoot } from "react-dom/client"
12
8
  import { OmgBadge } from "./OmgBadge"
13
9
 
@@ -18,8 +14,6 @@ function mount() {
18
14
  if (document.getElementById(HOST_ID)) return
19
15
  const host = document.createElement("div")
20
16
  host.id = HOST_ID
21
- // Feedback screenshots should capture the app, not platform chrome.
22
- host.setAttribute("data-vibes-feedback", "")
23
17
  document.body.appendChild(host)
24
18
  createRoot(host).render(<OmgBadge />)
25
19
  }
package/src/index.ts CHANGED
@@ -14,9 +14,6 @@ export * from "./auth/index"
14
14
  // Storage surface — useUpload() + <VibesUpload />. Re-exported here for
15
15
  // the same publish-workflow reason as auth (only "." subpath survives).
16
16
  export * from "./storage/index"
17
- // Feedback surface — <VibesFeedback /> gesture widget + trace capture.
18
- // Re-exported here for the same publish-workflow reason (only "." survives).
19
- export * from "./feedback/index"
20
17
  // Brand surface — <OmgBadge /> platform attribution badge.
21
18
  export * from "./brand/index"
22
19
  // Notifications surface — durable in-app inbox + Web Push registration.
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { createSandbox, forkSandbox, runtimeClaims } from "./sandbox";
3
3
 
4
+ it("exports the funded transcription capability", () => {
5
+ expect(runtimeClaims.speechTranscribe).toBe("speech.transcribe");
6
+ });
7
+
4
8
  describe("sandbox SDK", () => {
5
9
  it("posts create requests to the sandbox router", async () => {
6
10
  const calls: Array<{ url: string; body: unknown }> = [];
package/src/sandbox.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export const runtimeClaims = {
2
2
  llmInvoke: "llm.invoke",
3
3
  mediaInvoke: "media.invoke",
4
+ speechTranscribe: "speech.transcribe",
4
5
  browserUse: "browser.use",
5
6
  sandboxCreate: "sandbox.create",
6
7
  sandboxDelegate: "sandbox.delegate",