@replohq/sandbox-runtime 1.19.5 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/PagePreviewHeightResponder.d.ts +1 -0
  2. package/PagePreviewHeightResponder.js +159 -0
  3. package/_vendor/schemas/comments.d.ts +898 -0
  4. package/_vendor/schemas/commentsOverlayRpc.d.ts +348 -0
  5. package/_vendor/schemas/commentsOverlayRpc.mjs +92 -0
  6. package/_vendor/schemas/generated/comments.d.ts +237 -0
  7. package/_vendor/schemas/generated/commentsOverlayRpc.d.ts +18 -0
  8. package/_vendor/schemas/generated/selectMode.d.ts +3 -1
  9. package/_vendor/schemas/pagePreviewHeight.d.ts +14 -0
  10. package/_vendor/schemas/pagePreviewHeight.mjs +18 -0
  11. package/_vendor/schemas/selectMode.d.ts +113 -0
  12. package/_vendor/schemas/selectMode.mjs +1 -1
  13. package/_vendor/schemas/selectModeIframeRpc.d.ts +226 -0
  14. package/_vendor/schemas/selectModeIframeRpc.mjs +1 -1
  15. package/buildMetadata.js +3 -3
  16. package/chunk-NVOBD46N.mjs +541 -0
  17. package/comments-overlay/CommentsOverlayMount.d.ts +1 -0
  18. package/comments-overlay/CommentsOverlayMount.js +44 -0
  19. package/comments-overlay/LICENSE-THIRD-PARTY.md +45 -0
  20. package/comments-overlay/bridge.d.ts +11 -0
  21. package/comments-overlay/bridge.js +136 -0
  22. package/comments-overlay/capture.d.ts +55 -0
  23. package/comments-overlay/capture.js +250 -0
  24. package/comments-overlay/originGate.d.ts +1 -0
  25. package/comments-overlay/originGate.js +11 -0
  26. package/comments-overlay/overlay.d.ts +52 -0
  27. package/comments-overlay/overlay.js +781 -0
  28. package/comments-overlay/resolve.d.ts +27 -0
  29. package/comments-overlay/resolve.js +201 -0
  30. package/comments-overlay/sourceLookup.d.ts +11 -0
  31. package/comments-overlay/sourceLookup.js +63 -0
  32. package/comments-overlay/textQuote.d.ts +17 -0
  33. package/comments-overlay/textQuote.js +87 -0
  34. package/comments-overlay/vendor/bippy.d.ts +53 -0
  35. package/comments-overlay/vendor/bippy.js +402 -0
  36. package/comments-overlay/vendor/elementAnchors.d.ts +9 -0
  37. package/comments-overlay/vendor/elementAnchors.js +144 -0
  38. package/comments-overlay/vendor/fiberElement.d.ts +21 -0
  39. package/comments-overlay/vendor/fiberElement.js +251 -0
  40. package/comments-overlay/vendor/htmlPreview.d.ts +13 -0
  41. package/comments-overlay/vendor/htmlPreview.js +179 -0
  42. package/comments-overlay/vendor/selector.d.ts +38 -0
  43. package/comments-overlay/vendor/selector.js +372 -0
  44. package/package.json +5 -4
  45. package/pinPreviewViewportUnits.d.ts +4 -0
  46. package/pinPreviewViewportUnits.js +76 -0
  47. package/sandbox-runtime.js +4 -0
  48. package/select-mode/descriptor/elementDescriptor.d.ts +4 -3
  49. package/select-mode/descriptor/elementDescriptor.js +20 -30
  50. package/select-mode/iframeRpcChannel.d.ts +84 -0
  51. package/select-mode/symbolicate.js +25 -7
  52. package/chunk-QTGDSYSU.mjs +0 -249
