@abbox/inspector 0.4.16 → 0.4.19

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 +1 @@
1
- {"version":3,"file":"client-bridge.d.ts","sourceRoot":"","sources":["../../src/host/client-bridge.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CA4EjB"}
1
+ {"version":3,"file":"client-bridge.d.ts","sourceRoot":"","sources":["../../src/host/client-bridge.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAuFjB"}
@@ -1,5 +1,6 @@
1
1
  import { ABBBOX_INSPECTOR_CHANNEL, PROTOCOL_VERSION, } from "../protocol.js";
2
2
  import { createInspectorRuntime } from "../runtime/inspector.js";
3
+ import { createRecorder } from "../runtime/recorder.js";
3
4
  import { withLocalhostAliases } from "./origin-aliases.js";
4
5
  import { validateIncomingMessage } from "./validate.js";
5
6
  export function createInspectorClient(options) {
@@ -41,10 +42,12 @@ export function createInspectorClient(options) {
41
42
  onChangeMarkerSelected: (changeId) => {
42
43
  send({ ...base(), type: "CHANGE_MARKER_SELECTED", changeId });
43
44
  },
44
- onRouteChange: (path) => {
45
- send({ ...base(), type: "ROUTE_CHANGED", path });
45
+ onRouteChange: (path, href) => {
46
+ send({ ...base(), type: "ROUTE_CHANGED", path, href });
46
47
  },
47
48
  });
49
+ // The last minute of activity, for bug reports from the Viewer.
50
+ const recorder = createRecorder(win, doc);
48
51
  const onMessage = (event) => {
49
52
  const msg = validateIncomingMessage(event.data, event.origin, {
50
53
  sessionId: options.sessionId,
@@ -61,6 +64,14 @@ export function createInspectorClient(options) {
61
64
  if (msg.type === "APPLY_CHANGE_MARKERS") {
62
65
  runtime.setChangeMarkers(msg.markers);
63
66
  }
67
+ if (msg.type === "APPLY_STYLE_OVERRIDES") {
68
+ runtime.applyStyleOverrides(msg.elementRefId, msg.styles);
69
+ }
70
+ if (msg.type === "CLEAR_STYLE_OVERRIDES")
71
+ runtime.clearStyleOverrides();
72
+ if (msg.type === "REQUEST_RECORDING") {
73
+ send({ ...base(), type: "RECORDING", steps: recorder.snapshot() });
74
+ }
64
75
  };
65
76
  return {
66
77
  start: () => {
@@ -75,6 +86,7 @@ export function createInspectorClient(options) {
75
86
  },
76
87
  stop: () => {
77
88
  win.removeEventListener("message", onMessage);
89
+ recorder.stop();
78
90
  runtime.destroy();
79
91
  },
80
92
  };
@@ -1,4 +1,4 @@
1
- import { type ChangeMarker, type ClientToHostMessage } from "../protocol.js";
1
+ import { type ChangeMarker, type ClientToHostMessage, type RecordedStep } from "../protocol.js";
2
2
  export type InspectorHostOptions = {
3
3
  sessionId: string;
4
4
  /** Origin of the embedded preview (iframe src origin). */
@@ -19,7 +19,8 @@ export type InspectorHostHandlers = {
19
19
  onSelect?: (payload: SelectPayload) => void;
20
20
  onCleared?: () => void;
21
21
  onChangeMarkerSelected?: (changeId: string) => void;
22
- onRouteChange?: (path: string) => void;
22
+ onRouteChange?: (path: string, href?: string) => void;
23
+ onRecording?: (steps: RecordedStep[]) => void;
23
24
  onError?: (code: string, message: string) => void;
24
25
  };
25
26
  export type InspectorHost = {
@@ -30,6 +31,11 @@ export type InspectorHost = {
30
31
  applyChangeMarkers: (markers: ChangeMarker[]) => void;
31
32
  /** Ask the preview to resend INSPECTOR_READY (call after iframe load). */
32
33
  pingPreview: () => void;
34
+ /** Ask the preview for its last minute of activity (answered via onRecording). */
35
+ requestRecording: () => void;
36
+ /** Live Design tab edits on one element; an empty object restores it. Needs inspector 0.4.18 in the preview. */
37
+ applyStyleOverrides: (elementRefId: string, styles: Record<string, string>) => void;
38
+ clearStyleOverrides: () => void;
33
39
  };
34
40
  export declare function createInspectorHost(options: InspectorHostOptions, handlers: InspectorHostHandlers): InspectorHost;
35
41
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAGzB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,YAAY,GAAG,OAAO,CACzB,mBAAmB,EACnB;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAC5B,CAAC,SAAS,CAAC,CAAC;AACb,KAAK,aAAa,GAAG,OAAO,CAC1B,mBAAmB,EACnB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAC7B,CAAC,SAAS,CAAC,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACtD,0EAA0E;IAC1E,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,qBAAqB,GAC9B,aAAa,CAkEf"}
1
+ {"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAGxB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,YAAY,GAAG,OAAO,CACzB,mBAAmB,EACnB;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAC5B,CAAC,SAAS,CAAC,CAAC;AACb,KAAK,aAAa,GAAG,OAAO,CAC1B,mBAAmB,EACnB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAC7B,CAAC,SAAS,CAAC,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACtD,0EAA0E;IAC1E,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,gHAAgH;IAChH,mBAAmB,EAAE,CACnB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC3B,IAAI,CAAC;IACV,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,qBAAqB,GAC9B,aAAa,CA8Ef"}
@@ -36,7 +36,10 @@ export function createInspectorHost(options, handlers) {
36
36
  handlers.onChangeMarkerSelected?.(msg.changeId);
37
37
  }
38
38
  if (msg.type === "ROUTE_CHANGED") {
39
- handlers.onRouteChange?.(msg.path);
39
+ handlers.onRouteChange?.(msg.path, msg.href);
40
+ }
41
+ if (msg.type === "RECORDING") {
42
+ handlers.onRecording?.(msg.steps);
40
43
  }
41
44
  if (msg.type === "INSPECTOR_ERROR") {
42
45
  handlers.onError?.(msg.code, msg.message);
@@ -58,6 +61,15 @@ export function createInspectorHost(options, handlers) {
58
61
  applyChangeMarkers: (markers) => {
59
62
  send({ ...base(), type: "APPLY_CHANGE_MARKERS", markers });
60
63
  },
64
+ requestRecording: () => {
65
+ send({ ...base(), type: "REQUEST_RECORDING" });
66
+ },
67
+ applyStyleOverrides: (elementRefId, styles) => {
68
+ send({ ...base(), type: "APPLY_STYLE_OVERRIDES", elementRefId, styles });
69
+ },
70
+ clearStyleOverrides: () => {
71
+ send({ ...base(), type: "CLEAR_STYLE_OVERRIDES" });
72
+ },
61
73
  pingPreview: () => {
62
74
  send({
63
75
  ...base(),
@@ -74,6 +74,11 @@ export type ElementHoverPayload = {
74
74
  metadata: ElementMetadata;
75
75
  source: SourceLocation | null;
76
76
  mappingStatus: SourceMappingStatus;
77
+ /**
78
+ * Computed styles of a selected element, for the Design tab (CSS property →
79
+ * value). Sent with selections only; absent from inspectors before 0.4.18.
80
+ */
81
+ styles?: Record<string, string>;
77
82
  };
78
83
  export type ElementSelectedPayload = ElementHoverPayload;
79
84
  export type ElementHoveredMessage = InspectorMessageBase & {
@@ -117,17 +122,57 @@ export type ChangeMarkerSelectedMessage = InspectorMessageBase & {
117
122
  type: "CHANGE_MARKER_SELECTED";
118
123
  changeId: string;
119
124
  };
120
- /** The preview navigated (client-side or full load) to a new path. */
125
+ /** The preview navigated (client-side or full load) to a new path or query. */
121
126
  export type RouteChangedMessage = InspectorMessageBase & {
122
127
  type: "ROUTE_CHANGED";
123
128
  path: string;
129
+ /** The full address, query included (0.4.19+), so the Viewer can show and restore it. */
130
+ href?: string;
124
131
  };
125
132
  export type ApplyChangeMarkersMessage = InspectorMessageBase & {
126
133
  type: "APPLY_CHANGE_MARKERS";
127
134
  markers: ChangeMarker[];
128
135
  };
129
- export type HostToClientMessage = InspectorEnableMessage | InspectorDisableMessage | InspectorHostHelloMessage | ApplyChangeMarkersMessage;
130
- export type ClientToHostMessage = InspectorReadyMessage | ElementHoveredMessage | ElementSelectedMessage | ElementClearedMessage | ChangeMarkerSelectedMessage | RouteChangedMessage | InspectorErrorMessage;
136
+ /** One step from the preview's last minute, for bug reports. Never holds field values. */
137
+ export type RecordedStep = {
138
+ /** Epoch milliseconds. */
139
+ t: number;
140
+ kind: "click" | "submit" | "input" | "route" | "scroll" | "error" | "console-error" | "request";
141
+ route: string;
142
+ label?: string;
143
+ /** `data-abbox-src` of the element, linking the step to code. */
144
+ source?: string;
145
+ tag?: string;
146
+ detail?: string;
147
+ /** Script location of an error, when the browser reports one. */
148
+ location?: string;
149
+ method?: string;
150
+ url?: string;
151
+ status?: number;
152
+ durationMs?: number;
153
+ };
154
+ /**
155
+ * Live style edits from the Design tab: replaces any earlier overrides on the
156
+ * element; an empty `styles` restores it. Only design properties are applied.
157
+ */
158
+ export type ApplyStyleOverridesMessage = InspectorMessageBase & {
159
+ type: "APPLY_STYLE_OVERRIDES";
160
+ elementRefId: string;
161
+ styles: Record<string, string>;
162
+ };
163
+ /** Removes every live style edit, e.g. when the Design tab closes. */
164
+ export type ClearStyleOverridesMessage = InspectorMessageBase & {
165
+ type: "CLEAR_STYLE_OVERRIDES";
166
+ };
167
+ export type RequestRecordingMessage = InspectorMessageBase & {
168
+ type: "REQUEST_RECORDING";
169
+ };
170
+ export type RecordingMessage = InspectorMessageBase & {
171
+ type: "RECORDING";
172
+ steps: RecordedStep[];
173
+ };
174
+ export type HostToClientMessage = InspectorEnableMessage | InspectorDisableMessage | InspectorHostHelloMessage | ApplyChangeMarkersMessage | ApplyStyleOverridesMessage | ClearStyleOverridesMessage | RequestRecordingMessage;
175
+ export type ClientToHostMessage = InspectorReadyMessage | ElementHoveredMessage | ElementSelectedMessage | ElementClearedMessage | ChangeMarkerSelectedMessage | RouteChangedMessage | RecordingMessage | InspectorErrorMessage;
131
176
  export type InspectorMessage = HostToClientMessage | ClientToHostMessage;
132
177
  export declare function isInspectorMessage(value: unknown): value is InspectorMessage;
133
178
  //# sourceMappingURL=protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAE3C,eAAO,MAAM,wBAAwB,EAAG,iBAA0B,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,wBAAwB,CAAC;IACzC,OAAO,EAAE,OAAO,gBAAgB,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,IAAI,EAAE,iBAAiB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GAAG;IAC1D,IAAI,EAAE,kBAAkB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,GAAG;IAC3D,IAAI,EAAE,mBAAmB,CAAC;CAC3B,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,IAAI,EAAE,sBAAsB,CAAC;IAC7B,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,mBAAmB,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE,mBAAmB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GAAG;IAC1D,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,sBAAsB,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,IAAI,EAAE,iBAAiB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,YAAY,GACZ,cAAc,GACd,iBAAiB,GACjB,WAAW,GACX,aAAa,GACb,UAAU,GACV,UAAU,GACV,WAAW,GACX,WAAW,GACX,YAAY,CAAC;AAEjB,2FAA2F;AAC3F,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,gBAAgB,CAAC;IACvB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,GAAG;IAC/D,IAAI,EAAE,wBAAwB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,GAAG;IACvD,IAAI,EAAE,eAAe,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,YAAY,GAAG;IACzB,0BAA0B;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EACA,OAAO,GACP,QAAQ,GACR,OAAO,GACP,OAAO,GACP,QAAQ,GACR,OAAO,GACP,eAAe,GACf,SAAS,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,oBAAoB,GAAG;IAC9D,IAAI,EAAE,uBAAuB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,0BAA0B,GAAG,oBAAoB,GAAG;IAC9D,IAAI,EAAE,uBAAuB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,GAAG;IAC3D,IAAI,EAAE,mBAAmB,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG;IACpD,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,sBAAsB,GACtB,uBAAuB,GACvB,yBAAyB,GACzB,yBAAyB,GACzB,0BAA0B,GAC1B,0BAA0B,GAC1B,uBAAuB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAC3B,qBAAqB,GACrB,qBAAqB,GACrB,sBAAsB,GACtB,qBAAqB,GACrB,2BAA2B,GAC3B,mBAAmB,GACnB,gBAAgB,GAChB,qBAAqB,CAAC;AAE1B,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAEzE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAS5E"}
@@ -6,7 +6,7 @@ export type InspectorRuntimeOptions = {
6
6
  onSelect?: (payload: ElementHoverPayload) => void;
7
7
  onClear?: () => void;
8
8
  onChangeMarkerSelected?: (changeId: string) => void;
9
- onRouteChange?: (path: string) => void;
9
+ onRouteChange?: (path: string, href: string) => void;
10
10
  };
11
11
  export type InspectorRuntime = {
12
12
  enable: () => void;
@@ -14,6 +14,9 @@ export type InspectorRuntime = {
14
14
  isEnabled: () => boolean;
15
15
  setChangeMarkers: (markers: ChangeMarker[]) => void;
16
16
  getElementByRefId: (refId: string) => Element | null;
17
+ /** Live Design tab edits; an empty object restores the element. */
18
+ applyStyleOverrides: (refId: string, styles: Record<string, string>) => void;
19
+ clearStyleOverrides: () => void;
17
20
  destroy: () => void;
18
21
  };
19
22
  export declare function createInspectorRuntime(options: InspectorRuntimeOptions): InspectorRuntime;
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../src/runtime/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAyCxE,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,gBAAgB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,CAsWlB"}
1
+ {"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../src/runtime/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA0CxE,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,gBAAgB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,mEAAmE;IACnE,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC7E,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,CA4XlB"}
@@ -1,6 +1,7 @@
1
1
  import { runtimeElementRefFor } from "./element-ref.js";
2
2
  import { extractMetadata, findSourceLocation, sourceKeyFor, sourceMappingStatus, } from "./metadata.js";
3
3
  import { createOverlay, hideHighlight, hideSelectedHighlight, positionHighlight, positionSelectedHighlight, rectForElement, renderChangeMarkers, } from "./overlay.js";
4
+ import { createStyleOverrides, readDesignStyles } from "./styles.js";
4
5
  const SOURCE_LINE_TOLERANCE = 2;
5
6
  function parseSourceKey(raw) {
6
7
  const m = raw ? /^(.*?):(\d+)(?::\d+)?$/.exec(raw) : null;
@@ -105,7 +106,8 @@ export function createInspectorRuntime(options) {
105
106
  renderChangeMarkers(overlay.markerLayer, markers, (id) => refIdToElement.get(id) ?? null, options.onChangeMarkerSelected);
106
107
  }
107
108
  };
108
- function payloadFor(el) {
109
+ const styleOverrides = createStyleOverrides();
110
+ function payloadFor(el, withStyles = false) {
109
111
  const source = findSourceLocation(el);
110
112
  const sourceKey = sourceKeyFor(source);
111
113
  const elementRef = runtimeElementRefFor(el, sourceKey);
@@ -116,6 +118,7 @@ export function createInspectorRuntime(options) {
116
118
  metadata: extractMetadata(el),
117
119
  source,
118
120
  mappingStatus: sourceMappingStatus(source),
121
+ ...(withStyles ? { styles: readDesignStyles(el, win) } : {}),
119
122
  };
120
123
  }
121
124
  function setHover(el) {
@@ -138,7 +141,7 @@ export function createInspectorRuntime(options) {
138
141
  return;
139
142
  reportedSize = sizeKey(selected);
140
143
  positionSelectedHighlight(overlay.selectedHighlight, rectForElement(selected));
141
- options.onSelect?.(payloadFor(selected));
144
+ options.onSelect?.(payloadFor(selected, true));
142
145
  });
143
146
  function select(el) {
144
147
  selected = el;
@@ -148,7 +151,7 @@ export function createInspectorRuntime(options) {
148
151
  sizeObserver?.disconnect();
149
152
  reportedSize = sizeKey(el);
150
153
  sizeObserver?.observe(el);
151
- options.onSelect?.(payloadFor(el));
154
+ options.onSelect?.(payloadFor(el, true));
152
155
  }
153
156
  const onCurrentRoute = (marker) => !marker.route ||
154
157
  win.location.pathname.replace(/\/$/, "") ===
@@ -250,6 +253,7 @@ export function createInspectorRuntime(options) {
250
253
  }
251
254
  }
252
255
  let lastPath = win.location.pathname;
256
+ let lastSearch = win.location.search;
253
257
  let refreshFrame = 0;
254
258
  /** SPA navigation and async data swap the DOM after markers arrive. */
255
259
  const scheduleRefresh = () => {
@@ -259,10 +263,16 @@ export function createInspectorRuntime(options) {
259
263
  refreshFrame = 0;
260
264
  if (win.location.pathname !== lastPath) {
261
265
  lastPath = win.location.pathname;
266
+ lastSearch = win.location.search;
262
267
  // The selection belonged to the previous page.
263
268
  if (selected)
264
269
  clearSelection();
265
- options.onRouteChange?.(lastPath);
270
+ options.onRouteChange?.(lastPath, win.location.href);
271
+ }
272
+ else if (win.location.search !== lastSearch) {
273
+ // Same page, new query (filters, tabs): report it, keep the selection.
274
+ lastSearch = win.location.search;
275
+ options.onRouteChange?.(lastPath, win.location.href);
266
276
  }
267
277
  else if (selected && !selected.isConnected) {
268
278
  // The app re-rendered without the selected element.
@@ -326,7 +336,18 @@ export function createInspectorRuntime(options) {
326
336
  refreshMarkers();
327
337
  },
328
338
  getElementByRefId: (id) => refIdToElement.get(id) ?? null,
339
+ applyStyleOverrides: (id, styles) => {
340
+ const el = refIdToElement.get(id);
341
+ if (!el?.isConnected)
342
+ return;
343
+ styleOverrides.apply(el, styles);
344
+ if (el === selected) {
345
+ positionSelectedHighlight(overlay.selectedHighlight, rectForElement(el));
346
+ }
347
+ },
348
+ clearStyleOverrides: () => styleOverrides.clearAll(),
329
349
  destroy: () => {
350
+ styleOverrides.clearAll();
330
351
  if (enabled) {
331
352
  enabled = false;
332
353
  teardownListeners();
@@ -0,0 +1,15 @@
1
+ import type { RecordedStep } from "../protocol.js";
2
+ /** Like a dashcam: only the last minute is kept. */
3
+ export declare const RECORDING_WINDOW_MS = 60000;
4
+ export type Recorder = {
5
+ snapshot: () => RecordedStep[];
6
+ stop: () => void;
7
+ };
8
+ /**
9
+ * Records the last minute of what happened in the page so a bug report can
10
+ * show the steps that led to it. Field values, request bodies and query strings
11
+ * are never stored. Lives only in this tab's sessionStorage, so it survives a
12
+ * reload and never leaves the page until the Viewer asks for it.
13
+ */
14
+ export declare function createRecorder(win: Window, doc: Document): Recorder;
15
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/runtime/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAK1C,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,MAAM,YAAY,EAAE,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AA+EF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,QAAQ,CAuLnE"}
@@ -0,0 +1,245 @@
1
+ import { ABBBOX_SOURCE_ATTR } from "../instrument/constants.js";
2
+ /** Like a dashcam: only the last minute is kept. */
3
+ export const RECORDING_WINDOW_MS = 60_000;
4
+ const STORAGE_KEY = "abbox:recording";
5
+ const MAX_STEPS = 400;
6
+ const LABEL_LIMIT = 80;
7
+ function readStored(win) {
8
+ try {
9
+ const raw = win.sessionStorage.getItem(STORAGE_KEY);
10
+ return raw ? JSON.parse(raw) : [];
11
+ }
12
+ catch {
13
+ return [];
14
+ }
15
+ }
16
+ function labelOf(el) {
17
+ // Prefer what a person would call the control: its label, then its hint or name.
18
+ const field = el instanceof HTMLInputElement ||
19
+ el instanceof HTMLTextAreaElement ||
20
+ el instanceof HTMLSelectElement
21
+ ? el
22
+ : null;
23
+ const fieldHint = field instanceof HTMLSelectElement
24
+ ? field.name
25
+ : field
26
+ ? field.placeholder || field.name
27
+ : null;
28
+ const text = el.getAttribute("aria-label") ??
29
+ field?.labels?.[0]?.textContent ??
30
+ fieldHint ??
31
+ el.textContent;
32
+ const trimmed = text?.replace(/\s+/g, " ").trim();
33
+ return trimmed ? trimmed.slice(0, LABEL_LIMIT) : undefined;
34
+ }
35
+ /**
36
+ * What was entered, without the content: enough for a diagnosis to tell a
37
+ * single word from a phrase, or an empty field from a filled one.
38
+ */
39
+ function shapeOf(el) {
40
+ if (el instanceof HTMLSelectElement) {
41
+ return `selected option ${el.selectedIndex + 1} of ${el.options.length}`;
42
+ }
43
+ if (el instanceof HTMLInputElement &&
44
+ (el.type === "checkbox" || el.type === "radio")) {
45
+ return el.checked ? "checked" : "unchecked";
46
+ }
47
+ const text = el.value.trim();
48
+ if (text === "")
49
+ return "cleared";
50
+ const words = text.split(/\s+/).length;
51
+ const kind = el instanceof HTMLInputElement && !["text", "search"].includes(el.type)
52
+ ? `${el.type}, `
53
+ : "";
54
+ return `filled: ${kind}${words} word${words === 1 ? "" : "s"}, ${text.length} characters`;
55
+ }
56
+ function sourceOf(el) {
57
+ return (el.closest(`[${ABBBOX_SOURCE_ATTR}]`)?.getAttribute(ABBBOX_SOURCE_ATTR) ??
58
+ undefined);
59
+ }
60
+ /** Path only: query strings and fragments can carry tokens or personal data. */
61
+ function pathOf(url, base) {
62
+ try {
63
+ const parsed = new URL(url, base);
64
+ return parsed.origin === new URL(base).origin
65
+ ? parsed.pathname
66
+ : `${parsed.origin}${parsed.pathname}`;
67
+ }
68
+ catch {
69
+ return url.split(/[?#]/)[0] ?? url;
70
+ }
71
+ }
72
+ /**
73
+ * Records the last minute of what happened in the page so a bug report can
74
+ * show the steps that led to it. Field values, request bodies and query strings
75
+ * are never stored. Lives only in this tab's sessionStorage, so it survives a
76
+ * reload and never leaves the page until the Viewer asks for it.
77
+ */
78
+ export function createRecorder(win, doc) {
79
+ let steps = readStored(win);
80
+ const prune = (now) => {
81
+ steps = steps
82
+ .filter((step) => now - step.t <= RECORDING_WINDOW_MS)
83
+ .slice(-MAX_STEPS);
84
+ };
85
+ const push = (step) => {
86
+ const now = Date.now();
87
+ steps.push({ t: now, route: win.location.pathname, ...step });
88
+ prune(now);
89
+ try {
90
+ win.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(steps));
91
+ }
92
+ catch {
93
+ // Storage full or blocked: the in-memory copy still works for this page.
94
+ }
95
+ };
96
+ const onClick = (event) => {
97
+ const target = event.target instanceof Element ? event.target : null;
98
+ const el = target?.closest("button, a, [role=button], input, select, label") ??
99
+ target;
100
+ if (!el)
101
+ return;
102
+ push({
103
+ kind: "click",
104
+ label: labelOf(el),
105
+ source: sourceOf(el),
106
+ tag: el.tagName.toLowerCase(),
107
+ });
108
+ };
109
+ const onSubmit = (event) => {
110
+ const form = event.target instanceof Element ? event.target : null;
111
+ if (!form)
112
+ return;
113
+ const submitter = event.submitter;
114
+ push({
115
+ kind: "submit",
116
+ label: submitter ? labelOf(submitter) : labelOf(form),
117
+ source: sourceOf(form),
118
+ tag: "form",
119
+ });
120
+ };
121
+ const onChange = (event) => {
122
+ const el = event.target;
123
+ if (!(el instanceof HTMLInputElement ||
124
+ el instanceof HTMLTextAreaElement ||
125
+ el instanceof HTMLSelectElement))
126
+ return;
127
+ if (el instanceof HTMLInputElement && el.type === "password")
128
+ return;
129
+ push({
130
+ kind: "input",
131
+ label: labelOf(el) ?? el.name ?? undefined,
132
+ source: sourceOf(el),
133
+ tag: el.tagName.toLowerCase(),
134
+ detail: shapeOf(el),
135
+ });
136
+ };
137
+ let lastScroll = 0;
138
+ const onScroll = () => {
139
+ const now = Date.now();
140
+ if (now - lastScroll < 1000)
141
+ return;
142
+ lastScroll = now;
143
+ const max = doc.documentElement.scrollHeight - win.innerHeight;
144
+ const pct = max > 0 ? Math.round((win.scrollY / max) * 100) : 0;
145
+ push({ kind: "scroll", detail: `${pct}% down` });
146
+ };
147
+ let lastPath = win.location.pathname;
148
+ const routeTimer = win.setInterval(() => {
149
+ if (win.location.pathname === lastPath)
150
+ return;
151
+ lastPath = win.location.pathname;
152
+ push({ kind: "route", detail: lastPath });
153
+ }, 300);
154
+ const onError = (event) => {
155
+ push({
156
+ kind: "error",
157
+ detail: String(event.message).slice(0, 300),
158
+ ...(event.filename
159
+ ? {
160
+ location: `${pathOf(event.filename, win.location.href)}:${event.lineno}`,
161
+ }
162
+ : {}),
163
+ });
164
+ };
165
+ const onRejection = (event) => {
166
+ const reason = event.reason instanceof Error
167
+ ? event.reason.message
168
+ : String(event.reason);
169
+ push({
170
+ kind: "error",
171
+ detail: `Unhandled rejection: ${reason}`.slice(0, 300),
172
+ });
173
+ };
174
+ const originalConsoleError = console.error;
175
+ console.error = (...args) => {
176
+ push({
177
+ kind: "console-error",
178
+ detail: args
179
+ .map((a) => a instanceof Error ? a.message : typeof a === "string" ? a : "")
180
+ .join(" ")
181
+ .trim()
182
+ .slice(0, 300),
183
+ });
184
+ originalConsoleError.apply(console, args);
185
+ };
186
+ const originalFetch = win.fetch;
187
+ win.fetch = async (input, init) => {
188
+ const started = Date.now();
189
+ const url = typeof input === "string"
190
+ ? input
191
+ : input instanceof URL
192
+ ? input.href
193
+ : input.url;
194
+ // Framework dev tooling (Next.js overlay, HMR) is not the app's activity.
195
+ if (/\/(__nextjs|_next\/)/.test(url)) {
196
+ return originalFetch.call(win, input, init);
197
+ }
198
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
199
+ try {
200
+ const response = await originalFetch.call(win, input, init);
201
+ push({
202
+ kind: "request",
203
+ method,
204
+ url: pathOf(url, win.location.href),
205
+ status: response.status,
206
+ durationMs: Date.now() - started,
207
+ });
208
+ return response;
209
+ }
210
+ catch (error) {
211
+ push({
212
+ kind: "request",
213
+ method,
214
+ url: pathOf(url, win.location.href),
215
+ status: 0,
216
+ detail: error instanceof Error ? error.message : "Network error",
217
+ durationMs: Date.now() - started,
218
+ });
219
+ throw error;
220
+ }
221
+ };
222
+ doc.addEventListener("click", onClick, true);
223
+ doc.addEventListener("submit", onSubmit, true);
224
+ doc.addEventListener("change", onChange, true);
225
+ win.addEventListener("scroll", onScroll, { passive: true });
226
+ win.addEventListener("error", onError);
227
+ win.addEventListener("unhandledrejection", onRejection);
228
+ return {
229
+ snapshot: () => {
230
+ prune(Date.now());
231
+ return [...steps];
232
+ },
233
+ stop: () => {
234
+ doc.removeEventListener("click", onClick, true);
235
+ doc.removeEventListener("submit", onSubmit, true);
236
+ doc.removeEventListener("change", onChange, true);
237
+ win.removeEventListener("scroll", onScroll);
238
+ win.removeEventListener("error", onError);
239
+ win.removeEventListener("unhandledrejection", onRejection);
240
+ win.clearInterval(routeTimer);
241
+ console.error = originalConsoleError;
242
+ win.fetch = originalFetch;
243
+ },
244
+ };
245
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Computed styles the Viewer's Design tab shows and edits, and live overrides
3
+ * of them. Overrides are inline `!important` declarations so they win over
4
+ * class-based styles; the element's original `style` attribute is restored
5
+ * when they are cleared.
6
+ */
7
+ export declare const DESIGN_STYLE_PROPERTIES: readonly ["display", "position", "width", "height", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin-top", "margin-right", "margin-bottom", "margin-left", "gap", "flex-direction", "justify-content", "align-items", "background-color", "background-image", "opacity", "border-top-width", "border-top-style", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "box-shadow", "color", "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "text-align", "text-transform", "text-decoration-line"];
8
+ export declare function readDesignStyles(el: Element, win: Window): Record<string, string>;
9
+ export type StyleOverrides = {
10
+ /** Replaces all overrides on `el`; an empty object restores it. */
11
+ apply: (el: Element, styles: Record<string, string>) => void;
12
+ clearAll: () => void;
13
+ };
14
+ export declare function createStyleOverrides(): StyleOverrides;
15
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/runtime/styles.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,0mBAqC1B,CAAC;AAIX,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAYD,MAAM,MAAM,cAAc,GAAG;IAC3B,mEAAmE;IACnE,KAAK,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC7D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,wBAAgB,oBAAoB,IAAI,cAAc,CA+BrD"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Computed styles the Viewer's Design tab shows and edits, and live overrides
3
+ * of them. Overrides are inline `!important` declarations so they win over
4
+ * class-based styles; the element's original `style` attribute is restored
5
+ * when they are cleared.
6
+ */
7
+ export const DESIGN_STYLE_PROPERTIES = [
8
+ "display",
9
+ "position",
10
+ "width",
11
+ "height",
12
+ "padding-top",
13
+ "padding-right",
14
+ "padding-bottom",
15
+ "padding-left",
16
+ "margin-top",
17
+ "margin-right",
18
+ "margin-bottom",
19
+ "margin-left",
20
+ "gap",
21
+ "flex-direction",
22
+ "justify-content",
23
+ "align-items",
24
+ "background-color",
25
+ "background-image",
26
+ "opacity",
27
+ "border-top-width",
28
+ "border-top-style",
29
+ "border-top-color",
30
+ "border-top-left-radius",
31
+ "border-top-right-radius",
32
+ "border-bottom-right-radius",
33
+ "border-bottom-left-radius",
34
+ "box-shadow",
35
+ "color",
36
+ "font-family",
37
+ "font-size",
38
+ "font-weight",
39
+ "line-height",
40
+ "letter-spacing",
41
+ "text-align",
42
+ "text-transform",
43
+ "text-decoration-line",
44
+ ];
45
+ const ALLOWED = new Set(DESIGN_STYLE_PROPERTIES);
46
+ export function readDesignStyles(el, win) {
47
+ const computed = win.getComputedStyle(el);
48
+ const styles = {};
49
+ for (const property of DESIGN_STYLE_PROPERTIES) {
50
+ styles[property] = computed.getPropertyValue(property).trim();
51
+ }
52
+ return styles;
53
+ }
54
+ /** Shorthands the Design tab may send besides the longhands above. */
55
+ const SHORTHANDS = new Set([
56
+ "padding",
57
+ "margin",
58
+ "border-width",
59
+ "border-style",
60
+ "border-color",
61
+ "border-radius",
62
+ ]);
63
+ export function createStyleOverrides() {
64
+ const originals = new Map();
65
+ const restore = (el) => {
66
+ if (!originals.has(el))
67
+ return;
68
+ const original = originals.get(el) ?? null;
69
+ if (original === null)
70
+ el.removeAttribute("style");
71
+ else
72
+ el.setAttribute("style", original);
73
+ originals.delete(el);
74
+ };
75
+ return {
76
+ apply: (el, styles) => {
77
+ if (!(el instanceof HTMLElement || el instanceof SVGElement))
78
+ return;
79
+ restore(el);
80
+ const entries = Object.entries(styles).filter(([property, value]) => (ALLOWED.has(property) || SHORTHANDS.has(property)) &&
81
+ typeof value === "string" &&
82
+ value.length <= 500);
83
+ if (entries.length === 0)
84
+ return;
85
+ originals.set(el, el.getAttribute("style"));
86
+ for (const [property, value] of entries) {
87
+ el.style.setProperty(property, value, "important");
88
+ }
89
+ },
90
+ clearAll: () => {
91
+ for (const el of [...originals.keys()])
92
+ restore(el);
93
+ },
94
+ };
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abbox/inspector",
3
- "version": "0.4.16",
3
+ "version": "0.4.19",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,12 +0,0 @@
1
- import type { ChangeMarker } from "../protocol.js";
2
- export type DomMatchConfidence = "exact" | "probable" | "ambiguous" | "unresolved";
3
- export type ChangeMarkerResolution = {
4
- changeId: string;
5
- confidence: DomMatchConfidence;
6
- candidateCount: number;
7
- };
8
- export declare function resolveChangeMarker(marker: ChangeMarker, doc: Document, pathname: string, refIdToElement: Map<string, Element>): {
9
- element: Element | null;
10
- resolution: ChangeMarkerResolution;
11
- };
12
- //# sourceMappingURL=resolve-change-marker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolve-change-marker.d.ts","sourceRoot":"","sources":["../../src/runtime/resolve-change-marker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC;AAEnF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAiCF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC;IAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAqEjE"}
@@ -1,93 +0,0 @@
1
- function routeMatches(markerRoute, pathname) {
2
- if (!markerRoute?.trim())
3
- return true;
4
- const route = markerRoute.trim();
5
- if (route === pathname)
6
- return true;
7
- if (route.endsWith("*") && pathname.startsWith(route.slice(0, -1)))
8
- return true;
9
- return false;
10
- }
11
- function collectSourcePrefixMatches(doc, prefix) {
12
- const matches = [];
13
- const nodes = doc.querySelectorAll("[data-abbox-src]");
14
- for (const node of nodes) {
15
- const attr = node.getAttribute("data-abbox-src");
16
- if (attr?.startsWith(prefix)) {
17
- matches.push(node);
18
- }
19
- }
20
- return matches;
21
- }
22
- function collectElementIdMatches(doc, elementId) {
23
- const escaped = elementId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
24
- const byId = doc.querySelectorAll(`[id="${escaped}"]`);
25
- if (byId.length > 0)
26
- return [...byId];
27
- const byData = doc.querySelectorAll(`[data-abbox-element-id="${escaped}"]`);
28
- return [...byData];
29
- }
30
- export function resolveChangeMarker(marker, doc, pathname, refIdToElement) {
31
- const base = {
32
- changeId: marker.elementRefId,
33
- confidence: "unresolved",
34
- candidateCount: 0,
35
- };
36
- if (!routeMatches(marker.route, pathname)) {
37
- return { element: null, resolution: base };
38
- }
39
- const byRef = refIdToElement.get(marker.elementRefId);
40
- if (byRef) {
41
- return {
42
- element: byRef,
43
- resolution: { ...base, confidence: "exact", candidateCount: 1 },
44
- };
45
- }
46
- let candidates = [];
47
- if (marker.compilerElementId?.trim()) {
48
- candidates = collectElementIdMatches(doc, marker.compilerElementId.trim());
49
- if (candidates.length === 1) {
50
- return {
51
- element: candidates[0],
52
- resolution: { ...base, confidence: "exact", candidateCount: 1 },
53
- };
54
- }
55
- if (candidates.length > 1) {
56
- return {
57
- element: null,
58
- resolution: { ...base, confidence: "ambiguous", candidateCount: candidates.length },
59
- };
60
- }
61
- }
62
- const prefix = marker.sourceKeyPrefix?.trim();
63
- if (prefix) {
64
- candidates = collectSourcePrefixMatches(doc, prefix);
65
- if (marker.interactionId) {
66
- // Narrow by label hint when multiple nodes share a source line.
67
- const label = marker.label.toLowerCase();
68
- const narrowed = candidates.filter((el) => {
69
- const text = (el.textContent ?? "").toLowerCase();
70
- return label.length > 0 && text.includes(label.slice(0, 24));
71
- });
72
- if (narrowed.length === 1) {
73
- return {
74
- element: narrowed[0],
75
- resolution: { ...base, confidence: "probable", candidateCount: 1 },
76
- };
77
- }
78
- }
79
- if (candidates.length === 1) {
80
- return {
81
- element: candidates[0],
82
- resolution: { ...base, confidence: "probable", candidateCount: 1 },
83
- };
84
- }
85
- if (candidates.length > 1) {
86
- return {
87
- element: null,
88
- resolution: { ...base, confidence: "ambiguous", candidateCount: candidates.length },
89
- };
90
- }
91
- }
92
- return { element: null, resolution: base };
93
- }