@buoy-gg/images 7.0.14 → 7.0.15

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 (74) hide show
  1. package/lib/commonjs/capture/actions.js +15 -0
  2. package/lib/commonjs/capture/actions.js.map +1 -1
  3. package/lib/commonjs/capture/install.js +5 -1
  4. package/lib/commonjs/capture/install.js.map +1 -1
  5. package/lib/commonjs/capture/overrides.js +70 -10
  6. package/lib/commonjs/capture/overrides.js.map +1 -1
  7. package/lib/commonjs/capture/savings.js +16 -0
  8. package/lib/commonjs/capture/savings.js.map +1 -1
  9. package/lib/commonjs/components/ImageDetail.js +11 -1
  10. package/lib/commonjs/components/ImageDetail.js.map +1 -1
  11. package/lib/commonjs/components/ImagesModal.js +103 -38
  12. package/lib/commonjs/components/ImagesModal.js.map +1 -1
  13. package/lib/commonjs/index.js +26 -1
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/remote.js +42 -0
  16. package/lib/commonjs/remote.js.map +1 -0
  17. package/lib/commonjs/state.js +5 -1
  18. package/lib/commonjs/state.js.map +1 -1
  19. package/lib/commonjs/store/imageEventStore.js +79 -1
  20. package/lib/commonjs/store/imageEventStore.js.map +1 -1
  21. package/lib/commonjs/sync/imagesSyncAdapter.js.map +1 -1
  22. package/lib/module/capture/actions.js +15 -0
  23. package/lib/module/capture/actions.js.map +1 -1
  24. package/lib/module/capture/install.js +5 -1
  25. package/lib/module/capture/install.js.map +1 -1
  26. package/lib/module/capture/overrides.js +70 -10
  27. package/lib/module/capture/overrides.js.map +1 -1
  28. package/lib/module/capture/savings.js +16 -0
  29. package/lib/module/capture/savings.js.map +1 -1
  30. package/lib/module/components/ImageDetail.js +13 -3
  31. package/lib/module/components/ImageDetail.js.map +1 -1
  32. package/lib/module/components/ImagesModal.js +104 -40
  33. package/lib/module/components/ImagesModal.js.map +1 -1
  34. package/lib/module/index.js +6 -0
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/remote.js +36 -0
  37. package/lib/module/remote.js.map +1 -0
  38. package/lib/module/state.js +5 -1
  39. package/lib/module/state.js.map +1 -1
  40. package/lib/module/store/imageEventStore.js +76 -1
  41. package/lib/module/store/imageEventStore.js.map +1 -1
  42. package/lib/module/sync/imagesSyncAdapter.js.map +1 -1
  43. package/lib/typescript/capture/actions.d.ts.map +1 -1
  44. package/lib/typescript/capture/install.d.ts.map +1 -1
  45. package/lib/typescript/capture/overrides.d.ts.map +1 -1
  46. package/lib/typescript/capture/savings.d.ts.map +1 -1
  47. package/lib/typescript/components/ImageDetail.d.ts.map +1 -1
  48. package/lib/typescript/components/ImagesModal.d.ts +9 -1
  49. package/lib/typescript/components/ImagesModal.d.ts.map +1 -1
  50. package/lib/typescript/index.d.ts +5 -0
  51. package/lib/typescript/index.d.ts.map +1 -1
  52. package/lib/typescript/remote.d.ts +16 -0
  53. package/lib/typescript/remote.d.ts.map +1 -0
  54. package/lib/typescript/state.d.ts +9 -1
  55. package/lib/typescript/state.d.ts.map +1 -1
  56. package/lib/typescript/store/imageEventStore.d.ts +15 -1
  57. package/lib/typescript/store/imageEventStore.d.ts.map +1 -1
  58. package/lib/typescript/sync/imagesSyncAdapter.d.ts +3 -70
  59. package/lib/typescript/sync/imagesSyncAdapter.d.ts.map +1 -1
  60. package/lib/typescript/types.d.ts +53 -0
  61. package/lib/typescript/types.d.ts.map +1 -1
  62. package/package.json +2 -2
  63. package/src/capture/actions.ts +9 -0
  64. package/src/capture/install.ts +5 -1
  65. package/src/capture/overrides.ts +59 -9
  66. package/src/capture/savings.ts +11 -0
  67. package/src/components/ImageDetail.tsx +12 -2
  68. package/src/components/ImagesModal.tsx +107 -39
  69. package/src/index.tsx +12 -0
  70. package/src/remote.ts +42 -0
  71. package/src/state.ts +16 -1
  72. package/src/store/imageEventStore.ts +76 -2
  73. package/src/sync/imagesSyncAdapter.ts +2 -2
  74. package/src/types.ts +43 -0
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Remote-dispatch seam for dashboard mirrors (Buoy Desktop).
3
+ *
4
+ * On device this stays null and every action in capture/ runs locally. A
5
+ * dashboard sets a handler that forwards the action name + params over the
6
+ * external-sync protocol to the SAME adapter actions the MCP uses
7
+ * (sync/imagesSyncAdapter.ts), so one action vocabulary serves phone UI,
8
+ * desktop UI, and LLM automation.
9
+ */
10
+ import type { ActionResult } from "./capture/actions";
11
+ export type ImagesRemoteDispatch = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
12
+ export declare function setRemoteImagesHandler(handler: ImagesRemoteDispatch | null): void;
13
+ export declare function remoteImagesDispatch(): ImagesRemoteDispatch | null;
14
+ /** Coerce an adapter action's `{ ok, message }` reply into an ActionResult. */
15
+ export declare function remoteActionResult(call: Promise<unknown>): Promise<ActionResult>;
16
+ //# sourceMappingURL=remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,oBAAoB,GAAG,IAAI,GACnC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,IAAI,oBAAoB,GAAG,IAAI,CAElE;AAED,+EAA+E;AAC/E,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB"}
@@ -9,7 +9,7 @@
9
9
  * flag. Keying all mutable state on globalThis under a well-known symbol makes