@@ -0,0 +1,27 @@
1
+ import type { CommentAnchorElement } from "../_vendor/schemas/generated/comments";
2
+ import type { ElementSource } from "./capture";
3
+ export type AnchorResolution = {
4
+ status: "attached" | "ambiguous" | "detached";
5
+ element?: Element;
6
+ range?: Range;
7
+ };
8
+ type ResolveAnchorOptions = {
9
+ /** The element this anchor resolved to last time; wins ties and survives DOM swaps. */
10
+ previous: Element | null;
11
+ /** Source lookup for candidates; null uses the live fiber source. */
12
+ getSource: ((element: Element) => ElementSource | null) | null;
13
+ };
14
+ /**
15
+ * Re-finds an element anchor (D-15). Every signal proposes candidates and
16
+ * adds its weight; `listKey` and `textPreview` only add to candidates another
17
+ * signal proposed. The best candidate at or above the threshold is
18
+ * `attached`, `ambiguous` when the runner-up is within the margin, and
19
+ * `detached` when nothing reaches the threshold.
20
+ */
21
+ export declare function resolveAnchor(anchor: CommentAnchorElement, root: ParentNode, options: ResolveAnchorOptions): AnchorResolution;
22
+ /**
23
+ * False when the resolved element has no box at this viewport (zero rect or
24
+ * `display: none`): its thread is listed but not pinned here.
25
+ */
26
+ export declare function isResolutionVisible(resolution: AnchorResolution): boolean;
27
+ export {};
@@ -0,0 +1,201 @@
1
+ import {
2
+ getElementSource,
3
+ getTextPreview,
4
+ SHADOW_SELECTOR_DELIMITER
5
+ } from "./capture";
6
+ import { findQuoteRanges } from "./textQuote";
7
+ import { resolveLiveElement } from "./vendor/elementAnchors";
8
+ import {
9
+ findNearestListItemKey,
10
+ getElementFiber,
11
+ normalizeFilePath
12
+ } from "./vendor/fiberElement";
13
+ const ANCHOR_SIGNAL_WEIGHTS = {
14
+ semanticSelector: 3,
15
+ fileAndComponent: 3,
16
+ selector: 1.5,
17
+ nthOfTypePath: 1.5,
18
+ quote: 3,
19
+ lineWithinTolerance: 0.5,
20
+ listKey: 1,
21
+ textPreviewSimilarity: 1
22
+ };
23
+ const ANCHOR_ATTACH_THRESHOLD = 4;
24
+ const ANCHOR_AMBIGUITY_MARGIN = 0.5;
25
+ const ANCHOR_LINE_TOLERANCE = 5;
26
+ const TEXT_PREVIEW_SIMILARITY_THRESHOLD = 0.8;
27
+ const MAX_SOURCE_CANDIDATE_SCAN = 5e3;
28
+ function textSimilarity(first, second) {
29
+ if (first === second) {
30
+ return 1;
31
+ }
32
+ const longest = Math.max(first.length, second.length);
33
+ let previousRow = Array.from(
34
+ { length: second.length + 1 },
35
+ (_, index) => index
36
+ );
37
+ for (let row = 1; row <= first.length; row++) {
38
+ const currentRow = [row];
39
+ for (let column = 1; column <= second.length; column++) {
40
+ const substitution = (previousRow[column - 1] ?? 0) + (first[row - 1] === second[column - 1] ? 0 : 1);
41
+ currentRow[column] = Math.min(
42
+ (previousRow[column] ?? 0) + 1,
43
+ (currentRow[column - 1] ?? 0) + 1,
44
+ substitution
45
+ );
46
+ }
47
+ previousRow = currentRow;
48
+ }
49
+ return 1 - (previousRow[second.length] ?? longest) / longest;
50
+ }
51
+ function querySegment(roots, selector) {
52
+ return roots.flatMap((root) => {
53
+ try {
54
+ return Array.from(root.querySelectorAll(selector));
55
+ } catch {
56
+ return [];
57
+ }
58
+ });
59
+ }
60
+ function queryAll(root, selector) {
61
+ if (!selector) {
62
+ return [];
63
+ }
64
+ try {
65
+ return Array.from(root.querySelectorAll(selector));
66
+ } catch {
67
+ if (!selector.includes(SHADOW_SELECTOR_DELIMITER)) {
68
+ return [];
69
+ }
70
+ }
71
+ const [first = "", ...rest] = selector.split(SHADOW_SELECTOR_DELIMITER);
72
+ let matches = querySegment([root], first);
73
+ for (const segment of rest) {
74
+ const shadowRoots = matches.flatMap((host) => {
75
+ return host.shadowRoot ? [host.shadowRoot] : [];
76
+ });
77
+ matches = querySegment(shadowRoots, segment);
78
+ }
79
+ return matches;
80
+ }
81
+ function getSearchRoot(root) {
82
+ if (root instanceof Element) {
83
+ return root;
84
+ }
85
+ if ("body" in root && root.body instanceof Element) {
86
+ return root.body;
87
+ }
88
+ return null;
89
+ }
90
+ function getQuoteElement(range, tagName) {
91
+ const node = range.commonAncestorContainer;
92
+ const container = node instanceof Element ? node : node.parentElement;
93
+ for (let current = container; current; current = current.parentElement) {
94
+ if (current.tagName.toLowerCase() === tagName) {
95
+ return current;
96
+ }
97
+ }
98
+ return container;
99
+ }
100
+ function resolveAnchor(anchor, root, options) {
101
+ const getSource = options.getSource ?? getElementSource;
102
+ const candidates = /* @__PURE__ */ new Map();
103
+ const add = (element2, weight, range2) => {
104
+ const candidate2 = candidates.get(element2) ?? { score: 0 };
105
+ candidate2.score += weight;
106
+ if (range2 && !candidate2.range) {
107
+ candidate2.range = range2;
108
+ }
109
+ candidates.set(element2, candidate2);
110
+ };
111
+ for (const element2 of queryAll(root, anchor.semanticSelector)) {
112
+ add(element2, ANCHOR_SIGNAL_WEIGHTS.semanticSelector, null);
113
+ }
114
+ for (const element2 of queryAll(root, anchor.selector)) {
115
+ add(element2, ANCHOR_SIGNAL_WEIGHTS.selector, null);
116
+ }
117
+ for (const element2 of queryAll(root, anchor.nthOfTypePath)) {
118
+ add(element2, ANCHOR_SIGNAL_WEIGHTS.nthOfTypePath, null);
119
+ }
120
+ const anchorSource = anchor.source;
121
+ if (anchorSource) {
122
+ const filePath = normalizeFilePath(anchorSource.filePath);
123
+ const componentName = anchorSource.componentChain[0];
124
+ const sameTag = queryAll(root, anchor.tagName).slice(
125
+ 0,
126
+ MAX_SOURCE_CANDIDATE_SCAN
127
+ );
128
+ for (const element2 of sameTag) {
129
+ const source = getSource(element2);
130
+ if (!source || normalizeFilePath(source.filePath) !== filePath || source.componentChain[0] !== componentName) {
131
+ continue;
132
+ }
133
+ const isLineClose = anchorSource.line !== void 0 && source.line !== void 0 && Math.abs(source.line - anchorSource.line) <= ANCHOR_LINE_TOLERANCE;
134
+ add(
135
+ element2,
136
+ ANCHOR_SIGNAL_WEIGHTS.fileAndComponent + (isLineClose ? ANCHOR_SIGNAL_WEIGHTS.lineWithinTolerance : 0),
137
+ null
138
+ );
139
+ }
140
+ }
141
+ const searchRoot = getSearchRoot(root);
142
+ if (anchor.quote && searchRoot) {
143
+ const { withContext, exactOnly } = findQuoteRanges(
144
+ searchRoot,
145
+ anchor.quote
146
+ );
147
+ const uniqueExact = exactOnly.length === 1 ? exactOnly : [];
148
+ const quoteRanges = withContext.length > 0 ? withContext : uniqueExact;
149
+ for (const range2 of quoteRanges) {
150
+ const element2 = getQuoteElement(range2, anchor.tagName);
151
+ if (element2) {
152
+ add(element2, ANCHOR_SIGNAL_WEIGHTS.quote, range2);
153
+ }
154
+ }
155
+ }
156
+ for (const [element2, candidate2] of candidates) {
157
+ if (anchor.listKey) {
158
+ const fiber = getElementFiber(element2);
159
+ if (fiber && findNearestListItemKey(fiber) === anchor.listKey) {
160
+ candidate2.score += ANCHOR_SIGNAL_WEIGHTS.listKey;
161
+ }
162
+ }
163
+ if (textSimilarity(getTextPreview(element2), anchor.textPreview) >= TEXT_PREVIEW_SIMILARITY_THRESHOLD) {
164
+ candidate2.score += ANCHOR_SIGNAL_WEIGHTS.textPreviewSimilarity;
165
+ }
166
+ }
167
+ const previous = options.previous ? resolveLiveElement(options.previous) : null;
168
+ const ranked = Array.from(candidates).sort(
169
+ ([elementA, candidateA], [elementB, candidateB]) => {
170
+ return candidateB.score - candidateA.score || Number(elementB === previous) - Number(elementA === previous);
171
+ }
172
+ );
173
+ const [best, runnerUp] = ranked;
174
+ if (!best || best[1].score < ANCHOR_ATTACH_THRESHOLD) {
175
+ return { status: "detached" };
176
+ }
177
+ const [element, candidate] = best;
178
+ const range = candidate.range ?? (anchor.quote ? findQuoteRanges(element, anchor.quote).exactOnly[0] : void 0);
179
+ const isAmbiguous = runnerUp !== void 0 && best[1].score - runnerUp[1].score <= ANCHOR_AMBIGUITY_MARGIN;
180
+ return {
181
+ status: isAmbiguous ? "ambiguous" : "attached",
182
+ element,
183
+ ...range ? { range } : {}
184
+ };
185
+ }
186
+ function isResolutionVisible(resolution) {
187
+ const element = resolution.element;
188
+ if (!element || resolution.status === "detached") {
189
+ return false;
190
+ }
191
+ const view = element.ownerDocument.defaultView;
192
+ if (view?.getComputedStyle(element).display === "none") {
193
+ return false;
194
+ }
195
+ const rect = element.getBoundingClientRect();
196
+ return rect.width > 0 || rect.height > 0;
197
+ }
198
+ export {
199
+ isResolutionVisible,
200
+ resolveAnchor
201
+ };
@@ -0,0 +1,11 @@
1
+ import type { ElementSource, SourceSymbolicator } from "./capture";
2
+ /**
3
+ * Resolver source lookup that answers in original files once symbolicated.
4
+ * Stored anchors carry original paths while live fibers point at compiled
5
+ * chunks, so unseen locations are symbolicated in batches in the background
6
+ * and `onUpdate` asks the caller to re-resolve with the new answers.
7
+ */
8
+ export declare function createSymbolicatedSourceLookup({ symbolicate, onUpdate, }: {
9
+ symbolicate: SourceSymbolicator;
10
+ onUpdate: () => void;
11
+ }): (element: Element) => ElementSource | null;
@@ -0,0 +1,63 @@
1
+ import { getElementSource } from "./capture";
2
+ const MAX_LOCATIONS_PER_BATCH = 200;
3
+ function getLocationKey(location) {
4
+ return `${location.filePath}:${location.line ?? ""}:${location.col ?? ""}`;
5
+ }
6
+ function createSymbolicatedSourceLookup({
7
+ symbolicate,
8
+ onUpdate
9
+ }) {
10
+ const resolved = /* @__PURE__ */ new Map();
11
+ const requested = /* @__PURE__ */ new Set();
12
+ let queue = [];
13
+ let isFlushScheduled = false;
14
+ const flush = async () => {
15
+ isFlushScheduled = false;
16
+ const batch = queue.slice(0, MAX_LOCATIONS_PER_BATCH);
17
+ queue = queue.slice(MAX_LOCATIONS_PER_BATCH);
18
+ if (queue.length > 0) {
19
+ scheduleFlush();
20
+ }
21
+ const results = await symbolicate(batch);
22
+ for (const [index, location] of batch.entries()) {
23
+ const result = results[index];
24
+ if (result) {
25
+ resolved.set(getLocationKey(location), result);
26
+ }
27
+ }
28
+ onUpdate();
29
+ };
30
+ const scheduleFlush = () => {
31
+ if (isFlushScheduled) {
32
+ return;
33
+ }
34
+ isFlushScheduled = true;
35
+ queueMicrotask(() => {
36
+ void flush();
37
+ });
38
+ };
39
+ return (element) => {
40
+ const source = getElementSource(element);
41
+ if (!source) {
42
+ return null;
43
+ }
44
+ const key = getLocationKey(source);
45
+ const original = resolved.get(key);
46
+ if (original) {
47
+ return { ...original, componentChain: source.componentChain };
48
+ }
49
+ if (!requested.has(key)) {
50
+ requested.add(key);
51
+ queue.push({
52
+ filePath: source.filePath,
53
+ line: source.line,
54
+ col: source.col
55
+ });
56
+ scheduleFlush();
57
+ }
58
+ return source;
59
+ };
60
+ }
61
+ export {
62
+ createSymbolicatedSourceLookup
63
+ };
@@ -0,0 +1,17 @@
1
+ import type { CommentAnchorElement } from "../_vendor/schemas/generated/comments";
2
+ export type TextQuote = NonNullable<CommentAnchorElement["quote"]>;
3
+ export declare const OVERLAY_ROOT_ATTRIBUTE = "data-replo-comments-overlay";
4
+ /**
5
+ * Quote for a text selection: the exact text plus up to 80 characters on each
6
+ * side, taken within `container` so the context survives edits elsewhere.
7
+ */
8
+ export declare function captureQuote(range: Range, container: Element): TextQuote | null;
9
+ /**
10
+ * Every occurrence of the quote under `root`: `withContext` also matches the
11
+ * prefix and suffix, `exactOnly` matches the quoted text alone (the context
12
+ * was edited). Text beyond 500k characters is not searched.
13
+ */
14
+ export declare function findQuoteRanges(root: Element, quote: TextQuote): {
15
+ withContext: Range[];
16
+ exactOnly: Range[];
17
+ };
@@ -0,0 +1,87 @@
1
+ const QUOTE_MAX_CHARS = 2e3;
2
+ const QUOTE_CONTEXT_CHARS = 80;
3
+ const TEXT_SEARCH_MAX_CHARS = 5e5;
4
+ const MAX_QUOTE_MATCHES = 20;
5
+ const OVERLAY_ROOT_ATTRIBUTE = "data-replo-comments-overlay";
6
+ function captureQuote(range, container) {
7
+ const exact = range.toString();
8
+ if (!exact.trim() || exact.length > QUOTE_MAX_CHARS) {
9
+ return null;
10
+ }
11
+ const before = range.cloneRange();
12
+ before.selectNodeContents(container);
13
+ before.setEnd(range.startContainer, range.startOffset);
14
+ const after = range.cloneRange();
15
+ after.selectNodeContents(container);
16
+ after.setStart(range.endContainer, range.endOffset);
17
+ return {
18
+ exact,
19
+ prefix: before.toString().slice(-QUOTE_CONTEXT_CHARS),
20
+ suffix: after.toString().slice(0, QUOTE_CONTEXT_CHARS)
21
+ };
22
+ }
23
+ function indexText(root) {
24
+ const index = { text: "", nodes: [] };
25
+ const visit = (subtree) => {
26
+ const walker = root.ownerDocument.createTreeWalker(
27
+ subtree,
28
+ NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
29
+ );
30
+ for (let node = subtree; node; node = walker.nextNode()) {
31
+ if (node instanceof Element) {
32
+ if (node.shadowRoot && !visit(node.shadowRoot)) {
33
+ return false;
34
+ }
35
+ continue;
36
+ }
37
+ if (!(node instanceof Text) || node.parentElement?.closest(`[${OVERLAY_ROOT_ATTRIBUTE}]`)) {
38
+ continue;
39
+ }
40
+ index.nodes.push({ node, start: index.text.length });
41
+ index.text += node.data;
42
+ if (index.text.length > TEXT_SEARCH_MAX_CHARS) {
43
+ return false;
44
+ }
45
+ }
46
+ return true;
47
+ };
48
+ return visit(root) ? index : null;
49
+ }
50
+ function toRange(index, start, end) {
51
+ const startEntry = index.nodes.findLast((entry) => entry.start <= start);
52
+ const endEntry = index.nodes.findLast((entry) => entry.start < end);
53
+ if (!startEntry || !endEntry || startEntry.node.getRootNode() !== endEntry.node.getRootNode()) {
54
+ return null;
55
+ }
56
+ const range = startEntry.node.ownerDocument.createRange();
57
+ range.setStart(startEntry.node, start - startEntry.start);
58
+ range.setEnd(endEntry.node, end - endEntry.start);
59
+ return range;
60
+ }
61
+ function findQuoteRanges(root, quote) {
62
+ const result = {
63
+ withContext: [],
64
+ exactOnly: []
65
+ };
66
+ const index = quote.exact ? indexText(root) : null;
67
+ if (!index) {
68
+ return result;
69
+ }
70
+ for (let at = index.text.indexOf(quote.exact); at >= 0 && result.exactOnly.length < MAX_QUOTE_MATCHES; at = index.text.indexOf(quote.exact, at + 1)) {
71
+ const range = toRange(index, at, at + quote.exact.length);
72
+ if (!range) {
73
+ continue;
74
+ }
75
+ result.exactOnly.push(range);
76
+ const end = at + quote.exact.length;
77
+ if (index.text.slice(Math.max(0, at - quote.prefix.length), at) === quote.prefix && index.text.slice(end, end + quote.suffix.length) === quote.suffix) {
78
+ result.withContext.push(range);
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ export {
84
+ OVERLAY_ROOT_ATTRIBUTE,
85
+ captureQuote,
86
+ findQuoteRanges
87
+ };
@@ -0,0 +1,53 @@
1
+ /*!
2
+ * Copyright (c) 2024-present Aiden Bai
3
+ * SPDX-License-Identifier: MIT
4
+ * https://github.com/aidenybai/bippy
5
+ */
6
+ export interface DebugSource {
7
+ fileName: string;
8
+ lineNumber?: number;
9
+ columnNumber?: number;
10
+ }
11
+ export interface ServerComponentInfo {
12
+ name: string;
13
+ env?: string;
14
+ debugStack?: unknown;
15
+ owner?: Fiber | ServerComponentInfo | null;
16
+ }
17
+ export interface Fiber {
18
+ tag: number;
19
+ key: string | null;
20
+ type: unknown;
21
+ stateNode: unknown;
22
+ return: Fiber | null;
23
+ child: Fiber | null;
24
+ sibling: Fiber | null;
25
+ alternate: Fiber | null;
26
+ _debugSource?: DebugSource | null;
27
+ _debugStack?: unknown;
28
+ _debugOwner?: Fiber | ServerComponentInfo | null;
29
+ }
30
+ export interface StackFrame {
31
+ functionName?: string;
32
+ fileName?: string;
33
+ lineNumber?: number;
34
+ columnNumber?: number;
35
+ }
36
+ export declare function isFiber(value: unknown): value is Fiber;
37
+ export declare function isCompositeFiber(fiber: Fiber): boolean;
38
+ export declare function isHostFiber(fiber: Fiber): boolean;
39
+ export declare function getFiberFromHostInstance(element: Element): Fiber | null;
40
+ export declare function getDisplayName(type: unknown): string | null;
41
+ export declare function getLatestFiber(fiber: Fiber): Fiber;
42
+ export declare function parseStack(stackString: string): StackFrame[];
43
+ export declare function parseDebugStack(debugStack: unknown): {
44
+ frames: StackFrame[];
45
+ isTrusted: boolean;
46
+ };
47
+ export declare function normalizeFileName(fileName: string): string;
48
+ /**
49
+ * Compiled (unsymbolicated) source of a fiber, in bippy's `getSource` order:
50
+ * `_debugSource` (React ≤18), the fiber's own `_debugStack` (React 19), an
51
+ * owned child's `_debugStack`, then the throwing dispatcher.
52
+ */
53
+ export declare function getFiberSource(fiber: Fiber): DebugSource | null;