arcy.js 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -3
- package/dist/arcy.chat.ab181cf5a84b.js +1258 -0
- package/dist/arcy.flow.fef131718e42.js +3 -0
- package/dist/arcy.legacy.ab4e9266b657.js +567 -0
- package/dist/arcy.loader.js +2 -0
- package/dist/arcy.modern.60faeffc62a2.js +567 -0
- package/dist/arcy.picker.c598a581d964.js +59 -0
- package/dist/design-mode.cjs +135 -0
- package/dist/design-mode.d.cts +197 -0
- package/dist/design-mode.d.ts +197 -0
- package/dist/design-mode.js +127 -0
- package/dist/fonts/dm-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/dm-sans-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-latin.woff2 +0 -0
- package/dist/fonts/inter-latin-ext.woff2 +0 -0
- package/dist/fonts/inter-latin.woff2 +0 -0
- package/dist/fonts/lato-latin-ext.woff2 +0 -0
- package/dist/fonts/lato-latin.woff2 +0 -0
- package/dist/fonts/manrope-latin-ext.woff2 +0 -0
- package/dist/fonts/manrope-latin.woff2 +0 -0
- package/dist/fonts/montserrat-latin-ext.woff2 +0 -0
- package/dist/fonts/montserrat-latin.woff2 +0 -0
- package/dist/fonts/nunito-latin-ext.woff2 +0 -0
- package/dist/fonts/nunito-latin.woff2 +0 -0
- package/dist/fonts/open-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/open-sans-latin.woff2 +0 -0
- package/dist/fonts/playfair-display-latin-ext.woff2 +0 -0
- package/dist/fonts/playfair-display-latin.woff2 +0 -0
- package/dist/fonts/poppins-latin-ext.woff2 +0 -0
- package/dist/fonts/poppins-latin.woff2 +0 -0
- package/dist/fonts/roboto-latin-ext.woff2 +0 -0
- package/dist/fonts/roboto-latin.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin-ext.woff2 +0 -0
- package/dist/fonts/source-sans-3-latin.woff2 +0 -0
- package/dist/fonts/work-sans-latin-ext.woff2 +0 -0
- package/dist/fonts/work-sans-latin.woff2 +0 -0
- package/dist/index.cjs +5114 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +5109 -0
- package/dist/snippet.cjs +46 -0
- package/dist/snippet.d.cts +39 -0
- package/dist/snippet.d.ts +39 -0
- package/dist/snippet.js +43 -0
- package/package.json +69 -5
- package/index.js +0 -5
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The thin identity core: the fields an interaction event carries in
|
|
3
|
+
* `payload.fingerprint`, and the fields the backend hashes into
|
|
4
|
+
* `fingerprint_key` (sorted keys, canonical JSON, SHA-256; slice 19.0).
|
|
5
|
+
*
|
|
6
|
+
* This shape is a cross-repo contract, not a convenience type. The same
|
|
7
|
+
* object is captured in two places, by 7.3's collector on every interactive
|
|
8
|
+
* click and by the design-mode picker (15.4) when an operator selects an
|
|
9
|
+
* element, and the two must produce byte-identical JSON for the same element
|
|
10
|
+
* or match counts (15.9), the observed-element list (D180), and the
|
|
11
|
+
* fingerprint-confidence stats silently read zero. Three rules keep the hash
|
|
12
|
+
* stable, enforced by the golden-vector parity tests:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Flat.** `computeFingerprintKey` sorts only top-level keys, so a nested
|
|
15
|
+
* object would hash in whatever insertion order the client used. The
|
|
16
|
+
* semantic ancestor is three flat fields for exactly this reason.
|
|
17
|
+
* 2. **Absent keys are omitted, never `null` or `undefined`.**
|
|
18
|
+
* `JSON.stringify` drops `undefined` properties but keeps `null` ones, so
|
|
19
|
+
* the two spellings of "no value" would hash differently. There is one
|
|
20
|
+
* spelling: the key does not appear.
|
|
21
|
+
* 3. **No raw class string, anywhere.** G5 (PRODUCT_SCOPE §6.2.5.1.1): on a
|
|
22
|
+
* Tailwind app a class string is a presentational sentence that changes
|
|
23
|
+
* when a designer touches padding. Classes enter the system only through
|
|
24
|
+
* the ranked selector generator (15.2), after utility suppression.
|
|
25
|
+
*
|
|
26
|
+
* Changing this field set orphans every stored `fingerprint_key`. It is a
|
|
27
|
+
* migration, not an edit (see ADR 0059).
|
|
28
|
+
*/
|
|
29
|
+
interface EventFingerprint {
|
|
30
|
+
/** Lowercase tag name. The one field that is always present. */
|
|
31
|
+
tag: string;
|
|
32
|
+
/** Subtree text, whitespace-collapsed, PII-masked, capped. Primary matcher. */
|
|
33
|
+
text?: string;
|
|
34
|
+
/** The icon-only control's signal; masked and capped like text. */
|
|
35
|
+
ariaLabel?: string;
|
|
36
|
+
/** Form controls have no textContent; their placeholder stands in. */
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The element's own id, only when it looks author-written. Generated ids
|
|
40
|
+
* (React useId's `:r3:`, Radix) change per render and would fragment the
|
|
41
|
+
* key, turning one button into a new "element" every session.
|
|
42
|
+
*/
|
|
43
|
+
id?: string;
|
|
44
|
+
/** The explicit `role` attribute. No implicit-role table: not worth its bytes. */
|
|
45
|
+
role?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The element's own `data-testid`, `data-test`, or `data-cy`, first one
|
|
48
|
+
* present (ADR 0134). The strongest evidence a page can offer that two
|
|
49
|
+
* elements are the same element: it is written by a developer to be stable
|
|
50
|
+
* and survives a redesign, a copy edit, and a locale switch at once.
|
|
51
|
+
*
|
|
52
|
+
* Read from the element itself, unlike `ancestorAttr`/`ancestorValue`,
|
|
53
|
+
* which report the same attributes found on an ancestor. Absent on most
|
|
54
|
+
* elements, and absence is spelled the way every optional field here is
|
|
55
|
+
* spelled: the key does not appear.
|
|
56
|
+
*/
|
|
57
|
+
testId?: string;
|
|
58
|
+
/** 0-based index among same-tag element siblings. The structural tiebreak. */
|
|
59
|
+
siblingIndex: number;
|
|
60
|
+
/** Nearest ancestor carrying a semantic identifier, as three flat fields. */
|
|
61
|
+
ancestorTag?: string;
|
|
62
|
+
/** Which attribute won the ancestor walk (e.g. "data-testid", "id"). */
|
|
63
|
+
ancestorAttr?: string;
|
|
64
|
+
/** That attribute's value, masked and capped like text. */
|
|
65
|
+
ancestorValue?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* How strictly the runtime matcher (15.3) scores candidates. A discrete step,
|
|
69
|
+
* not a float: the mapping from step to score threshold lives in the matcher,
|
|
70
|
+
* so tuning the scoring function never migrates stored targets. 1 is loosest,
|
|
71
|
+
* 5 is strictest. The slider help text is the workflow: loosen when the
|
|
72
|
+
* element is not found, tighten when the wrong element is found.
|
|
73
|
+
*/
|
|
74
|
+
type Precision = 1 | 2 | 3 | 4 | 5;
|
|
75
|
+
/**
|
|
76
|
+
* The full element target: the identity core plus the authoring layer an
|
|
77
|
+
* operator sets in the picker (15.4) and edits in manual adjustment (15.9).
|
|
78
|
+
* Stored on a flow step, so this object outlives any single arcy.js version.
|
|
79
|
+
*/
|
|
80
|
+
interface TargetFingerprint {
|
|
81
|
+
/**
|
|
82
|
+
* Schema version. Durable data stored in customers' flows needs a
|
|
83
|
+
* discriminant before a v2 exists, or the v2 migration has nothing to
|
|
84
|
+
* dispatch on. Bump only with a migration plan for stored targets.
|
|
85
|
+
*/
|
|
86
|
+
v: 1;
|
|
87
|
+
/**
|
|
88
|
+
* The identity core, embedded as-is and never spread flat. Everything that
|
|
89
|
+
* hashes or matches reads `target.core`, structurally the same object shape
|
|
90
|
+
* an event carries, so a field can never be forgotten in translation.
|
|
91
|
+
*/
|
|
92
|
+
core: EventFingerprint;
|
|
93
|
+
/**
|
|
94
|
+
* The active selector refinement. Default null (§6.2.5.1: the reference
|
|
95
|
+
* leads with text, not a selector, and that default is preserved). Never
|
|
96
|
+
* hand-authored; 15.2's generator is the only writer, and it writes only
|
|
97
|
+
* when the top-ranked candidate is top-two-tier and matches exactly one
|
|
98
|
+
* element at pick time (D318).
|
|
99
|
+
*/
|
|
100
|
+
selector: string | null;
|
|
101
|
+
/**
|
|
102
|
+
* Ranked stable-selector candidates captured at pick time by 15.2, shown
|
|
103
|
+
* read-only in manual adjustment as a diagnostic. Capped so a pathological
|
|
104
|
+
* page cannot bloat a stored target.
|
|
105
|
+
*/
|
|
106
|
+
selectorCandidates: string[];
|
|
107
|
+
/**
|
|
108
|
+
* 1-based position among document-ordered candidates that survive the
|
|
109
|
+
* precision threshold: "if multiple matches, select 1st element". Always
|
|
110
|
+
* present. This is the G5 policy reversal (D013): a human pointed at the
|
|
111
|
+
* element and set this, so ties take the ordinal, never refuse-to-guess.
|
|
112
|
+
*/
|
|
113
|
+
ordinal: number;
|
|
114
|
+
precision: Precision;
|
|
115
|
+
/**
|
|
116
|
+
* Marks text as changing at runtime ("3 items in cart"), so the matcher
|
|
117
|
+
* down-weights text rather than treating its mismatch as disqualifying.
|
|
118
|
+
*/
|
|
119
|
+
dynamicText: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The one masking token. Exported because the matcher (15.3) must treat it
|
|
123
|
+
* as an absent signal rather than a value: every masked email collapses to
|
|
124
|
+
* this same string, so scoring it as text would make any PII match any
|
|
125
|
+
* other PII exactly. */
|
|
126
|
+
declare const MASK = "[masked]";
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Bumped whenever a message shape changes incompatibly. A receiver that reads
|
|
130
|
+
* a number it does not speak refuses and says so, rather than guessing at a
|
|
131
|
+
* payload it half understands (the same posture ADR 0069 took for loading).
|
|
132
|
+
*/
|
|
133
|
+
declare const DESIGN_MODE_PROTOCOL = 2;
|
|
134
|
+
/** Posted once, when the overlay has mounted and the operator can click. */
|
|
135
|
+
declare const DESIGN_MODE_READY = "arcy:design-mode:ready";
|
|
136
|
+
/** Posted for every pick. The last one wins; there is no confirm (D502). */
|
|
137
|
+
declare const DESIGN_MODE_PICK = "arcy:design-mode:pick";
|
|
138
|
+
/** Posted when the operator leaves design mode. */
|
|
139
|
+
declare const DESIGN_MODE_EXIT = "arcy:design-mode:exit";
|
|
140
|
+
/**
|
|
141
|
+
* Posted once per pick, asynchronously, after rasterization finishes
|
|
142
|
+
* (15.7). Never gates `DESIGN_MODE_PICK`'s own delivery (D502): a receiver
|
|
143
|
+
* that has not built `DESIGN_MODE_SCREENSHOT` into its `switch` simply drops
|
|
144
|
+
* it via the same unknown-`type` path every other unrecognized message takes,
|
|
145
|
+
* so this needed no `DESIGN_MODE_PROTOCOL` bump to add.
|
|
146
|
+
*/
|
|
147
|
+
declare const DESIGN_MODE_SCREENSHOT = "arcy:design-mode:screenshot";
|
|
148
|
+
interface DesignModeReady {
|
|
149
|
+
type: typeof DESIGN_MODE_READY;
|
|
150
|
+
protocol: number;
|
|
151
|
+
}
|
|
152
|
+
interface DesignModePick {
|
|
153
|
+
type: typeof DESIGN_MODE_PICK;
|
|
154
|
+
protocol: number;
|
|
155
|
+
target: TargetFingerprint;
|
|
156
|
+
/**
|
|
157
|
+
* The path the element was picked on, read at pick time (D504). A target
|
|
158
|
+
* and the page it lives on are stored as two separate fields on a step, and
|
|
159
|
+
* browse mode means the operator can be somewhere else by the time they
|
|
160
|
+
* pick. Without this the step would keep whichever page was chosen in the
|
|
161
|
+
* dropdown, and a target filed under the wrong page reports zero forever.
|
|
162
|
+
*/
|
|
163
|
+
route: string;
|
|
164
|
+
/**
|
|
165
|
+
* How many elements on this page matched the target at the moment it was
|
|
166
|
+
* picked (ADR 0132). Scored here, where the live document and the matcher
|
|
167
|
+
* both already are, rather than asked for afterwards over a channel to a tab
|
|
168
|
+
* the pick is about to close.
|
|
169
|
+
*
|
|
170
|
+
* Information for the operator, never a gate: zero and forty are both real
|
|
171
|
+
* answers worth showing, and neither is a reason to refuse a save.
|
|
172
|
+
*/
|
|
173
|
+
matchCount: number;
|
|
174
|
+
}
|
|
175
|
+
interface DesignModeExit {
|
|
176
|
+
type: typeof DESIGN_MODE_EXIT;
|
|
177
|
+
protocol: number;
|
|
178
|
+
}
|
|
179
|
+
interface DesignModeScreenshot {
|
|
180
|
+
type: typeof DESIGN_MODE_SCREENSHOT;
|
|
181
|
+
protocol: number;
|
|
182
|
+
/** A `data:image/png;base64,...` artifact of the most recently picked
|
|
183
|
+
* element. Last one wins, same as the pick it decorates: a receiver simply
|
|
184
|
+
* overwrites whatever it is holding. */
|
|
185
|
+
screenshot: string;
|
|
186
|
+
}
|
|
187
|
+
type DesignModeMessage = DesignModeReady | DesignModePick | DesignModeExit | DesignModeScreenshot;
|
|
188
|
+
/**
|
|
189
|
+
* The receiver's only entry point: turns `event.data` into a message it can
|
|
190
|
+
* act on, or null. Says nothing about *who* sent it. The other two checks
|
|
191
|
+
* D501 requires, the source window and the origin, belong to the receiver,
|
|
192
|
+
* because only it knows which window it opened and which environment it is
|
|
193
|
+
* authoring in.
|
|
194
|
+
*/
|
|
195
|
+
declare function readDesignModeMessage(data: unknown): DesignModeMessage | null;
|
|
196
|
+
|
|
197
|
+
export { DESIGN_MODE_EXIT, DESIGN_MODE_PICK, DESIGN_MODE_PROTOCOL, DESIGN_MODE_READY, DESIGN_MODE_SCREENSHOT, type DesignModeExit, type DesignModeMessage, type DesignModePick, type DesignModeReady, type DesignModeScreenshot, MASK, readDesignModeMessage };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* arcy.js — https://arcyai.com */
|
|
2
|
+
|
|
3
|
+
// src/fingerprint/model.ts
|
|
4
|
+
var FINGERPRINT_TEXT_MAX = 80;
|
|
5
|
+
var SELECTOR_CANDIDATES_MAX = 5;
|
|
6
|
+
|
|
7
|
+
// src/fingerprint/capture.ts
|
|
8
|
+
var MASK = "[masked]";
|
|
9
|
+
|
|
10
|
+
// src/picker/message.ts
|
|
11
|
+
var DESIGN_MODE_PROTOCOL = 2;
|
|
12
|
+
var DESIGN_MODE_READY = "arcy:design-mode:ready";
|
|
13
|
+
var DESIGN_MODE_PICK = "arcy:design-mode:pick";
|
|
14
|
+
var DESIGN_MODE_EXIT = "arcy:design-mode:exit";
|
|
15
|
+
var DESIGN_MODE_SCREENSHOT = "arcy:design-mode:screenshot";
|
|
16
|
+
var TAG_MAX = 64;
|
|
17
|
+
var ROUTE_MAX = 2048;
|
|
18
|
+
var SELECTOR_MAX = 512;
|
|
19
|
+
var ORDINAL_MAX = 1e4;
|
|
20
|
+
var SIBLING_INDEX_MAX = 1e5;
|
|
21
|
+
var SCREENSHOT_DATA_URL_MAX = 4e6;
|
|
22
|
+
var SCREENSHOT_PREFIX = "data:image/png;base64,";
|
|
23
|
+
var MATCH_COUNT_MAX = 1e6;
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function isBoundedString(value, max) {
|
|
28
|
+
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
29
|
+
}
|
|
30
|
+
function isOptionalBoundedString(value, max) {
|
|
31
|
+
return value === void 0 || isBoundedString(value, max);
|
|
32
|
+
}
|
|
33
|
+
function isBoundedInteger(value, min, max) {
|
|
34
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
35
|
+
}
|
|
36
|
+
function readCore(value) {
|
|
37
|
+
if (!isRecord(value)) return null;
|
|
38
|
+
if (!isBoundedString(value.tag, TAG_MAX)) return null;
|
|
39
|
+
if (!isBoundedInteger(value.siblingIndex, 0, SIBLING_INDEX_MAX)) return null;
|
|
40
|
+
const optional = [
|
|
41
|
+
"text",
|
|
42
|
+
"ariaLabel",
|
|
43
|
+
"placeholder",
|
|
44
|
+
"id",
|
|
45
|
+
"role",
|
|
46
|
+
"testId",
|
|
47
|
+
"ancestorTag",
|
|
48
|
+
"ancestorAttr",
|
|
49
|
+
"ancestorValue"
|
|
50
|
+
];
|
|
51
|
+
for (const key of optional) {
|
|
52
|
+
if (!isOptionalBoundedString(value[key], FINGERPRINT_TEXT_MAX)) return null;
|
|
53
|
+
}
|
|
54
|
+
const core = {
|
|
55
|
+
tag: value.tag,
|
|
56
|
+
siblingIndex: value.siblingIndex
|
|
57
|
+
};
|
|
58
|
+
for (const key of optional) {
|
|
59
|
+
const field = value[key];
|
|
60
|
+
if (typeof field === "string") core[key] = field;
|
|
61
|
+
}
|
|
62
|
+
return core;
|
|
63
|
+
}
|
|
64
|
+
function readTarget(value) {
|
|
65
|
+
if (!isRecord(value)) return null;
|
|
66
|
+
if (value.v !== 1) return null;
|
|
67
|
+
const core = readCore(value.core);
|
|
68
|
+
if (!core) return null;
|
|
69
|
+
const { selector, selectorCandidates, ordinal, precision, dynamicText } = value;
|
|
70
|
+
if (selector !== null && !isBoundedString(selector, SELECTOR_MAX)) return null;
|
|
71
|
+
if (!Array.isArray(selectorCandidates)) return null;
|
|
72
|
+
if (selectorCandidates.length > SELECTOR_CANDIDATES_MAX) return null;
|
|
73
|
+
if (!selectorCandidates.every((c) => isBoundedString(c, SELECTOR_MAX)))
|
|
74
|
+
return null;
|
|
75
|
+
if (!isBoundedInteger(ordinal, 1, ORDINAL_MAX)) return null;
|
|
76
|
+
if (!isBoundedInteger(precision, 1, 5)) return null;
|
|
77
|
+
if (typeof dynamicText !== "boolean") return null;
|
|
78
|
+
return {
|
|
79
|
+
v: 1,
|
|
80
|
+
core,
|
|
81
|
+
selector,
|
|
82
|
+
selectorCandidates,
|
|
83
|
+
ordinal,
|
|
84
|
+
precision,
|
|
85
|
+
dynamicText
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function readDesignModeMessage(data) {
|
|
89
|
+
if (!isRecord(data)) return null;
|
|
90
|
+
if (data.protocol !== DESIGN_MODE_PROTOCOL) return null;
|
|
91
|
+
switch (data.type) {
|
|
92
|
+
case DESIGN_MODE_READY:
|
|
93
|
+
return { type: DESIGN_MODE_READY, protocol: DESIGN_MODE_PROTOCOL };
|
|
94
|
+
case DESIGN_MODE_EXIT:
|
|
95
|
+
return { type: DESIGN_MODE_EXIT, protocol: DESIGN_MODE_PROTOCOL };
|
|
96
|
+
case DESIGN_MODE_PICK: {
|
|
97
|
+
const target = readTarget(data.target);
|
|
98
|
+
if (!target) return null;
|
|
99
|
+
const { route, matchCount } = data;
|
|
100
|
+
if (!isBoundedString(route, ROUTE_MAX)) return null;
|
|
101
|
+
if (!route.startsWith("/")) return null;
|
|
102
|
+
if (route.startsWith("//")) return null;
|
|
103
|
+
if (!isBoundedInteger(matchCount, 0, MATCH_COUNT_MAX)) return null;
|
|
104
|
+
return {
|
|
105
|
+
type: DESIGN_MODE_PICK,
|
|
106
|
+
protocol: DESIGN_MODE_PROTOCOL,
|
|
107
|
+
target,
|
|
108
|
+
route,
|
|
109
|
+
matchCount
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case DESIGN_MODE_SCREENSHOT: {
|
|
113
|
+
const { screenshot } = data;
|
|
114
|
+
if (!isBoundedString(screenshot, SCREENSHOT_DATA_URL_MAX)) return null;
|
|
115
|
+
if (!screenshot.startsWith(SCREENSHOT_PREFIX)) return null;
|
|
116
|
+
return {
|
|
117
|
+
type: DESIGN_MODE_SCREENSHOT,
|
|
118
|
+
protocol: DESIGN_MODE_PROTOCOL,
|
|
119
|
+
screenshot
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { DESIGN_MODE_EXIT, DESIGN_MODE_PICK, DESIGN_MODE_PROTOCOL, DESIGN_MODE_READY, DESIGN_MODE_SCREENSHOT, MASK, readDesignModeMessage };
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|