10
10
  * every copy operate on the same data.
11
11
  */
12
- import type { ImageRecord } from "./types";
12
+ import type { CaptureStatus, ImageRecord } from "./types";
13
13
  export interface ImagesGlobalState {
14
14
  records: ImageRecord[];
15
15
  byId: Map<number, ImageRecord>;
@@ -49,6 +49,14 @@ export interface ImagesGlobalState {
49
49
  network: "normal" | "offline" | "cold";
50
50
  blank: boolean;
51
51
  };
52
+ /** True on dashboards: local capture must never install (mirror only). */
53
+ captureDisabled: boolean;
54
+ /** Forwards actions to the synced device instead of running them locally. */
55
+ remoteDispatch: ((action: string, params?: Record<string, unknown>) => Promise<unknown>) | null;
56
+ /** Device PixelRatio derived from the synced snapshot (layout → neededPx). */
57
+ mirrorPixelRatio: number | null;
58
+ /** Device-reported capture status (overrides the local probe on mirrors). */
59
+ mirrorCaptureStatus: CaptureStatus | null;
52
60
  }
53
61
  export declare function imagesState(): ImagesGlobalState;
54
62
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IAEvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IAEvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IAEnB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IAErB,uEAAuE;IACvE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9D,gFAAgF;IAChF,eAAe,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACjC,wEAAwE;IACxE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEpC,6EAA6E;IAC7E,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,wEAAwE;IACxE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,6EAA6E;IAC7E,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,WAAW,EAAE;QAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CACzE;AAID,wBAAgB,WAAW,IAAI,iBAAiB,CA8B/C"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAEhC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IAEvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IAEvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IAEnB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IAErB,uEAAuE;IACvE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9D,gFAAgF;IAChF,eAAe,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACjC,wEAAwE;IACxE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEpC,6EAA6E;IAC7E,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,wEAAwE;IACxE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,6EAA6E;IAC7E,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,WAAW,EAAE;QAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAExE,0EAA0E;IAC1E,eAAe,EAAE,OAAO,CAAC;IACzB,6EAA6E;IAC7E,cAAc,EACV,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GACxE,IAAI,CAAC;IACT,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,6EAA6E;IAC7E,mBAAmB,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3C;AAID,wBAAgB,WAAW,IAAI,iBAAiB,CAkC/C"}
@@ -8,7 +8,7 @@
8
8
  * globalThis singleton (see ../state.ts) so a dual module instance of this
9
9
  * package still reads/writes one shared store.
10
10
  */
11
- import type { ImageRecord, ImageStats } from "../types";
11
+ import type { ImageRecord, ImageStats, ImagesSnapshotPayload } from "../types";
12
12
  export declare function createRecord(partial: Omit<ImageRecord, "id" | "createdAt" | "loadCount" | "mounted" | "status">): ImageRecord;
13
13
  /** Mutate a record in place (records are owned by the store) and notify. */
14
14
  export declare function updateRecord(id: number, patch: Partial<ImageRecord>): ImageRecord | undefined;
@@ -16,6 +16,20 @@ export declare function getRecord(id: number): ImageRecord | undefined;
16
16
  export declare function getSnapshot(): ImageRecord[];
17
17
  export declare function subscribe(listener: () => void): () => void;
18
18
  export declare function clearRecords(): void;
19
+ /**
20
+ * Dashboards never instrument their own images — the store is purely a mirror
21
+ * of the synced device. Makes installImageCapture() a permanent no-op.
22
+ */
23
+ export declare function disableImagesCapture(): void;
24
+ /**
25
+ * Replace the whole store from a synced adapter snapshot (wire records are
26
+ * newest-first; the store keeps oldest-first and useImageRecords reverses).
27
+ * Also adopts the device's global modes / capture status and derives the
28
+ * DEVICE's PixelRatio so oversize math doesn't use the dashboard display's.
29
+ */
30
+ export declare function replaceImagesFromSnapshot(payload: ImagesSnapshotPayload): void;
31
+ /** Device PixelRatio on device; the SYNCED device's ratio on a mirror. */
32
+ export declare function effectivePixelRatio(): number;
19
33
  /** Physical pixels actually needed to fill the laid-out view 1:1. */
20
34
  export declare function neededPixels(record: ImageRecord): {
21
35
  width: number;
@@ -1 +1 @@
1
- {"version":3,"file":"imageEventStore.d.ts","sourceRoot":"","sources":["../../../src/store/imageEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAsBxD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC,GAClF,WAAW,CAkBb;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,WAAW,GAAG,SAAS,CAOzB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE7D;AAED,wBAAgB,WAAW,IAAI,WAAW,EAAE,CAE3C;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAI1D;AAED,wBAAgB,YAAY,IAAI,IAAI,CAQnC;AAMD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO1F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAG3D;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQ1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,UAAU,CAuB5D"}
1
+ {"version":3,"file":"imageEventStore.d.ts","sourceRoot":"","sources":["../../../src/store/imageEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAsB/E,wBAAgB,YAAY,CAC1B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC,GAClF,WAAW,CAkBb;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,WAAW,GAAG,SAAS,CAOzB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE7D;AAED,wBAAgB,WAAW,IAAI,WAAW,EAAE,CAE3C;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAI1D;AAED,wBAAgB,YAAY,IAAI,IAAI,CAWnC;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CA6C9E;AAMD,0EAA0E;AAC1E,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO1F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAG3D;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQ1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,UAAU,CAuB5D"}
@@ -8,44 +8,11 @@
8
8
  * global modes, and cache clears.
9
9
  */
10
10
  import type { NetworkMode } from "../capture/overrides";
11
+ import type { ImageRecordWire } from "../types";
11
12
  export declare const imagesSyncAdapter: {
12
13
  version: number;
13
14
  getSnapshot: () => {
14
- records: {
15
- id: number;
16
- lib: import("..").ImageLib;
17
- uri: string;
18
- kind: import("..").SourceKind;
19
- status: import("..").ImageStatus;
20
- mounted: boolean;
21
- cache: import("..").CacheVerdict | null;
22
- cacheSource: "event" | "queryCache" | null;
23
- ms: number | null;
24
- intrinsic: {
25
- width: number;
26
- height: number;
27
- } | null;
28
- layout: {
29
- width: number;
30
- height: number;
31
- } | null;
32
- neededPx: {
33
- width: number;
34
- height: number;
35
- } | null;
36
- oversizeFactor: number | null;
37
- decodedKB: number;
38
- wastedKB: number;
39
- progressSeen: boolean;
40
- bytesTotal: number | null;
41
- error: string | null;
42
- errorCode: number | null;
43
- loadCount: number;
44
- overrideLabel: string | null;
45
- hasAltText: boolean | null;
46
- layoutShifts: number;
47
- ageMs: number;
48
- }[];
15
+ records: ImageRecordWire[];
49
16
  stats: import("..").ImageStats;
50
17
  captureStatus: import("..").CaptureStatus;
51
18
  globalModes: {
@@ -59,41 +26,7 @@ export declare const imagesSyncAdapter: {
59
26
  /** List records (newest first). Params: { limit?, status? }. */
60
27
  list: (params: unknown) => {
61
28
  total: number;
62
- records: {
63
- id: number;
64
- lib: import("..").ImageLib;
65
- uri: string;
66
- kind: import("..").SourceKind;
67
- status: import("..").ImageStatus;
68
- mounted: boolean;
69
- cache: import("..").CacheVerdict | null;
70
- cacheSource: "event" | "queryCache" | null;
71
- ms: number | null;
72
- intrinsic: {
73
- width: number;
74
- height: number;
75
- } | null;
76
- layout: {
77
- width: number;
78
- height: number;
79
- } | null;
80
- neededPx: {
81
- width: number;
82
- height: number;
83
- } | null;
84
- oversizeFactor: number | null;
85
- decodedKB: number;
86
- wastedKB: number;
87
- progressSeen: boolean;
88
- bytesTotal: number | null;
89
- error: string | null;
90
- errorCode: number | null;
91
- loadCount: number;
92
- overrideLabel: string | null;
93
- hasAltText: boolean | null;
94
- layoutShifts: number;
95
- ageMs: number;
96
- }[];
29
+ records: ImageRecordWire[];
97
30
  stats: import("..").ImageStats;
98
31
  captureStatus: import("..").CaptureStatus;
99
32
  globalModes: {
@@ -1 +1 @@
1
- {"version":3,"file":"imagesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/imagesSyncAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,sBAAsB,CAAC;AAyExE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAMN,MAAM,IAAI;;QAE9B,gEAAgE;uBACjD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAatB,kDAAkD;4BAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAI3B,2CAA2C;6BACtB,OAAO;QAC5B,qFAAqF;+BAC9D,OAAO;QAC9B,gDAAgD;wBAChC,OAAO;QACvB,uEAAuE;4BAC7C,OAAO;;;;QAUjC,iEAAiE;wBACjD,OAAO;;;;QAIvB;;;WAGG;8BACmB,OAAO;;;;QAwB7B,oDAAoD;gCAC5B,OAAO;;;;QAI/B;;;WAGG;6BACwB,OAAO;QA6BlC,8EAA8E;iCACrD,OAAO;;;;;;;QAQhC,iEAAiE;iCACxC,OAAO;;;;;;;QAIhC,2DAA2D;;;;;QAK3D,0EAA0E;kCAC1C,OAAO;;;;;;CAsB1C,CAAC"}
1
+ {"version":3,"file":"imagesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/imagesSyncAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,sBAAsB,CAAC;AAYxE,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,UAAU,CAAC;AA6D7D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;0BAMN,MAAM,IAAI;;QAE9B,gEAAgE;uBACjD,OAAO;;;;;;;;;;;QAatB,kDAAkD;4BAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAI3B,2CAA2C;6BACtB,OAAO;QAC5B,qFAAqF;+BAC9D,OAAO;QAC9B,gDAAgD;wBAChC,OAAO;QACvB,uEAAuE;4BAC7C,OAAO;;;;QAUjC,iEAAiE;wBACjD,OAAO;;;;QAIvB;;;WAGG;8BACmB,OAAO;;;;QAwB7B,oDAAoD;gCAC5B,OAAO;;;;QAI/B;;;WAGG;6BACwB,OAAO;QA6BlC,8EAA8E;iCACrD,OAAO;;;;;;;QAQhC,iEAAiE;iCACxC,OAAO;;;;;;;QAIhC,2DAA2D;;;;;QAK3D,0EAA0E;kCAC1C,OAAO;;;;;;CAsB1C,CAAC"}
@@ -106,6 +106,59 @@ export interface ImageStats {
106
106
  /** Σ estimated wasted decoded bytes from oversized sources (mounted). */
107
107
  estWastedBytes: number;
108
108
  }
109
+ /**
110
+ * Slim wire form of a record as pushed by the external-sync adapter (heavy
111
+ * derived math precomputed on device). This is what dashboards receive and
112
+ * what `replaceFromSnapshot` reconstructs ImageRecords from — keep it in
113
+ * lockstep with `toWire` in sync/imagesSyncAdapter.ts (and the Flutter port).
114
+ */
115
+ export interface ImageRecordWire {
116
+ id: number;
117
+ lib: ImageLib;
118
+ uri: string;
119
+ kind: SourceKind;
120
+ status: ImageStatus;
121
+ mounted: boolean;
122
+ cache: CacheVerdict | null;
123
+ cacheSource: "event" | "queryCache" | null;
124
+ ms: number | null;
125
+ intrinsic: {
126
+ width: number;
127
+ height: number;
128
+ } | null;
129
+ layout: {
130
+ width: number;
131
+ height: number;
132
+ } | null;
133
+ neededPx: {
134
+ width: number;
135
+ height: number;
136
+ } | null;
137
+ oversizeFactor: number | null;
138
+ decodedKB: number;
139
+ wastedKB: number;
140
+ progressSeen: boolean;
141
+ bytesTotal: number | null;
142
+ error: string | null;
143
+ errorCode: number | null;
144
+ loadCount: number;
145
+ overrideLabel: string | null;
146
+ hasAltText: boolean | null;
147
+ layoutShifts: number;
148
+ ageMs: number;
149
+ }
150
+ /** Full snapshot payload the adapter pushes to watching dashboards. */
151
+ export interface ImagesSnapshotPayload {
152
+ stats?: ImageStats;
153
+ captureStatus?: CaptureStatus;
154
+ globalModes?: {
155
+ network: "normal" | "offline" | "cold";
156
+ blank: boolean;
157
+ };
158
+ insights?: ImageInsights;
159
+ /** Newest first, capped at 100. */
160
+ records?: ImageRecordWire[];
161
+ }
109
162
  /** Capture capability report — what the interceptors managed to hook. */
110
163
  export interface CaptureStatus {
111
164
  installed: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/images",
3
- "version": "7.0.14",
3
+ "version": "7.0.15",
4
4
  "description": "Debug every image in your React Native app — live load registry, cache verdicts (memory/disk/network), timings, oversize + wasted-memory checks, and failure diagnosis for RN <Image> and expo-image. Part of Buoy devtools.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -18,7 +18,7 @@
18
18
  "**/index.*"
19
19
  ],
20
20
  "dependencies": {
21
- "@buoy-gg/shared-ui": "^7.0.14"
21
+ "@buoy-gg/shared-ui": "^7.0.15"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "expo-image": "*",
@@ -16,6 +16,7 @@
16
16
  * rewrites both cache tiers.
17
17
  */
18
18
 
19
+ import { remoteActionResult, remoteImagesDispatch } from "../remote";
19
20
  import { imagesState } from "../state";
20
21
  import { touchRecord } from "./overrides";
21
22
  import type { ImageRecord } from "../types";
@@ -147,6 +148,8 @@ export type ActionResult = { ok: boolean; message: string };
147
148
 
148
149
  /** Fresh load attempt for a mounted image (no cache bypass). */
149
150
  export async function retryRecord(record: ImageRecord): Promise<ActionResult> {
151
+ const remote = remoteImagesDispatch();
152
+ if (remote) return remoteActionResult(remote("retry", { id: record.id }));
150
153
  if (!record.mounted) {
151
154
  return { ok: false, message: "Instance unmounted — cannot reload" };
152
155
  }
@@ -166,6 +169,10 @@ export async function retryRecord(record: ImageRecord): Promise<ActionResult> {
166
169
 
167
170
  /** Hard-reload every mounted image (mass cache-bust). Returns count started. */
168
171
  export async function hardReloadAllMounted(): Promise<ActionResult> {
172
+ const remote = remoteImagesDispatch();
173
+ if (remote) {
174
+ return remoteActionResult(remote("massAction", { kind: "reload" }));
175
+ }
169
176
  const { getSnapshot } = require("../store/imageEventStore") as {
170
177
  getSnapshot: () => ImageRecord[];
171
178
  };
@@ -180,6 +187,8 @@ export async function hardReloadAllMounted(): Promise<ActionResult> {
180
187
 
181
188
  /** Bypass-cache reload: refetches from network and rewrites the caches. */
182
189
  export async function hardReloadRecord(record: ImageRecord): Promise<ActionResult> {
190
+ const remote = remoteImagesDispatch();
191
+ if (remote) return remoteActionResult(remote("hardReload", { id: record.id }));
183
192
  if (!record.mounted) {
184
193
  return { ok: false, message: "Instance unmounted — cannot reload" };
185
194
  }
@@ -17,13 +17,17 @@ import type { CaptureStatus } from "../types";
17
17
 
18
18
  export function installImageCapture(): void {
19
19
  const s = imagesState();
20
- if (s.captureInstalled) return;
20
+ // Mirror mode (desktop dashboard): never instrument the dashboard's own UI.
21
+ if (s.captureDisabled || s.captureInstalled) return;
21
22
  s.captureInstalled = true;
22
23
  installRnImageInterceptor();
23
24
  installExpoImageInterceptor();
24
25
  }
25
26
 
26
27
  export function getCaptureStatus(): CaptureStatus {
28
+ // Mirror mode: report the synced DEVICE's capture status, not the local probe.
29
+ const mirrored = imagesState().mirrorCaptureStatus;
30
+ if (mirrored) return mirrored;
27
31
  const rn = rnCaptureStatus();
28
32
  const expo = expoCaptureStatus();
29
33
  return {
@@ -14,6 +14,7 @@
14
14
  * directly via forceUpdate() from the per-record instance registry.
15
15
  */
16
16
 
17
+ import { remoteImagesDispatch } from "../remote";
17
18
  import { imagesState } from "../state";
18
19
  import { getRecord, getSnapshot, updateRecord } from "../store/imageEventStore";
19
20
  import type { ImageLib } from "../types";
@@ -106,18 +107,47 @@ export function getRenderVersion(recordId: number | null): number {
106
107
  // Per-record overrides
107
108
  // ---------------------------------------------------------------------------
108
109
 
109
- export function setOverride(recordId: number, override: ImageOverride): void {
110
+ /** Remote param shape for the adapter's `setOverride` action. */
111
+ function overrideParams(
112
+ recordId: number,
113
+ source: OverrideSource
114
+ ): Record<string, unknown> {
115
+ return source.type === "url"
116
+ ? { id: recordId, kind: "url", uri: source.uri }
117
+ : { id: recordId, kind: source.type };
118
+ }
119
+
120
+ /**
121
+ * Local bookkeeping only (no remote forward) — mirrors keep this as an
122
+ * optimistic UI update; the next device snapshot is the source of truth.
123
+ */
124
+ function setOverrideLocal(recordId: number, override: ImageOverride): void {
110
125
  imagesState().overrides.set(recordId, override);
111
126
  updateRecord(recordId, { overrideLabel: override.label });
112
127
  touchRecord(recordId);
113
128
  }
114
129
 
115
- export function clearOverride(recordId: number): void {
130
+ export function setOverride(recordId: number, override: ImageOverride): void {
131
+ if (override.source) {
132
+ void remoteImagesDispatch()?.(
133
+ "setOverride",
134
+ overrideParams(recordId, override.source)
135
+ );
136
+ }
137
+ setOverrideLocal(recordId, override);
138
+ }
139
+
140
+ function clearOverrideLocal(recordId: number): void {
116
141
  imagesState().overrides.delete(recordId);
117
142
  updateRecord(recordId, { overrideLabel: undefined });
118
143
  touchRecord(recordId);
119
144
  }
120
145
 
146
+ export function clearOverride(recordId: number): void {
147
+ void remoteImagesDispatch()?.("clearOverride", { id: recordId });
148
+ clearOverrideLocal(recordId);
149
+ }
150
+
121
151
  export function getOverride(recordId: number | null): ImageOverride | undefined {
122
152
  if (recordId == null) return undefined;
123
153
  return imagesState().overrides.get(recordId) as ImageOverride | undefined;
@@ -127,12 +157,19 @@ export function getOverride(recordId: number | null): ImageOverride | undefined
127
157
  // Mass actions (every mounted image at once)
128
158
  // ---------------------------------------------------------------------------
129
159
 
160
+ /** Adapter `massAction` kind for a mass override (per-record 'hang' is mass 'loading'). */
161
+ function massKind(source: OverrideSource): string {
162
+ return source.type === "hang" ? "loading" : source.type;
163
+ }
164
+
130
165
  /** Apply one override to every mounted record. Returns how many were hit. */
131
166
  export function setOverrideForAllMounted(source: OverrideSource, label: string): number {
167
+ // One mass action on the device, not N per-record forwards.
168
+ void remoteImagesDispatch()?.("massAction", { kind: massKind(source) });
132
169
  let count = 0;
133
170
  for (const record of getSnapshot()) {
134
171
  if (!record.mounted) continue;
135
- setOverride(record.id, { source, label });
172
+ setOverrideLocal(record.id, { source, label });
136
173
  count++;
137
174
  }
138
175
  return count;
@@ -140,24 +177,29 @@ export function setOverrideForAllMounted(source: OverrideSource, label: string):
140
177
 
141
178
  /** Remove every active override. Returns how many were cleared. */
142
179
  export function clearAllOverrides(): number {
143
- const ids = [...imagesState().overrides.keys()];
144
- for (const id of ids) clearOverride(id);
145
- return ids.length;
180
+ void remoteImagesDispatch()?.("massAction", { kind: "restore" });
181
+ // Mirrors track labels on records, not the overrides map — clear both.
182
+ const ids = new Set<number>(imagesState().overrides.keys());
183
+ for (const record of getSnapshot()) {
184
+ if (record.overrideLabel != null) ids.add(record.id);
185
+ }
186
+ for (const id of ids) clearOverrideLocal(id);
187
+ return ids.size;
146
188
  }
147
189
 
148
190
  /** Flash every mounted image (locate everything the tool tracks). */
149
191
  export function flashAllMounted(durationMs = 2500): number {
192
+ void remoteImagesDispatch()?.("massAction", { kind: "flash" });
150
193
  let count = 0;
151
194
  for (const record of getSnapshot()) {
152
195
  if (!record.mounted) continue;
153
- flashRecord(record.id, durationMs);
196
+ flashRecordLocal(record.id, durationMs);
154
197
  count++;
155
198
  }
156
199
  return count;
157
200
  }
158
201
 
159
- /** Flash a border on the on-screen image so it can be located visually. */
160
- export function flashRecord(recordId: number, durationMs = 2500): void {
202
+ function flashRecordLocal(recordId: number, durationMs = 2500): void {
161
203
  const s = imagesState();
162
204
  s.flashUntil.set(recordId, Date.now() + durationMs);
163
205
  touchRecord(recordId);
@@ -167,6 +209,12 @@ export function flashRecord(recordId: number, durationMs = 2500): void {
167
209
  }, durationMs + 50);
168
210
  }
169
211
 
212
+ /** Flash a border on the on-screen image so it can be located visually. */
213
+ export function flashRecord(recordId: number, durationMs = 2500): void {
214
+ void remoteImagesDispatch()?.("flash", { id: recordId });
215
+ flashRecordLocal(recordId, durationMs);
216
+ }
217
+
170
218
  // ---------------------------------------------------------------------------
171
219
  // Global simulation modes
172
220
  // ---------------------------------------------------------------------------
@@ -181,12 +229,14 @@ export function flashRecord(recordId: number, durationMs = 2500): void {
181
229
  * `cachePolicy:'none'`): first-launch behavior without clearing anything.
182
230
  */
183
231
  export function setNetworkMode(mode: NetworkMode): void {
232
+ void remoteImagesDispatch()?.("setNetworkMode", { mode });
184
233
  imagesState().globalModes.network = mode;
185
234
  touchGlobal();
186
235
  }
187
236
 
188
237
  /** Chrome-style "disable images": render every image without a source. */
189
238
  export function setBlankImages(enabled: boolean): void {
239
+ void remoteImagesDispatch()?.("setBlankImages", { enabled });
190
240
  imagesState().globalModes.blank = enabled;
191
241
  touchGlobal();
192
242
  }
@@ -12,6 +12,7 @@
12
12
  * total) → expo disk-cache file size → HEAD Content-Length.
13
13
  */
14
14
 
15
+ import { remoteImagesDispatch } from "../remote";
15
16
  import { neededPixels } from "../store/imageEventStore";
16
17
  import type { ImageRecord } from "../types";
17
18
 
@@ -104,6 +105,16 @@ async function resolveOriginalBytes(
104
105
  * Requires expo-image-manipulator (graceful message when absent).
105
106
  */
106
107
  export async function proveSavings(record: ImageRecord): Promise<SavingsResult> {
108
+ // Mirror mode: the re-encode must happen ON DEVICE (that's the whole point —
109
+ // real bytes from the real encoder); forward and relay the result.
110
+ const remote = remoteImagesDispatch();
111
+ if (remote) {
112
+ try {
113
+ return (await remote("proveSavings", { id: record.id })) as SavingsResult;
114
+ } catch (error) {
115
+ return { ok: false, message: String((error as Error)?.message ?? error) };
116
+ }
117
+ }
107
118
  if (!record.uri || (record.sourceKind !== "network" && record.sourceKind !== "asset" && record.sourceKind !== "file")) {
108
119
  return { ok: false, message: "No loadable source URI on this record" };
109
120
  }
@@ -8,7 +8,6 @@
8
8
  import { useState, type ReactNode } from "react";
9
9
  import {
10
10
  Image,
11
- PixelRatio,
12
11
  ScrollView,
13
12
  StyleSheet,
14
13
  Text,
@@ -20,7 +19,9 @@ import { macOSColors } from "@buoy-gg/shared-ui";
20
19
  import { clearOverride, setOverride } from "../capture/overrides";
21
20
  import { proveSavings } from "../capture/savings";
22
21
  import type { SavingsResult } from "../capture/savings";
22
+ import { remoteImagesDispatch } from "../remote";
23
23
  import {
24
+ effectivePixelRatio,
24
25
  estDecodedBytes,
25
26
  estWastedBytes,
26
27
  neededPixels,
@@ -54,6 +55,15 @@ function Row({ label, value, color }: { label: string; value: string; color?: st
54
55
 
55
56
  /** expo-image global cache clears (no-ops if expo-image is absent). */
56
57
  function clearExpoCache(kind: "memory" | "disk"): void {
58
+ // Mirror mode: the caches live on the synced device.
59
+ const remote = remoteImagesDispatch();
60
+ if (remote) {
61
+ void remote("clearExpoCaches", {
62
+ memory: kind === "memory",
63
+ disk: kind === "disk",
64
+ });
65
+ return;
66
+ }
57
67
  try {
58
68
  const expo = require("expo-image");
59
69
  if (kind === "memory") void expo.Image.clearMemoryCache();
@@ -289,7 +299,7 @@ export function ImageDetail({ record }: ImageDetailProps) {
289
299
  label="Displayed"
290
300
  value={`${Math.round(record.layout.width)}×${Math.round(record.layout.height)} dp (needs ${
291
301
  needed ? `${needed.width}×${needed.height}` : "?"
292
- } px @${PixelRatio.get()}x)`}
302
+ } px @${Math.round(effectivePixelRatio() * 100) / 100}x)`}
293
303
  />
294
304
  ) : (
295
305
  <Row label="Displayed" value="no layout yet" />